Back to blog
AzureBest PracticesCloud SecurityCompute & ContainersNetworking

Azure Container App Is Externally Accessible: Risks and Remediation

Learn why external ingress on Azure Container Apps is risky, how to switch to internal ingress, and how to prevent public exposure with Azure Policy and IaC.

TL;DR

This check flags Azure Container Apps that accept traffic from the public internet via external ingress. If the app does not need to be reachable from outside your network, switch ingress to internal so it only accepts traffic from within its Container Apps environment and your VNet.

Azure Container Apps make it easy to run containerized workloads without managing the underlying Kubernetes plumbing. That convenience cuts both ways. With a single property set to external, your app gets a public FQDN and starts answering requests from anyone on the internet. Sometimes that is exactly what you want. Often it is not, and the exposure goes unnoticed until a scanner finds it.

The Container App Is Externally Accessible check looks at the ingress configuration of each Container App and raises a finding when ingress.external is set to true.


What this check detects

Every Container App that exposes a port has an ingress configuration. The key field is external:

  • External ingress (external: true) assigns a public FQDN and routes traffic from the internet to your app.
  • Internal ingress (external: false) assigns an FQDN that resolves only inside the Container Apps environment and the VNet it is integrated with.

This check fires when a Container App has ingress enabled and external is set to true. It does not assert that public access is wrong, only that it exists and should be reviewed against your intent.

Note: Internal ingress requires the Container Apps environment to be deployed with VNet integration using a custom virtual network. An environment created without a custom VNet can only host externally accessible apps, so you may need to recreate the environment to get internal-only ingress.


Why it matters

A public endpoint is an open invitation for traffic you did not ask for. The risk depends entirely on what sits behind the ingress, and the problem is that internal services often end up exposed by accident.

Internal APIs leaking to the internet

A common pattern is a frontend app that should be public and a backend API that should only be called by that frontend. If both are deployed with external: true, the backend is now directly reachable. Anyone who discovers the FQDN can call its endpoints, skipping whatever authentication or rate limiting the frontend was supposed to enforce.

Attack surface for unauthenticated workloads

Container Apps do not authenticate requests unless you configure it. A service that assumes it lives in a trusted network, for example one that exposes admin routes or health endpoints with debug data, becomes a target the moment it is published. Automated scanners catalog new Azure FQDNs within hours.

Data exposure and lateral movement

If a publicly accessible app holds connection strings, managed identity tokens, or can reach databases and storage accounts, a compromise gives an attacker a foothold inside your environment. From there they probe for the next hop. Reducing exposure on the front door limits how far an intrusion can travel.

The cheapest vulnerability to fix is the one that was never exposed. If a service has no reason to be public, internal ingress removes an entire class of attacks before they start.


How to fix it

The fix is to set ingress to internal for any app that does not need public reachability. Confirm the app is genuinely internal first, since flipping this on a public-facing service will break it.

Check the current ingress setting

az containerapp ingress show \
  --name my-container-app \
  --resource-group my-rg \
  --query "{external: external, fqdn: fqdn, targetPort: targetPort}" \
  -o json

If external comes back true and the app is meant to be internal, switch it.

Warning: Changing ingress from external to internal changes the app's FQDN and stops it from resolving on the public internet. Anything calling the old public address, including DNS records, API gateways, or partner integrations, will break. Update those callers before or during the change.

Set ingress to internal with the CLI

az containerapp ingress enable \
  --name my-container-app \
  --resource-group my-rg \
  --type internal \
  --target-port 8080 \
  --transport auto

If the app should not accept any ingress at all, for example a background worker, disable ingress entirely:

az containerapp ingress disable \
  --name my-container-app \
  --resource-group my-rg

Fix it in the Azure Portal

  1. Open the Container App in the Azure Portal.
  2. Under Settings, select Ingress.
  3. With ingress enabled, change Ingress traffic from Accepting traffic from anywhere to Limited to Container Apps Environment.
  4. Confirm the target port and transport, then save.

Fix it in Bicep

resource containerApp 'Microsoft.App/containerApps@2024-03-01' = {
  name: 'my-container-app'
  location: location
  properties: {
    managedEnvironmentId: environmentId
    configuration: {
      ingress: {
        external: false        // internal only
        targetPort: 8080
        transport: 'auto'
        allowInsecure: false
      }
    }
    template: {
      containers: [
        {
          name: 'app'
          image: 'myregistry.azurecr.io/app:latest'
        }
      ]
    }
  }
}

