Skip to content

Azure Container Instances

Key Points

  • "Run a container" with no orchestration. Spin up a container in Azure with public/private IP. No K8s, no nodes to manage, no scheduler.
  • Per-second pricing of vCPU + memory. Cheap for short bursts; expensive for sustained load — at sustained load use Container Apps or AKS.
  • Container groups = multiple containers sharing lifecycle, network namespace, and volumes (analogous to a Kubernetes pod).
  • Restart policies: Always, OnFailure, Never. Pick Never for batch/cron jobs.
  • No autoscale, no rolling deploy. Updates = destroy + recreate. Limits: 4 vCPU, 16 GB RAM per container group (higher SKUs available).
  • VNet injection for private IPs (Standard SKU); GPU SKUs (V100s) for ML inference.
  • Use cases: ephemeral build/test runners, batch jobs from Logic Apps / Functions, single-tenant dev sandboxes, simple ML inference, "run this container once" cron-style.
  • Compare: ACA (Container Apps) for autoscaled microservices; ACI for fire-and-forget containers; App Service for Containers for stateful web apps with slots.

Concepts (deep dive)

What ACI is — and isn't

ACI is the simplest possible container runtime on Azure: hand it an image and a CPU/memory size, get a running container with an IP, pay per second until it stops. There is no cluster to provision, no orchestrator to learn, no replica count to choose. The mental model is "an Azure-managed docker run."

That simplicity is also the limit. ACI has no autoscale, no rolling deploys, no traffic-splitting, no built-in load balancer across replicas — because there are no replicas. The right way to think about the three container hosting options is to place them on a "managed vs flexible" axis:

        ACI                          ACA                    AKS
   ──────────────              ──────────────         ──────────────
   Run a container         Microservices,          Full Kubernetes
   "as is", no fuss        scale to zero,          Bring your own
   No orchestration        KEDA, ingress,          everything
                           Dapr, revisions
   ┌────────────┐         ┌────────────┐          ┌────────────┐
   │ container  │         │ replica 1  │          │ pod        │
   └────────────┘         │ replica 2  │          │ pod        │
                          │ ... auto   │          │ daemonset  │
                          └────────────┘          │ statefulset│
                                                  │ ...        │
                                                  └────────────┘

ACI is the simplest of the three. No scaling, no traffic-splitting, no rolling deploy. You launch a container; it runs; you stop it (or it exits). That's it.

Pricing model

ACI's economics are bursty by design — every second a container isn't running is a second you aren't billed. That makes ACI cheap for things that aren't running most of the time (nightly batch, CI agents, sporadic ML inference) and expensive for things that are (a 24/7 API). The crossover from "ACI is cheaper" to "ACA / App Service is cheaper" happens fast: a vCPU running 24/7 on ACI costs more than a small ACA replica.

Per-second billing while a container is running:
   vCPU-seconds × $/vCPU-sec   +   GB-seconds × $/GB-sec   +   storage GB

Rough numbers (region-dependent): - 1 vCPU, 2 GB RAM, 1 hour ≈ ~$0.05–0.10 - Same load 24/7 for a month ≈ ~$30–50

⚠️ At sustained load ACI is more expensive than App Service / ACA / AKS. ACI shines for short bursts — minutes to hours, not 24/7.

Container groups (the "pod" concept)

A container group is ACI's equivalent of a Kubernetes pod — a colocated bundle of containers that share network, lifecycle, and volumes. This is how you do sidecars (log shipper, metrics collector) and init containers (DB migration before the main app starts) on ACI without an orchestrator. Inside the group, containers reach each other on localhost; outside, the group has one IP.

Multiple containers can share: - Lifecycle — start/stop together - Network namespacelocalhost between containers - Volumes — Azure Files / emptyDir - Public IP + DNS label

