DEMO/Infrastructure

← Back to dashboard

Outbound addresses on Azure Container Apps

Audience: operator · Profile: both · Read this when: managed nodes' firewalls need the dashboard's outbound addresses, and you are choosing how to give them.

Part of Cloud Hosting: Azure Container Apps.

A Container Apps environment with no NAT gateway has no single outbound address. It SNATs traffic out of a shared pool of several hundred addresses, and it picks an address per destination. That breaks anything that admits the dashboard by source address, the managed Portainer and Rancher node firewalls above all. The dashboard learns its address from an echo service, but the echo service and the node see different addresses, so the node drops the bootstrap. The job reads "serving", then ConnectTimeout, then "re-detecting … produced no change".

There are three ways to fix it. Pick one:

Cost Setup What the node admits
Let the dashboard read its pool free one Reader grant the environment's published pool (~400 shared Azure addresses)
NAT gateway ~$37/month + $0.045/GB three az commands one static address you own
Paste the pool by hand free a copy-paste, repeated when it goes stale the same pool as option 1, frozen

The pool in options 1 and 3 is shared Azure address space: other tenants' container apps in the same region can egress from it too, so they could reach the node's management ports. The node's own login still stands between them and the node. A NAT gateway is the only option that admits nothing but the dashboard.

Option 1: let the dashboard read its own pool

The platform publishes the pool as properties.outboundIpAddresses on the Container App, so the dashboard reads it with the app's own managed identity. Whenever a node firewall is applied (deploy, Re-apply, token mint), the whole pool is admitted on tcp 9443/8000 (Portainer) or 80/443 (Rancher) and re-read. There's nothing to paste and nothing to go stale. It needs one read grant, and one grant on the resource group covers both apps:

for app in dash dash-worker; do
  az containerapp identity assign -n $app -g RG-DASH --system-assigned
  az role assignment create --role Reader \
    --scope "$(az group show -n RG-DASH --query id -o tsv)" \
    --assignee-principal-type ServicePrincipal \
    --assignee-object-id "$(az containerapp show -n $app -g RG-DASH --query identity.principalId -o tsv)"
done

Settings → Containers (and → Kubernetes) shows Hosting platform outbound pool: N addresses once it works. When it doesn't, the readout shows why. That's nearly always the missing grant, and the message includes the exact command with the identity's object id already filled in.

Option 2: a NAT gateway

A NAT gateway on the environment's subnet gives every outbound connection one static address. The echo detection then sees the same address the node does, so the dashboard's normal /32 detection just works, on every cloud including AWS. This needs a workload profiles environment (the default since 2024; a Consumption-only environment can't take one). Costs are about $32.85/month for the gateway, $3.65/month for the Standard public IP, and $0.045 per GB processed.

az network public-ip create -g RG-DASH -n pip-dash-egress --sku Standard --allocation-method Static
az network nat gateway create -g RG-DASH -n nat-dash --public-ip-addresses pip-dash-egress
az network vnet subnet update -g RG-DASH --vnet-name vnet-dash -n snet-aca --nat-gateway nat-dash

Then turn option 1 off so the pool stops being admitted, and re-apply the node firewall (Settings → Containers → Re-apply) or redeploy:

for app in dash dash-worker; do
  az containerapp update -n $app -g RG-DASH --set-env-vars DASHBOARD_EGRESS_POOL=off
done

The address is az network public-ip show -g RG-DASH -n pip-dash-egress --query ipAddress -o tsv, which is also the one to give any other allow-list that needs to admit the dashboard. To remove the gateway, detach it first (az network vnet subnet update ... --remove natGateway): a NAT gateway can't be deleted while a subnet still references it.

Option 3: paste the pool by hand

When you can't grant roles (Contributor alone can't write role assignments) and don't want the cost of a NAT gateway, copy the pool into portainer_allowed_source_cidrs (or rancher_allowed_source_cidrs) in Settings, then Re-apply:

az containerapp show -n dash-worker -g RG-DASH --query "join(',', properties.outboundIpAddresses)" -o tsv

That copy can go stale when Azure grows the pool. The symptom is the same ConnectTimeout coming back, and the fix is to paste it again.