Fix it in Terraform

resource "azurerm_container_app" "app" {
  name                         = "my-container-app"
  container_app_environment_id = azurerm_container_app_environment.env.id
  resource_group_name          = azurerm_resource_group.rg.name
  revision_mode                = "Single"

  ingress {
    external_enabled = false   # internal only
    target_port      = 8080
    transport        = "auto"

    traffic_weight {
      latest_revision = true
      percentage      = 100
    }
  }

  template {
    container {
      name   = "app"
      image  = "myregistry.azurecr.io/app:latest"
      cpu    = 0.25
      memory = "0.5Gi"
    }
  }
}

Danger: Disabling ingress on a live app makes it unreachable immediately, including by any internal services that call it. Verify that no production traffic depends on the endpoint before running ingress disable, and prefer rolling the change through a non-production environment first.


What to do when an app must stay public

Some apps legitimately need external ingress. A public web frontend is the obvious case. When that is true, do not just accept the finding, harden the exposure:

  • Put a WAF in front of it. Use Azure Front Door or Application Gateway with the Web Application Firewall, and restrict the Container App so it only accepts traffic from the WAF.
  • Enforce HTTPS. Set allowInsecure: false so HTTP requests are redirected to HTTPS.
  • Add authentication. Use built-in authentication (Easy Auth) with Microsoft Entra ID or another identity provider for apps that should not be anonymous.
  • Scope IP restrictions. Container Apps support IP ingress rules. If the audience is known, allowlist their ranges.
az containerapp ingress access-restriction set \
  --name my-container-app \
  --resource-group my-rg \
  --rule-name "office-only" \
  --ip-address 203.0.113.0/24 \
  --action Allow

How to prevent it from happening again

Manual review does not scale. Catch external ingress before it reaches production by gating it in your pipeline and in Azure itself.

Azure Policy

Use a policy with the deny effect to block Container App deployments that set external ingress. The policy below targets the ingress property directly:

{
  "if": {
    "allOf": [
      {
        "field": "type",
        "equals": "Microsoft.App/containerApps"
      },
      {
        "field": "Microsoft.App/containerApps/configuration.ingress.external",
        "equals": true
      }
    ]
  },
  "then": {
    "effect": "deny"
  }
}

Assign this at the subscription or management group scope. If a handful of apps need to be public, use an exemption for those specific resources rather than weakening the policy for everyone.

Tip: Start the policy in audit mode to find existing external apps without breaking deployments, then flip it to deny once you have triaged the legitimate cases and added exemptions.

Policy-as-code in CI

For Terraform and Bicep, scan plans before they apply. A Checkov or Conftest rule that fails when external_enabled = true stops the misconfiguration at the pull request stage, where it is cheapest to fix.

# Run Checkov against a Terraform plan in CI
checkov -f tfplan.json --framework terraform_plan

Continuous scanning

Pipeline gates only cover what flows through the pipeline. Console changes, emergency fixes, and drift slip past them. Lensix continuously scans your live Azure subscriptions for externally accessible Container Apps and surfaces them with the affected resource and remediation guidance, so an app that gets flipped to public after deployment does not stay hidden.


Best practices

  • Default to internal. Treat public ingress as an explicit decision, not a default. Most backend services and workers never need it.
  • Build VNet-integrated environments. Create Container Apps environments with a custom VNet from the start so internal ingress is available without recreating the environment later.
  • Front public apps with a WAF. Never expose a Container App directly when you can place Front Door or Application Gateway in front of it and lock the app down to that source.
  • Authenticate by default. Anonymous public endpoints should be rare and deliberate. Use Easy Auth or app-level authentication for everything else.
  • Review ingress in code review. Make external and external_enabled values something reviewers actively check, not boilerplate they skim past.
  • Tag intentional exceptions. When an app is meant to be public, document it with a tag or policy exemption so audits do not keep re-flagging it and real findings do not get lost in the noise.

External ingress is a one-property switch, which is exactly why it is easy to get wrong. Decide deliberately which apps face the internet, enforce that decision with policy, and scan continuously so the ones that should be private stay private.