Developer Tools Azure

WunderGraph Cosmo Router on Ubuntu 24.04 on Azure User Guide

| Product: WunderGraph Cosmo Router on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of WunderGraph Cosmo Router on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Cosmo Router is the open source, Apache-2.0 licensed data plane of the WunderGraph Cosmo GraphQL federation platform: a single, statically compiled Go binary that federates many independent GraphQL services, called subgraphs, into one unified graph that clients query through a single endpoint. It plans and executes federated queries across your subgraphs, resolves shared entities by key, and returns one joined response, so a client can ask for fields that live in several services in a single round trip.

The cloudimg image runs the router standalone against a locally composed execution config, so it never needs the Cosmo Cloud control plane or a graph API token. To make federation provable the moment the instance boots, the image ships a small two subgraph demo (a products service and an inventory service) composed at build time with the Cosmo CLI. The router listens on the loopback interface and is fronted by nginx on port 80; the federated /graphql endpoint is gated by an API key generated uniquely for each VM on first boot, written to a 0600 root:root credentials file, while a public /health endpoint stays open for liveness checks. Backed by 24/7 cloudimg support.

WunderGraph and Cosmo are trademarks of their respective owner. This image is produced by cloudimg and is not affiliated with, endorsed by, or sponsored by WunderGraph. It ships the free and open source, Apache-2.0 licensed Cosmo Router, unmodified. The Cosmo Studio SaaS control plane is not included.

The Cosmo Router version 0.333.2, the two demo subgraph services, the router and nginx all active, and the listeners showing nginx on port 80 with the router and the two subgraphs bound to loopback only

What is included:

  • WunderGraph Cosmo Router v0.333.2 (the open source, Apache-2.0 licensed federation router), the official release binary run unmodified
  • A working demo supergraph composed at build time from two Apollo Federation v2 subgraphs (products on 127.0.0.1:4001, inventory on 127.0.0.1:4002), both bound to loopback only
  • nginx on port 80 reverse proxying the router, serving a public /health endpoint and gating /graphql behind a per instance API key
  • cosmo-subgraph-products.service, cosmo-subgraph-inventory.service, cosmo-router.service and nginx.service as systemd units, enabled and active on boot
  • A unique API key generated per VM on first boot, never baked into the image, written to /root/wundergraph-cosmo-credentials.txt
  • Ubuntu 24.04 LTS base with latest security patches applied at build time
  • Azure Linux Agent for seamless cloud integration and SSH key injection
  • 24/7 cloudimg support with guaranteed 24 hour response SLA

Prerequisites

  • Active Azure subscription, SSH public key, VNet + subnet in target region
  • Subscription to the WunderGraph Cosmo Router listing on Azure Marketplace

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is a sensible starting point. For higher request volumes use Standard_D2s_v5 or larger. NSG inbound: allow 22/tcp from your management network and 80/tcp for the GraphQL endpoint from the networks that call it.

Step 1: Deploy from the Azure Portal

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for WunderGraph Cosmo Router by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Then Review + create and Create.

Step 2: Deploy from the Azure CLI

RG="cosmo-prod"; LOCATION="eastus"; VM_NAME="cosmo-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/wundergraph-cosmo-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name cosmo-vnet --address-prefix 10.100.0.0/16 --subnet-name cosmo-subnet --subnet-prefix 10.100.1.0/24
az network nsg create -g "$RG" --name cosmo-nsg
az network nsg rule create -g "$RG" --nsg-name cosmo-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name cosmo-nsg --name allow-http --priority 110 \
  --destination-port-ranges 80 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name cosmo-vnet --subnet cosmo-subnet --nsg cosmo-nsg --public-ip-sku Standard

Step 3: Verify the services

SSH to the VM as azureuser and confirm the router, its two demo subgraphs and nginx are running, then check the listeners:

/opt/cosmo/router --version | head -5
systemctl is-active cosmo-subgraph-products cosmo-subgraph-inventory cosmo-router nginx | paste -sd' ' -
sudo ss -tlnp | grep -E ':80 |:3002|:4001|:4002' | awk '{print $1"  "$4}' | sort -u

The version prints 0.333.2, all four services report active, and the listeners show nginx on 0.0.0.0:80 with the router on 127.0.0.1:3002 and the two subgraphs on 127.0.0.1:4001 and 127.0.0.1:4002 — the router and subgraphs are bound to the loopback interface, so the only public entry point is nginx on port 80:

Router:
  Version: 0.333.2
active active active active
LISTEN  0.0.0.0:80
LISTEN  127.0.0.1:3002
LISTEN  127.0.0.1:4001
LISTEN  127.0.0.1:4002

The Cosmo Router version 0.333.2, the two demo subgraph services, the router and nginx all active, and the listeners showing nginx on port 80 with the router and the two subgraphs bound to loopback only

Step 4: Check the health endpoint

The router exposes a public, unauthenticated /health liveness endpoint, served through nginx on port 80, ideal for a load balancer health probe:

curl -s -o /dev/null -w 'GET /health -> HTTP %{http_code}\n' http://localhost/health
curl -s http://localhost/health; echo

This returns HTTP 200 with the body OK. No authentication is required for this endpoint; the federated GraphQL endpoint below is gated separately.

Step 5: Review the per instance API key

The federated /graphql endpoint is protected by an API key that is generated uniquely on the first boot of each VM. It is written, along with the resolved public URL, to a 0600 root:root credentials file. View it (the key is masked in the guide, but the real value is on your VM):

sudo stat -c '%A %U:%G %n' /root/wundergraph-cosmo-credentials.txt
sudo grep -E '^COSMO_API_KEY=|^COSMO_URL=' /root/wundergraph-cosmo-credentials.txt \
  | sed -r 's/(COSMO_API_KEY)=.*/\1=<PER-VM-64-HEX-API-KEY>/'

The file is -rw------- root:root. The COSMO_API_KEY is a 64 hex character value unique to this VM, generated by openssl rand -hex 32 on first boot before nginx started serving traffic. There is no default or known key in the image, and the same key gates every call to the federated endpoint.

The wundergraph-cosmo-credentials.txt file at mode 0600 root root showing the per VM API key masked and the resolved public URL, with a note that the key is generated uniquely on first boot

Step 6: Verify the API security model

The federated /graphql endpoint requires the per instance API key in the X-API-Key header. Confirm that a missing or wrong key is rejected with 401, while the per instance key is accepted with 200:

KEY=$(sudo grep '^COSMO_API_KEY=' /root/wundergraph-cosmo-credentials.txt | cut -d= -f2-)
Q='{"query":"{ topProducts { id } }"}'
curl -s -o /dev/null -w '/graphql (no key)    -> HTTP %{http_code}\n' -X POST -H 'Content-Type: application/json' -d "$Q" http://localhost/graphql
curl -s -o /dev/null -w '/graphql (wrong key) -> HTTP %{http_code}\n' -X POST -H 'Content-Type: application/json' -H 'X-API-Key: wrong' -d "$Q" http://localhost/graphql
curl -s -o /dev/null -w '/graphql (real key)  -> HTTP %{http_code}\n' -X POST -H 'Content-Type: application/json' -H "X-API-Key: $KEY" -d "$Q" http://localhost/graphql

The no key and wrong key calls are rejected with 401, and the call with the real per instance key returns 200. This is the security model: the health probe is open, and the federated GraphQL endpoint is gated by the per instance key, so the router is never left as an open, unauthenticated data gateway.

curl showing the federated GraphQL endpoint rejecting a missing key and a wrong key with HTTP 401 and accepting the per instance key with HTTP 200

Step 7: Run a real federated query

This is the point of a federation router: one query, one endpoint, data joined from several subgraphs. The topProducts field is served by the products subgraph, while inStock and deliveryEstimate for the same products are served by the inventory subgraph. The router plans the query, calls both subgraphs, resolves the shared Product entity by its key, and returns a single joined response:

KEY=$(sudo grep '^COSMO_API_KEY=' /root/wundergraph-cosmo-credentials.txt | cut -d= -f2-)
curl -s -X POST http://localhost/graphql \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $KEY" \
  -d '{"query":"{ topProducts { id name price inStock deliveryEstimate } }"}' \
  | python3 -m json.tool

The response joins name and price from the products subgraph with inStock and deliveryEstimate from the inventory subgraph, for each product, in a single result:

{
    "data": {
        "topProducts": [
            {
                "id": "1",
                "name": "Cosmo Router T-Shirt",
                "price": 24.99,
                "inStock": true,
                "deliveryEstimate": "2-3 business days"
            },
            {
                "id": "2",
                "name": "GraphQL Federation Mug",
                "price": 12.5,
                "inStock": false,
                "deliveryEstimate": "3-4 weeks"
            },
            {
                "id": "3",
                "name": "WunderGraph Cap",
                "price": 18,
                "inStock": true,
                "deliveryEstimate": "1-2 business days"
            }
        ]
    }
}

Neither subgraph on its own can answer this query — the products service does not know stock levels, and the inventory service does not know names or prices. The router is what makes them queryable as one graph.

A federated GraphQL query returning topProducts with name and price from the products subgraph joined with inStock and deliveryEstimate from the inventory subgraph in a single response

Step 8: Swap in your own subgraphs

The demo supergraph is composed from two local subgraph schemas and a small graph config. To federate your own services, edit the graph config, point it at your subgraphs, recompose the execution config with the Cosmo CLI, and restart the router:

# The graph config lists each subgraph name, routing URL and SDL file:
sudo cat /etc/cosmo/graph.yaml
# The composed execution config the router loads at startup:
sudo head -c 300 /etc/cosmo/execution-config.json; echo

To point at your own subgraphs: replace the routing_url and schema.file entries in /etc/cosmo/graph.yaml with your services, install the Cosmo CLI (npm install -g wgc), recompose with sudo wgc router compose -i /etc/cosmo/graph.yaml -o /etc/cosmo/execution-config.json, then sudo systemctl restart cosmo-router. The two bundled Node demo subgraphs are systemd services (cosmo-subgraph-products, cosmo-subgraph-inventory) you can disable once you have wired in your own upstreams. For production graphs managed across a team, connect the router to the Cosmo Cloud schema registry instead of a static file — see the WunderGraph Cosmo documentation.

Step 9: Call the endpoint from your own machine

From anywhere that can reach the VM on port 80, call the federated endpoint at the VM public IP with your per instance API key. The /health check needs no credentials:

curl http://<vm-ip>/health
curl -s -X POST http://<vm-ip>/graphql \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: <YOUR-PER-VM-API-KEY>' \
  -d '{"query":"{ topProducts { id name inStock } }"}'

Fetch <YOUR-PER-VM-API-KEY> from /root/wundergraph-cosmo-credentials.txt on the VM (sudo grep '^COSMO_API_KEY=' /root/wundergraph-cosmo-credentials.txt). Distribute it to your GraphQL clients as their gateway credential, or rotate it by editing /etc/nginx/conf.d/cosmo-apikey.conf and reloading nginx.

Step 10: Use your own domain and HTTPS (production)

The image serves the endpoint over plain HTTP on port 80, which is convenient behind a load balancer or private network. For production you should terminate TLS in front of the router so credentials and payloads travel encrypted:

  • Front the VM with Azure Application Gateway, a load balancer with a managed certificate, or a CDN, and forward to nginx on port 80.
  • Or install your own certificate: add a TLS server block to /etc/nginx/sites-available/wundergraph-cosmo referencing your certificate and key, open 443/tcp on the NSG, then sudo systemctl reload nginx.
  • Point a DNS name you control at the VM public IP and use that name (and https://) for your GraphQL consumers.

Step 11: Server components

Component Version / Detail
Federation router WunderGraph Cosmo Router v0.333.2 (open source, Apache-2.0)
Router endpoint /graphql on loopback 127.0.0.1:3002, fronted by nginx on :80
API front nginx on :80 reverse proxying to 127.0.0.1:3002
Demo subgraphs products (127.0.0.1:4001) + inventory (127.0.0.1:4002), Apollo Federation v2, loopback only
Execution config static, composed at build time by the Cosmo CLI (wgc router compose) — no control plane needed
Health endpoint /health (public, no auth)
API key per instance, generated first boot into /root/wundergraph-cosmo-credentials.txt, required on /graphql via X-API-Key

Support

Every cloudimg image is backed by 24/7 support with a guaranteed 24 hour response SLA. For help with this image, contact support@cloudimg.co.uk.

WunderGraph and Cosmo are trademarks of their respective owner. This image ships the free and open source, Apache-2.0 licensed Cosmo Router, unmodified, and is not affiliated with or endorsed by WunderGraph. The Cosmo Studio SaaS control plane is not included.