Skip to content

Web App for Containers

Web App for Containers is the App Service hosting model that runs a custom Docker container instead of a built-in runtime stack. It gives you full control over the OS, runtime, dependencies, and configuration while App Service manages the underlying infrastructure, scaling, and platform integration.

Use Web App for Containers when:

  • Your app requires a specific runtime version, native library, or OS package not available in the built-in stacks.
  • You want to standardize deployments around a container image across environments.
  • You are migrating a containerized workload from another platform to App Service.

Main Content

Linux vs Windows Container Hosting

App Service supports custom containers on both Linux and Windows. The two hosting paths differ in capabilities and recommended patterns.

Dimension Linux containers Windows containers
Image format OCI / Docker Linux Windows Server Core / Nano Server
Sidecar support Up to 9 sidecar containers per app Not supported
Registry auth Managed identity (recommended), credentials Managed identity (recommended); service principal no longer supported
Persistent storage /home directory, Azure Files mount C:\home directory
SSH access Built-in SSH support available Not available

Service principal auth for Windows containers is retired

Using a service principal for Windows container image pull authentication is no longer supported. Use managed identity for both Windows and Linux containers.

Container Lifecycle on App Service

Deploying a containerized app to App Service follows a consistent flow regardless of language or registry.

Portal view: Deployment Center — Containers tab

Deployment Center blade for a Web App with the "Containers (new)" tab selected. The body shows an empty containers table with column headers Name, Type, Source, Image, Tag, Port, and Logs, and an introductory line "Enhance site functionality by adding sidecar containers. Learn more". A command bar above the table contains Refresh, Add (split dropdown), Delete (disabled), and Send us your feedback buttons. Tabs across the top of the blade include Settings, Containers (new) (selected), Logs, and FTPS Credentials. The left navigation shows Deployment Center selected under the Deployment group.

The Containers (new) tab exposes the runtime contract that App Service must satisfy to start a containerized workload. Columns such as Source, Image, Tag, and Port map directly to the image-pull and startup phases: the platform must fetch the selected image, launch it with the configured port, and then watch logs and HTTP reachability to decide whether startup succeeded. The Add action and sidecar-focused table also show that container lifecycle on App Service is not just about one image, but about coordinating the main app container and any companion containers inside the same app boundary.

flowchart TD
    A[Write Dockerfile] --> B[Build image locally]
    B --> C[Push image to registry]
    C --> D[Create or update Web App]
    D --> E[Configure container image and registry]
    E --> F[App Service pulls image on startup]
    F --> G[Platform evaluates startup HTTP reachability]
    G --> H{Health check passes?}
    H -->|Yes| I[App is Running]
    H -->|No| J[Startup failure — check PORT/WEBSITES_PORT, binding, and logs]

Each stage has a corresponding verification step:

Stage Verification command
Image built docker run --rm --publish 8080:8080 <IMAGE> — confirm app responds locally
Image pushed az acr repository list --name $REGISTRY_NAME
App configured az webapp config container show --name $APP_NAME --resource-group $RG
App running az webapp show --name $APP_NAME --resource-group $RG --query "state"

Verify container configuration and running state:

az webapp config container show \
  --name $APP_NAME \
  --resource-group $RG \
  --output table
Command/Code Purpose
az webapp config container show Shows the current container image and registry settings applied to the app
Name                                 SlotSetting    Value
-----------------------------------  -------------  ---------------------------------------------------
WEBSITES_ENABLE_APP_SERVICE_STORAGE  False          false
DOCKER_REGISTRY_SERVER_URL           False          https://<registry-name>.azurecr.io
DOCKER_CUSTOM_IMAGE_NAME                            DOCKER|<registry-name>.azurecr.io/flask-test:v1
az webapp show \
  --name $APP_NAME \
  --resource-group $RG \
  --query "{name:name, state:state, defaultHostName:defaultHostName, linuxFxVersion:siteConfig.linuxFxVersion}" \
  --output yaml
Command/Code Purpose
--query "{...linuxFxVersion:siteConfig.linuxFxVersion}" Confirms the container image configured on the app
defaultHostName: <app-name>.azurewebsites.net
linuxFxVersion: DOCKER|<registry-name>.azurecr.io/flask-test:v1
name: <app-name>
state: Running

Registry Authentication Patterns

App Service supports three registry authentication patterns. Choose based on your registry type and security requirements.

flowchart TD
    Q1{Registry type?} -->|Azure Container Registry| Q2{Production workload?}
    Q1 -->|Docker Hub or other public| A2[No credentials needed\nSet image name only]
    Q1 -->|Private non-ACR registry| A3[Store credentials\nDOCKER_REGISTRY_SERVER_URL\nDOCKER_REGISTRY_SERVER_USERNAME\nDOCKER_REGISTRY_SERVER_PASSWORD]

    Q2 -->|Yes| A1[Managed identity\nacrUseManagedIdentityCreds: true]
    Q2 -->|No / Dev| A4[Admin credentials\ndocker-registry-server-user/password]

Managed identity eliminates stored credentials and is the recommended pattern for ACR.

az webapp identity assign \
  --name $APP_NAME \
  --resource-group $RG
Command/Code Purpose
az webapp identity assign Enables system-assigned managed identity on the web app
principalId: <object-id>
tenantId: <tenant-id>
type: SystemAssigned
userAssignedIdentities: null
az role assignment create \
  --assignee $PRINCIPAL_ID \
  --scope $REGISTRY_RESOURCE_ID \
  --role "AcrPull"