# Container group with main + sidecar
apiVersion: 2023-05-01
location: eastus
name: my-group
properties:
  containers:
    - name: app
      properties:
        image: myacr.azurecr.io/app:1.0
        resources: { requests: { cpu: 1.0, memoryInGB: 1.5 } }
        ports: [{ port: 80 }]
    - name: sidecar-logger
      properties:
        image: myacr.azurecr.io/log-shipper:1.0
        resources: { requests: { cpu: 0.5, memoryInGB: 0.5 } }
  ipAddress:
    type: Public
    ports: [{ protocol: tcp, port: 80 }]

Init containers run before the main containers — useful for migrations, certificate fetching, schema setup.

  initContainers:
    - name: db-migrate
      properties:
        image: myacr.azurecr.io/db-migrate:1.0
        command: ["dotnet", "DbMigrate.dll"]

Restart policies

What ACI does when the container exits. The default (Always) is what you want for a service that should keep running; Never is what you want for a job that's supposed to finish and stay finished. Picking the wrong one for a batch job means it loops on success.

Policy Behavior Use for
Always Restart on exit (success or fail) Long-running services
OnFailure Restart only on non-zero exit Long-running with retry-on-crash
Never Run once; don't restart Batch jobs, cron-style runs

For a batch job (the most common ACI use case):

properties:
  restartPolicy: Never
  containers:
    - name: nightly-report
      properties:
        image: myacr.azurecr.io/report-job:1.0

VNet injection (Standard SKU)

   Default ACI: public IP, optional DNS label
   VNet-injected ACI:
   - Deploys into a delegated subnet
   - Private IP only (no public)
   - Can reach VNet resources (SQL, Redis, Storage with private endpoints)
   - Sidecar pattern with backend services

Set the subnet during create:

az container create \
  --resource-group rg \
  --name privatecontainer \
  --image myacr.azurecr.io/app:1.0 \
  --vnet myvnet \
  --subnet aci-subnet

⚠️ The subnet must be delegated to Microsoft.ContainerInstance/containerGroups; can't be shared with other resources.

GPU SKUs

resources:
  requests:
    cpu: 4
    memoryInGB: 16
    gpu:
      count: 1
      sku: V100   # also K80, P100 in some regions

Use cases: - ML inference for sporadic traffic (cheaper than holding a GPU VM 24/7). - One-off training jobs. - Image / video processing bursts.

Updates: destroy and recreate

ACI doesn't support rolling deploy. Update flow:

1. Deploy new container group (different name, same DNS label or different)
2. Cut traffic over (DNS, Front Door, Traffic Manager)
3. Delete the old group

Or — simpler — use Bicep / ARM with delete-then-create. Expect downtime if the same name is reused.

If you need rolling deploy, you're outgrowing ACI; use ACA or App Service for Containers.

Limits

Limit Value
Max vCPU 4 (per group; higher in some regions)
Max memory 16 GB
Max GPU 4
Containers per group 60 (rarely useful past ~5)
Volumes Azure Files, emptyDir, secret, gitRepo
Networking Public IP, VNet-injected, DNS label

For more, jump to AKS or Azure Batch.

Image registry auth

  • Azure Container Registry — managed identity preferred:
    properties:
      imageRegistryCredentials:
        - server: myacr.azurecr.io
          identity: /subscriptions/.../userAssignedIdentities/aci-mi
    
  • Public registries — no creds needed.
  • Docker Hub private — username/password (avoid; use ACR).

Comparison — when to pick which

