Tutorials

Self-host a sovereign gateway on Azure Container Apps

From a single docker pull to a live, scale-to-zero HTTPS endpoint with data-residency enforcement, end to end.

Deploy the Routeplane gateway to Azure Container Apps with scale-to-zero: it costs nothing while idle, cold-starts on the first request, and terminates TLS for you. By the end you'll have a live HTTPS endpoint serving completions, with an in-region provider for data-residency enforcement.

You'll need: the Azure CLI (az) logged in to a subscription, the containerapp extension (az extension add --name containerapp), and at least one provider key. Set a few shell variables, RG (resource group), LOC (e.g. centralindia), and APP, before you start.

1. Create the environment

The gateway image is published to a public registry, so there's nothing to build, Container Apps pulls it directly. Create a resource group and a Container Apps environment:

bashresource group + environment
az group create -n "$RG" -l "$LOC"

az containerapp env create \
  -n "rp-env" -g "$RG" -l "$LOC"

2. Deploy the gateway with scale-to-zero

Point it at the published image. Ingress on target port 8080, --min-replicas 0 for scale-to-zero, the provider key as a Container Apps secret, and the key surfaced to the process as an env var via secretref:.

bashaz containerapp create
az containerapp create \
  -n "$APP" -g "$RG" --environment "rp-env" \
  --image ghcr.io/routeplane-core/routeplane:latest \
  --target-port 8080 --ingress external \
  --min-replicas 0 --max-replicas 5 \
  --secrets "openai-key=$OPENAI_API_KEY" \
  --env-vars "OPENAI_API_KEY=secretref:openai-key" "PORT=8080"
Secrets stay secrets. Passing the key via --secrets and referencing it with secretref: keeps it out of the container's plain env definition. Never put a provider key directly in --env-vars.

Want the image in your own registry instead of pulling from the public one (for a private pull path or compliance)? Import it once with az acr import --source ghcr.io/routeplane-core/routeplane:latest and point --image + --registry-server at your ACR.

3. Verify the live endpoint

bashsmoke test the FQDN
FQDN=$(az containerapp show -n "$APP" -g "$RG" \
  --query properties.configuration.ingress.fqdn -o tsv)

curl -s "https://$FQDN/healthz"               # 200 OK (may cold-start on first hit)

curl -s "https://$FQDN/v1/chat/completions" \
  -H 'content-type: application/json' \
  -H 'x-routeplane-api-key: rp_dev_REPLACE_ME' \
  -H 'x-routeplane-provider: openai' \
  -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hello from ACA"}]}'

The first request after an idle period pays a cold-start; subsequent requests are warm until it scales back to zero.

4. Make it sovereign

To enforce data residency, give the gateway an in-region provider and call with a residency header. Deploy this app to an Indian region (centralindia) and configure an Azure OpenAI deployment in-region via the AZURE_OPENAI_* settings (see Configuration). Then:

bashresidency-locked call
curl -s "https://$FQDN/v1/chat/completions" \
  -H 'content-type: application/json' \
  -H 'x-routeplane-api-key: rp_...' \
  -H 'x-routeplane-residency: IN' \
  -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"My PAN is ABCDE1234F"}]}'

Now a request carrying regulated data is hard-locked to the in-region provider, and fails closed with 422 if none is eligible.

From clicks to code. These az commands show the shape; for anything beyond a demo, manage it as infrastructure-as-code. A Terraform topology (reusable modules + per-environment wiring, with OIDC-federated CI, no static cloud credentials) provisions exactly this. See Deploy.