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. PickNeverfor 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 namespace — localhost 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
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:
- 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
You pay sustained vCPU/GB. ACA or App Service is cheaper at any sustained load.
✅ Correct: short-lived job on ACI
❌ Wrong: hardcoded ACR password in YAML
✅ 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
✅ Correct: Azure Container Apps with KEDA
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: Neverfor batch — defaults restart on exit; you'll loop forever. - ⚠️ Subnet not delegated — VNet inject fails.
- ⚠️ Hardcoded ACR creds — use managed identity.
- ⚠️ No autoscale — ACI 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.