Scenario Pick Why
Run nightly batch job ACI Fire-and-forget; pay only for runtime
Build agent for CI ACI Spin up per build; teardown
Microservice with autoscale ACA KEDA, scale to zero, ingress
Web app with slots App Service for Containers Slot swap, easy SSL/scale-out
Anything you need K8s features for AKS Full control plane
ML inference 24/7 ACA with GPU or AKS Sustained load
ML inference sporadic ACI GPU Cheap when idle (you don't pay)
Stateful workload App Service / AKS ACI has no managed state model

Triggering ACI from elsewhere

ACI is the execution surface for several Azure services:

Logic App ─── "Create container instance" action ──▶ ACI
Functions ─── REST / SDK ─────────────────────────▶ ACI
Event Grid ── trigger ────────────────────────────▶ Functions ─▶ ACI
Bicep / Terraform ─ az CLI in pipeline ───────────▶ ACI

Common pattern: a Logic App on a schedule creates an ACI, waits for completion, deletes it.


How it works under the hood

   ┌──────────────── Azure Container Instances ────────────────┐
   │                                                           │
   │   You: az container create --image myacr.../app:1.0       │
   │                                                           │
   │   ACI control plane:                                      │
   │   1. Schedule on shared (or VNet) host                    │
   │   2. Pull image (private = MI / SP creds)                 │
   │   3. Allocate IP, mount volumes                           │
   │   4. Start container(s) per group spec                    │
   │   5. Stream logs / metrics to Log Analytics               │
   │   6. On exit: apply restartPolicy                         │
   │                                                           │
   │   You're billed per-second from start to terminal state   │
   └───────────────────────────────────────────────────────────┘

ACI runs on a multi-tenant container fleet behind the scenes (or your VNet for injected groups). You don't see or manage the underlying hosts. Microsoft handles patching, isolation, networking.


Code: correct vs wrong

❌ Wrong: 24/7 service on ACI

az container create --image my-api:1.0 --restart-policy Always ...

You pay sustained vCPU/GB. ACA or App Service is cheaper at any sustained load.

✅ Correct: short-lived job on ACI

az container create --image my-batch-job:1.0 --restart-policy Never ...

❌ Wrong: hardcoded ACR password in YAML

imageRegistryCredentials:
  - server: myacr.azurecr.io
    username: myacr
    password: "secret-password"

✅ Correct: managed identity

imageRegistryCredentials:
  - server: myacr.azurecr.io
    identity: /subscriptions/.../userAssignedIdentities/aci-puller

❌ Wrong: expecting rolling deploy

# Update image in place — there is no in-place update
az container update --name my-app --image my-app:2.0

✅ Correct: blue/green via two groups

az container create --name my-app-v2 --image my-app:2.0 ...
# Cut traffic via Front Door / Traffic Manager
az container delete --name my-app-v1

❌ Wrong: ACI for autoscale

Need to scale 1 → 100 replicas based on queue depth.

✅ Correct: Azure Container Apps with KEDA

Container Apps + KEDA scaler on Service Bus / Event Hubs.

Bicep example

resource job 'Microsoft.ContainerInstance/containerGroups@2023-05-01' = {
  name: 'nightly-report'
  location: 'eastus'
  properties: {
    osType: 'Linux'
    restartPolicy: 'Never'
    imageRegistryCredentials: [{
      server: 'myacr.azurecr.io'
      identity: aciIdentity.id
    }]
    containers: [{
      name: 'report'
      properties: {
        image: 'myacr.azurecr.io/report:1.0'
        resources: { requests: { cpu: 1, memoryInGB: 1 } }
        environmentVariables: [
          { name: 'DB_CONN', secureValue: dbConnFromKv }
        ]
      }
    }]
  }
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: { '${aciIdentity.id}': {} }
  }
}

Design patterns for this topic

Pattern 1 — "Cron-style batch via Logic App + ACI"

  • Intent: scheduled job; pay only when running.
  • How: Logic App on recurrence; "Create container instance"; wait for completion; delete.

Pattern 2 — "Init container for migrations"

  • Intent: schema migration before app start.
  • How: initContainers: [db-migrate] runs first; main app waits.

Pattern 3 — "Sidecar pattern (logger / proxy)"

  • Intent: companion processes alongside main container.
  • How: Multi-container group; shared localhost; shared volume.

Pattern 4 — "GPU inference burst"

  • Intent: sporadic ML inference.
  • How: ACI GPU SKU; trigger from Functions / Event Grid; tear down when idle.

