Skip to content

Post-Install Reachability

CertifyClouds deploys with internal-only ingress by default. That's deliberate, a secrets-management tool exposed publicly is a footgun. But it means once the deploy finishes, the UI is unreachable from outside the VNet. This page covers the five sensible ways to fix that, cheapest first.

If you also haven't sorted the outbound egress for license/heartbeats and image pulls, see Network Requirements first. The two pages are paired: this one is about getting traffic in, that one is about letting CC reach the services it depends on out.


Decision tree

Start here. The right pattern depends on what you already have.

Do you have VPN or ExpressRoute reaching the CAE VNet (directly
or via peering)?
├─ Yes → Pattern A: Private DNS + VPN/ER     (no new infra, ~£0/mo)
└─ No
   ├─ Do you already run an internal App Gateway for other apps?
   │  └─ Yes → Pattern B: Reuse existing App Gateway    (~£0/mo)
   ├─ Is your Azure Front Door SKU Premium?
   │  └─ Yes → Pattern C: Front Door Premium + Private Link
   │           (requires CAE migration to Workload Profiles,
   │           one-way, ~£100+/mo baseline)
   ├─ Are you fine with traffic transiting Cloudflare?
   │  └─ Yes → Pattern D: Cloudflare Tunnel             (~£0–5/mo)
   └─ Otherwise → Pattern E: New Application Gateway v2
                  (~£140/mo + needs a dedicated /24 subnet)

Most banks land on A. The rest is for organisations without existing VPN/ER reach to this VNet.


Pattern A: VPN/ExpressRoute + Private DNS

When to use: Your corporate VPN or ExpressRoute already lands in (or peers with) the CAE VNet. Your administrators reach internal Azure services that way today.

Marginal cost: £0, uses sunk infrastructure.

Effort: ~15 minutes.

How it works

The deploy script created a private DNS A-record in <region>.azurecontainerapps.io pointing at the CAE's internal load-balancer IP. If that zone is linked to your VNet (or to your hub VNet, with peering reaching the CAE VNet), name resolution already works for anything inside that network, your admins on VPN can hit https://<app>.<env>.<region>.azurecontainerapps.io directly.

For a friendlier hostname:

# Bind a custom domain to the Container App via the deploy script's
# BYOC flow. You'll need a PFX (Cloudflare Origin Cert, Let's Encrypt
# DNS-01, or your corporate CA work fine here).

./deploy-certifyclouds-cae.sh \
    --custom-domain certifyclouds.internal.example.com \
    --cert-pfx ./certs/internal.pfx \
    --cert-password "<pfx password>" \
    [...your other flags...]

# Then add a private DNS A-record for the custom hostname pointing
# at the CAE's static IP. Replace the zone, RG, and IP to match.
az network private-dns record-set a add-record \
    --resource-group <private-dns-rg> \
    --zone-name internal.example.com \
    --record-set-name certifyclouds \
    --ipv4-address <env-static-ip>

If you don't have a private DNS zone for the custom hostname yet, create one and link it to the VNet your VPN/ER terminates in:

az network private-dns zone create \
    --resource-group <networking-rg> \
    --name internal.example.com

az network private-dns link vnet create \
    --resource-group <networking-rg> \
    --zone-name internal.example.com \
    --name link-hub \
    --virtual-network /subscriptions/.../virtualNetworks/<hub-vnet> \
    --registration-enabled false

NSG implications

The CAE subnet needs to allow inbound 443/tcp from your corporate IP ranges (the ones that come out of the VPN gateway). If you currently allow * from your gateway subnet, nothing to do. If you allow specific CIDRs, add yours.

When this doesn't fit

  • You don't have VPN/ExpressRoute reaching this VNet, and getting it set up is out of scope for this project.
  • Your administrators reach internal apps through a corporate web proxy that doesn't proxy Azure traffic.
  • You need external (non-corporate-network) access, e.g. for partners or auditors.

Pattern B: Reuse existing Application Gateway

When to use: Your organisation already runs an internal Application Gateway for other Azure-hosted apps. The gateway lives in another VNet (or another subnet in this one) and is peered with the CAE VNet (or you can peer them).

Marginal cost: £0, uses existing App Gateway.

Effort: ~1 hour (mostly waiting for peering propagation and listener provisioning).

How it works

App Gateway terminates TLS at its frontend, then forwards to the CAE's static IP with the right Host header. The CAE's internal-only FQDN doesn't resolve outside the CAE's private DNS zone, but App Gateway doesn't need to resolve it, pointing at the static IP and overriding the host header on the way out works around that gap entirely.

