WunderGraph Cosmo Router on Ubuntu 24.04 on Azure User Guide
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.

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 (
productson127.0.0.1:4001,inventoryon127.0.0.1:4002), both bound to loopback only - nginx on port
80reverse proxying the router, serving a public/healthendpoint and gating/graphqlbehind a per instance API key cosmo-subgraph-products.service,cosmo-subgraph-inventory.service,cosmo-router.serviceandnginx.serviceas 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

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.

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.

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.

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
serverblock to/etc/nginx/sites-available/wundergraph-cosmoreferencing your certificate and key, open443/tcpon the NSG, thensudo 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.