Command/Code Purpose
--role "AcrPull" Grants the minimum permission needed to pull images from ACR
--scope $REGISTRY_RESOURCE_ID Limits the role to a specific registry
roleDefinitionName: AcrPull
principalType: ServicePrincipal
scope: /subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.ContainerRegistry/registries/<registry-name>
az webapp config set \
  --name $APP_NAME \
  --resource-group $RG \
  --generic-configurations '{"acrUseManagedIdentityCreds": true}'
Command/Code Purpose
acrUseManagedIdentityCreds: true Instructs App Service to use the managed identity for ACR image pulls instead of stored credentials

Verify managed identity is active:

az webapp config show \
  --name $APP_NAME \
  --resource-group $RG \
  --query "acrUseManagedIdentityCreds" \
  --output tsv
Command/Code Purpose
--query "acrUseManagedIdentityCreds" Confirms managed identity image pull is enabled
true

Private Non-ACR Registry (Credentials)

For Docker Hub private repositories or any non-ACR private registry, supply credentials:

az webapp config container set \
  --name $APP_NAME \
  --resource-group $RG \
  --container-image-name $IMAGE_NAME \
  --docker-registry-server-url $REGISTRY_URL \
  --docker-registry-server-user $REGISTRY_USER \
  --docker-registry-server-password $REGISTRY_PASSWORD
Command/Code Purpose
--docker-registry-server-url Full URL of the private registry (for example, https://index.docker.io)
--docker-registry-server-user Registry username stored as an encrypted app setting
--docker-registry-server-password Registry password stored as an encrypted app setting

Credentials are not exposed to application code

App Service stores registry credentials in the reserved environment variables DOCKER_REGISTRY_SERVER_URL, DOCKER_REGISTRY_SERVER_USERNAME, and DOCKER_REGISTRY_SERVER_PASSWORD. These variables are not forwarded to the application container for security reasons.

Network-Protected Registry

To pull from a registry inside a virtual network or behind a private endpoint, enable VNet image pull:

az webapp config set \
  --name $APP_NAME \
  --resource-group $RG \
  --generic-configurations '{"vnetImagePullEnabled": true}'
Command/Code Purpose
vnetImagePullEnabled: true Routes image pull traffic through the app's VNet integration instead of the public internet

VNet integration is a prerequisite

The app must have VNet integration configured and DNS resolution in place before enabling vnetImagePullEnabled. Without VNet integration, the setting has no effect.

Multi-Container and Sidecar Pattern

App Service supports adding up to nine sidecar containers alongside the main application container. Sidecars run in the same App Service plan and share the network namespace with the main container.

flowchart TD
    subgraph Plan ["App Service Plan"]
        subgraph App ["Web App"]
            M[Main container\nYour application]
            S1[Sidecar 1\nOpenTelemetry collector]
            S2[Sidecar 2\nLog shipper]
        end
    end
    M -- shared network --> S1
    M -- shared network --> S2
    S1 --> OBS[Observability backend]
    S2 --> LOG[Log aggregator]

Use sidecars for:

  • Observability agents (OpenTelemetry, Datadog, Dynatrace)
  • Log shippers (Fluent Bit, Logstash)
  • Configuration or secrets sync sidecars

Sidecar support is Linux-only

Sidecars are available for Linux App Service apps, including both code-only (built-in runtime) apps and custom container apps. Windows containers do not support sidecars.

Configure a Sidecar

az webapp sitecontainers create \
  --name $APP_NAME \
  --resource-group $RG \
  --container-name $SIDECAR_NAME \
  --image $SIDECAR_IMAGE \
  --is-main false \
  --target-port 4317
Command/Code Purpose
az webapp sitecontainers create Attaches a sidecar container to the main application container
--is-main false Marks this as a sidecar — the main container must already exist
--target-port Port the sidecar listens on inside the app (for example, 4317 for OpenTelemetry gRPC)
{
  "authType": "Anonymous",
  "image": "otel/opentelemetry-collector:latest",
  "isMain": false,
  "name": "otel-collector",
  "targetPort": "4317",
  "type": "Microsoft.Web/sites/sitecontainer"
}

CLI command name

The Azure CLI command for managing sidecar containers is az webapp sitecontainers, not az webapp sidecar. The Microsoft Learn documentation refers to the feature as "sidecar containers."

Port Configuration and Health Checks

App Service sends startup HTTP checks to verify that the app became reachable. For Linux custom containers, port handling is more nuanced than a simple WEBSITES_PORT probe rule: the app should usually bind to the runtime-injected PORT value, and WEBSITES_PORT is one of the settings that can influence platform behavior. See Container HTTP Pings — Lab Guide for experimental evidence on Linux port behavior.

If your custom container uses a non-default HTTP port, configure WEBSITES_PORT and make sure the process actually listens on the value exposed through PORT:

az webapp config appsettings set \
  --name $APP_NAME \
  --resource-group $RG \
  --settings WEBSITES_PORT=8080
Command/Code Purpose
WEBSITES_PORT App setting commonly used to declare the custom container HTTP port; on Linux, the app should usually bind to the runtime-injected PORT value
- name: WEBSITES_PORT
  slotSetting: false
  value: '8080'

Check both PORT binding and WEBSITES_PORT

On Linux custom containers, startup failures are not explained by WEBSITES_PORT mismatch alone. Verify that the app binds to 0.0.0.0 on the runtime-injected PORT, then compare that behavior with any configured WEBSITES_PORT value.

Security Considerations

Topic Recommendation
ACR image pulls Use managed identity — eliminates stored credentials and supports rotation-free operation
Non-ACR registry credentials Store as app settings; never hardcode in Dockerfile or application code
Network-protected registry Use VNet integration + vnetImagePullEnabled instead of exposing registry to the public internet
Windows containers Service principal auth is retired; migrate to managed identity
Persistent storage Do not write persistent data to the container filesystem; use the /home directory or Azure Files

See Also

Sources