Setup

  1. Verify (or create) VNet peering between the App Gateway's VNet and the CAE VNet. Both directions, allow forwarded traffic.
  2. Confirm the NSG on the CAE subnet allows inbound 443/tcp from the App Gateway's subnet CIDR.
  3. Create a backend pool on the App Gateway with one target: the CAE environment's static IP (printed in deploy-output.json from the deploy script).
  4. Create an HTTP setting on the App Gateway:
    • Protocol: HTTPS
    • Port: 443
    • Override with new hostname: yes, set to the CAE's internal FQDN (e.g. <app>.<env>.<region>.azurecontainerapps.io)
    • Override hostname: yes
    • Backend host name from backend address: no
    • Use well-known CA certificate: no, upload the cert that's on the CAE (or skip backend cert validation if your security policy permits it on internal traffic).
  5. Create a listener on the desired public/private hostname + bind your TLS cert.
  6. Create a routing rule linking listener → backend pool → HTTP setting.
  7. Add the public hostname to DNS (or a private hostname if the App Gateway is internal-only).

When this doesn't fit

  • You don't run App Gateway today.
  • The existing App Gateway is in a different subscription/tenant and can't be peered.

Pattern C: Azure Front Door Premium + Private Endpoint

When to use: You already pay for Azure Front Door Premium for other apps. You want global anycast + WAF + DDoS protection. Traffic can transit Microsoft's backbone but must enter your VNet privately.

Marginal cost: Front Door Premium is paid for already; this pattern adds Private Endpoint cost (~£10/mo) and a CAE workload-profile baseline if you migrate from Consumption (~£100/mo for a D4 profile, idle).

Effort: 2–3 hours including a one-way CAE plan migration.

The Consumption-vs-Workload-Profiles gotcha

The deploy script creates the CAE in Consumption plan. Container Apps Environments on the Consumption plan do not support inbound Private Endpoints. AFD Premium reaches private backends only via Private Endpoint. So before this pattern works, the CAE needs to be on Workload Profiles plan.

Migration is one-way (Consumption → Workload Profiles is supported, the reverse is not). Adds a baseline cost even when idle.

# Migrate the CAE to Workload Profiles plan
az containerapp env workload-profile add \
    --resource-group <rg> \
    --name <cae-env-name> \
    --workload-profile-name "wp-cc" \
    --workload-profile-type "D4" \
    --min-nodes 1 \
    --max-nodes 3

# Move the container app onto the new profile
az containerapp update \
    --resource-group <rg> \
    --name <ca-name> \
    --workload-profile-name "wp-cc"

Setup

  1. Carve a Private Endpoint subnet (/28 minimum) in the CAE VNet. Set privateEndpointNetworkPolicies to Disabled on it.

    az network vnet subnet create \
        --resource-group <rg> \
        --vnet-name <vnet> \
        --name snet-cae-pe \
        --address-prefixes <cidr> \
        --private-endpoint-network-policies Disabled
    
  2. Create a Private Endpoint targeting the CAE managed environment:

    az network private-endpoint create \
        --resource-group <rg> \
        --name pe-cae-frontdoor \
        --vnet-name <vnet> \
        --subnet snet-cae-pe \
        --private-connection-resource-id /subscriptions/.../managedEnvironments/<cae> \
        --group-id managedEnvironments \
        --connection-name pe-conn-frontdoor
    
  3. In Azure Front Door Premium: add a new origin group → origin → set:

    • Origin type: Private Link
    • Region: same region as the CAE
    • Resource type: Container Apps Environment
    • Resource: select the CAE
    • Origin host header: the CAE's internal FQDN
  4. Approve the Private Endpoint connection on the CAE side:

    az network private-endpoint-connection list \
        --resource-group <rg> \
        --name <cae> \
        --type Microsoft.App/managedEnvironments
    
    az network private-endpoint-connection approve \
        --id <pe-connection-id>
    
  5. Configure your AFD endpoint hostname, route, WAF policy, caching as required for your usage.

When this doesn't fit

  • Your AFD is Standard SKU, Standard cannot do Private Link. Either upgrade to Premium or pick a different pattern.
  • The Workload Profiles cost is prohibitive for this workload.
  • You don't want any traffic to transit Microsoft's global edge.

Pattern D: Cloudflare Tunnel

When to use: You already use Cloudflare. You want a public hostname (with Cloudflare Access SSO in front) and no inbound firewall changes at all, cloudflared makes only outbound connections to Cloudflare.

Marginal cost: Free tier of Cloudflare works for small teams; expect ~£5/mo if you add CF Access policies for SSO.

Effort: 30 minutes.