Pattern 5 — "Build agent runner"

  • Intent: ephemeral CI agent.
  • How: Pipeline launches ACI with self-hosted agent image; runs job; deletes.

Pros & cons / trade-offs

Aspect Pros Cons
Setup One CLI / Bicep call No orchestration features
Pricing Per-second; great for bursts Expensive sustained
Lifecycle Container groups (pod-like) No autoscale, no rolling
Networking VNet injection available Subnet must be delegated
GPU Sporadic GPU workloads cheap Limited region availability
Updates Simple destroy/recreate Downtime by default

vs alternatives:

Service When over ACI
Container Apps (ACA) Autoscale, ingress, scale-to-zero microservices, KEDA, Dapr, revisions
App Service for Containers Stateful web apps with slots, App Service plan model, easier scale-out
AKS Full K8s features (statefulsets, daemonsets, custom controllers)
Azure Batch Massive parallel HPC batches, scheduling smarts

When to use / when to avoid

  • Use for ephemeral batch jobs, build/test runners, dev sandboxes, simple ML inference bursts.
  • Use as the "execution surface" triggered from Logic Apps / Functions / Pipelines.
  • Use when you want zero orchestration overhead and per-second billing.
  • Avoid for sustained 24/7 workloads — cost adds up.
  • Avoid when you need autoscale (use ACA).
  • Avoid when you need rolling deploys / slots (use App Service for Containers).
  • Avoid when you need K8s primitives (use AKS).
  • Avoid for stateful long-running services (no managed state model).

Interview Q&A

Q1. What is Azure Container Instances? A serverless "run a container" service: launch one container (or a container group) without orchestration, K8s, or VMs. Per-second billing.

Q2. ACI vs Container Apps vs AKS? ACI = "just run a container". ACA = autoscaled microservices with ingress/KEDA/Dapr. AKS = full Kubernetes.

Q3. What's a container group? Multiple containers sharing lifecycle, network namespace, volumes — analogous to a Kubernetes pod.

Q4. Pricing model? Per-second of vCPU + memory while running. Cheap for short bursts; expensive at sustained load.

Q5. Restart policies? Always, OnFailure, Never. Use Never for batch jobs.

Q6. VNet injection? Deploy ACI into a delegated subnet for private IP and access to VNet resources (SQL, Redis behind private endpoints).

Q7. GPU SKUs? V100, K80, P100 (region-dependent). Good for sporadic ML inference where holding a GPU VM 24/7 is wasteful.

Q8. Rolling deploy on ACI? Not supported. Destroy + recreate. For rolling, use ACA or App Service.

Q9. Init containers? Run before main containers in the group. Ideal for migrations, cert fetch, schema setup.

Q10. Sidecar pattern on ACI? Add a companion container to the same group; shared localhost; shared volumes.

Q11. ACI from Logic Apps? Yes — "Create container instance" action. Common pattern for scheduled batch.

Q12. Limits? 4 vCPU / 16 GB / 4 GPU per group typical; higher SKUs in some regions. No autoscale. Beyond that → ACA / AKS.


Gotchas / common mistakes

  • ⚠️ Sustained 24/7 on ACI — costs more than ACA/App Service.
  • ⚠️ Expecting in-place updates — destroy + recreate only.
  • ⚠️ Forgetting restartPolicy: Never for batch — defaults restart on exit; you'll loop forever.
  • ⚠️ Subnet not delegated — VNet inject fails.
  • ⚠️ Hardcoded ACR creds — use managed identity.
  • ⚠️ No autoscaleACI is fixed-count.
  • ⚠️ Multi-region failover — no built-in; do it yourself with Front Door.
  • ⚠️ Persistent storage assumed — mount Azure Files explicitly; container FS is ephemeral.
  • ⚠️ Cold start surprise — image pull can dominate startup; pre-warm or use small images.

Further reading