Caveats

  • Traffic transits Cloudflare's network. Some bank compliance teams reject this on principle.
  • The tunnel relies on cloudflared staying running. Run it as a sidecar container in the CAE so it co-locates with CC and shares its restart lifecycle.

Setup

# 1. Create a tunnel in Cloudflare (Zero Trust → Networks → Tunnels → Create).
#    Note the tunnel token.

# 2. Add a public hostname to the tunnel:
#    - Hostname: cc.example.com (or whatever you want)
#    - Service: HTTPS
#    - URL: <env-static-ip>
#    - TLS → Origin Server Name: <cae-internal-fqdn>
#    - TLS → No TLS verify: enabled (cert bound to FQDN, not IP)
#    - HTTP Settings → HTTP Host Header: <cae-internal-fqdn>

# 3. Deploy cloudflared as a sidecar container in the CAE env:
az containerapp create \
    --resource-group <rg> \
    --environment <cae-env> \
    --name cloudflared \
    --image cloudflare/cloudflared:latest \
    --command "cloudflared tunnel --no-autoupdate run --token <tunnel-token>" \
    --min-replicas 1 \
    --max-replicas 1

That's it. The tunnel comes up; the hostname resolves publicly via Cloudflare; traffic reaches the CAE static IP through the tunnel.

For SSO: enable Cloudflare Access on the hostname, attach an OIDC/SAML identity provider, scope to your domain.

When this doesn't fit

  • You can't have traffic transit Cloudflare for compliance reasons.
  • You don't want to manage a tunnel + tunnel credentials.

Pattern E: New Application Gateway v2

When to use: You don't have VPN/ER reach, don't have an existing App Gateway, can't use Front Door Premium (Standard tier or none), and can't use Cloudflare. This is the "stand it up yourself" path.

Marginal cost: ~£140/mo for App Gateway v2 Standard + WAF (slightly less without WAF, slightly more with WAF_v2).

Effort: 2–3 hours.

The subnet requirement

App Gateway v2 must live in its own dedicated /24 subnet. No other resources allowed in that subnet (except other App Gateways in the same VNet). This is a hard Microsoft requirement.

If your VNet was carved tightly and you don't have a spare /24, options:

  1. Add a second address space to the existing VNet:

    az network vnet update \
        --resource-group <rg> \
        --name <vnet> \
        --address-prefixes 10.0.0.0/16 10.1.0.0/16
    
  2. Carve the new /24 from the new address space:

    az network vnet subnet create \
        --resource-group <rg> \
        --vnet-name <vnet> \
        --name snet-appgw \
        --address-prefixes 10.1.0.0/24
    
  3. Or peer with another VNet that has room.

Setup

Standard App Gateway v2 setup. Backend points at the CAE static IP, host header set to the CAE internal FQDN (same pattern as Pattern B). Microsoft's docs cover the rest: Configure App Gateway with WAF.

When this doesn't fit

  • The cost is unjustifiable for the workload.
  • No spare /24 and you can't expand the VNet.

Comparison table

Pattern £/mo Effort Subnets needed Public hostname? WAF?
A. VPN/ER + Private DNS £0 15 min 0 new No (internal) n/a
B. Existing App Gateway £0 1 h 0 new Yes If GW has it
C. AFD Premium + PE ~£100+ 2–3 h 1 new (/28) + plan migration Yes Yes
D. Cloudflare Tunnel £0–5 30 min 0 new Yes CF WAF
E. New App Gateway v2 ~£140+ 2–3 h 1 new (/24) Yes Yes (opt)

Common pitfalls

  1. Internal FQDN doesn't resolve from outside the CAE's private DNS zone. Most reverse proxies live outside that zone. Use the static IP + Host header instead.
  2. The cert on the CAE is bound to the FQDN, not the IP. When pointing reverse proxies at the static IP, disable backend cert verification (or bind a different cert on the proxy).
  3. Front Door Standard cannot do Private Link. It can only reach public origins. Our CAE is internal-only, Standard can't reach it without making the CAE public (don't).
  4. App Gateway needs a dedicated /24 subnet. Not negotiable.
  5. Workload Profiles plan migration is one-way. Don't migrate speculatively.
  6. Cloudflare Tunnel hostnames need DNS pointing at Cloudflare nameservers. If your apex is delegated elsewhere, you'll need to either delegate a subdomain to CF or use CNAME setup with the proxy disabled.

  • Network Requirements, outbound egress allowlist + NSG snippets. Pair this with whichever inbound pattern you pick.
  • Installation, the upstream deploy flow that produces the internal-only CAE this page is about exposing.
  • Azure Permissions, RBAC + Graph permissions for the deployed app.