Cost Optimization

    How Do You Reduce Azure Container Instances Costs?

    Reduce Azure Container Instances costs through right-sizing resource requests, lifecycle automation, platform selection analysis, and commitment discounts for steady-state workloads.

    Spotto
    February 2026
    30 min read
    Azure Container Instances Cost Optimization

    Here is a scenario that plays out in Azure subscriptions more often than anyone admits: a developer spins up a container group for a quick data processing job, requests 2 vCPUs and 4 GB of memory because the defaults seemed reasonable, and moves on. Three months later, that container is still running 24/7, averaging 0.3 vCPUs, and silently billing for the full 2. Multiply that by 20 client tenants and you have a line item nobody budgeted for.

    Most ACI cost problems come down to one misunderstanding: ACI bills per second for every vCPU and GB of memory you request, not what you use. The most effective ACI cost reductions come from aligning resource requests to actual consumption, automating stop/start lifecycles for idle containers, choosing the right platform for your workload pattern, and applying commitment discounts for steady-state workloads.

    Key Takeaways at a Glance:

    ACI bills per second for requested vCPUs (rounded to whole numbers) and memory (rounded to 0.1 GB), not actual utilization
    Windows containers on ACI cost ~96% more than Linux due to software surcharge
    ACI is cheaper than Container Apps for 24/7 always-on workloads
    Migrating scheduled WebJobs to ACI can reduce costs by 90%+
    Automating stop/start during idle periods can reduce costs by 50-67%

    What Problem This Solves

    ACI occupies a specific niche: serverless containers with per-second billing and no cluster management. That simplicity creates a false sense of cheapness. The per-second model means you only pay for what you run -- but you pay for what you request, not what you consume. This disconnect is the root of most ACI cost traps.

    Common ACI cost traps:

    Oversized resource requests -- 2 vCPU/4 GB requested when actual usage is 0.5 vCPU/1.2 GB
    Always-on containers on a per-second service -- Running 24/7 on a service designed for ephemeral workloads
    Missing lifecycle automation -- No stop/start schedules for containers that only need to run during business hours
    Windows container surcharges -- Running Windows containers when Linux would work, nearly doubling the bill
    Scheduled jobs on App Service plans -- Paying for an always-on B1/S1 plan for a job that runs 5 minutes a day
    Pay-as-you-go for steady workloads -- Missing savings plan discounts for containers that run predictably

    ACI Billing Model

    Understanding the billing model is the foundation of every optimization. ACI charges three components, all billed per second with a minimum of 60 seconds.

    ComponentRate (Linux, East US)Notes
    vCPU$0.0000112/second ($0.0405/hour)Rounded up to whole vCPUs
    Memory (GB)$0.00000123/GB-second ($0.00445/GB-hour)Rounded up to 0.1 GB
    Windows Software$0.0000120/vCPU-second ($0.0432/vCPU-hour)Add-on for Windows OS containers

    Monthly cost examples (730 hours, Linux, East US):

    ACI Monthly Cost Examples (Linux, East US, 730 hours)
    =====================================================
    
    1 vCPU / 1 GB:
      vCPU:   1 x $0.0405/hr x 730 hr  = $29.57
      Memory: 1 x $0.00445/hr x 730 hr = $3.25
      Total:  $32.82/month
    
    2 vCPU / 4 GB:
      vCPU:   2 x $0.0405/hr x 730 hr  = $59.13
      Memory: 4 x $0.00445/hr x 730 hr = $12.99
      Total:  $72.12/month
    
    4 vCPU / 16 GB:
      vCPU:   4 x $0.0405/hr x 730 hr  = $118.26
      Memory: 16 x $0.00445/hr x 730 hr = $51.98
      Total:  $170.24/month

    Two key billing details most people miss:

    1. Windows surcharge nearly doubles the bill. Adding the Windows software fee to a 2 vCPU container adds $0.0432/hr per vCPU = $63.07/month. That turns a $72.12 Linux container into a $135.19 Windows container -- a 96% increase for the same workload.

    2. Rounding applies to requests, not usage. If you request 1.5 vCPUs, you are billed for 2. If you request 3.7 GB of memory, you are billed for 3.7 GB (rounded to nearest 0.1 GB). The vCPU rounding to whole numbers is the one that catches people.

    ACI Optimization Priority Flowchart

    Use this decision tree to prioritize which optimizations to tackle first for each container group:

    ACI Cost Optimization Priority Flowchart
    =========================================
    
    [Start] Is the container group running Windows?
      |
      +-- YES --> Can it run on Linux?
      |     |
      |     +-- YES --> MIGRATE TO LINUX (saves ~49% immediately)
      |     +-- NO  --> Continue to next check
      |
      +-- NO --> Continue to next check
      |
      +-- Is the container running 24/7?
            |
            +-- YES --> Does it need to run 24/7?
            |     |
            |     +-- NO  --> AUTOMATE STOP/START (saves 50-67%)
            |     +-- YES --> Is it a steady-state workload?
            |           |
            |           +-- YES --> APPLY SAVINGS PLAN (saves 27-52%)
            |           +-- NO  --> Continue to next check
            |
            +-- NO --> Continue to next check
            |
            +-- Are resource requests aligned to actual usage?
                  |
                  +-- NO  --> RIGHT-SIZE REQUESTS (saves 30-70%)
                  +-- YES --> Is this workload better suited for another platform?
                        |
                        +-- AKS (high density) --> MIGRATE
                        +-- Container Apps (event-driven) --> EVALUATE
                        +-- App Service (web apps) --> EVALUATE
                        +-- KEEP ON ACI (ephemeral/serverless fit)

    How It Works

    Right-Sizing and Billing Alignment

    Right-sizing is the highest-impact optimization because ACI bills for requested resources, not consumed resources. A container requesting 2 vCPUs but averaging 0.3 vCPUs is wasting 85% of its compute spend.

    5-step right-sizing process:

    Step 1: Collect metrics -- Pull CPU and memory utilization from Azure Monitor for at least 7 days (ideally 30 days) to capture peak and average patterns
    Step 2: Identify peak utilization -- Find the P95 (95th percentile) CPU and memory usage. This is your baseline for right-sizing -- you want headroom above peak, not average
    Step 3: Apply rounding rules -- vCPUs round to whole numbers. Memory rounds to 0.1 GB. Set requests to the next rounding boundary above your P95
    Step 4: Add safety buffer -- Add 20-30% headroom above P95 for unexpected spikes. For batch jobs with predictable patterns, 10% may suffice
    Step 5: Redeploy and monitor -- ACI does not support in-place resource changes. You must delete and recreate the container group with new resource requests

    CLI commands for right-sizing analysis:

    # List all container groups with their resource requests
    az container list --query "[].{Name:name, ResourceGroup:resourceGroup, \
      Containers:containers[].{Name:name, CPU:resources.requests.cpu, \
      MemoryGB:resources.requests.memoryInGb}, \
      OsType:osType, State:instanceView.state}" -o table
    
    # Get CPU utilization metrics for a container group (last 7 days)
    az monitor metrics list \
      --resource /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.ContainerInstance/containerGroups/<name> \
      --metric CpuUsage \
      --interval PT1H \
      --start-time $(date -d '7 days ago' -u +%Y-%m-%dT%H:%M:%SZ) \
      --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
      --aggregation Average Maximum \
      --query "value[0].timeseries[0].data[].{Time:timeStamp, AvgCPU:average, MaxCPU:maximum}" -o table
    
    # Get memory utilization metrics
    az monitor metrics list \
      --resource /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.ContainerInstance/containerGroups/<name> \
      --metric MemoryUsage \
      --interval PT1H \
      --start-time $(date -d '7 days ago' -u +%Y-%m-%dT%H:%M:%SZ) \
      --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
      --aggregation Average Maximum \
      --query "value[0].timeseries[0].data[].{Time:timeStamp, AvgMemMB:average, MaxMemMB:maximum}" -o table
    
    # Redeploy with right-sized resources (requires delete + create)
    az container delete --name <name> --resource-group <rg> --yes
    az container create \
      --name <name> \
      --resource-group <rg> \
      --image <image> \
      --cpu 1 \
      --memory 1.5 \
      --os-type Linux

    Failure mode warning: ACI does not support in-place resource updates. You must delete and recreate the container group, which means downtime. For production workloads, use a blue-green deployment pattern: create the new container group first, validate it, then delete the old one. If the container uses a public IP, the IP will change unless you use an Application Gateway or Azure Front Door in front of it.

    Switch Windows Containers to Linux

    The Windows software surcharge adds $0.0432/vCPU-hour on top of the base compute cost. For a 2 vCPU container running 24/7, that is $63.07/month in additional charges -- nearly doubling the bill. If your application can run on Linux (most .NET 6+ apps can), switching the OS type is the single highest-impact change per container.

    ConfigurationLinux Cost/moWindows Cost/moDifference
    1 vCPU / 1.5 GB$36.23$67.77+$31.54 (+87%)
    2 vCPU / 4 GB$72.12$135.19+$63.07 (+87%)
    4 vCPU / 16 GB$170.24$296.38+$126.14 (+74%)

    Note: Windows containers on ACI also have limited region availability and do not support GPU SKUs. If you are running .NET Framework 4.x applications that cannot be ported to .NET 6+, Windows containers are required. For everything else, Linux is the cost-optimal choice.

    Platform Selection

    ACI is not always the cheapest option. The right platform depends on your workload pattern: how long it runs, how predictable the load is, and how many containers you manage.

    When to Stay on ACI

    Short-lived batch jobs (minutes to hours)
    Scheduled tasks that run on a timer (cron-style)
    CI/CD build agents that spin up per-pipeline
    Dev/test containers that need fast startup without cluster overhead
    Always-on workloads with 1-3 containers (ACI is cheaper than Container Apps here)

    When to Move Off ACI

    High-density microservices (10+ containers) -- AKS is more cost-effective at scale
    Event-driven scale-to-zero -- Container Apps with KEDA scales to zero; ACI does not
    Complex networking or service mesh requirements -- ACI has limited networking options
    Stateful workloads requiring persistent volumes beyond Azure Files

    Platform cost comparison (1 vCPU / 2 GB, Linux, East US, 730 hours/month):

    PlatformMonthly CostBilling ModelBest For
    ACI$38.47Per-second (requested)Ephemeral, batch, always-on (small count)
    Container Apps (Consumption)$43.80Per-second (active) + requestsEvent-driven, scale-to-zero
    AKS (D2s v3 node)$70.08Per-node (VM pricing)High-density, many containers per node
    App Service (B1)$13.14Fixed planWeb apps, always-on

    Important insight: ACI is actually cheaper than Container Apps for 24/7 always-on workloads. Container Apps adds a per-request charge and has higher per-second rates. If your container runs continuously and does not need scale-to-zero, ACI is the better deal. Container Apps wins when you need event-driven auto-scaling or scale-to-zero during idle periods.

    Migrate Scheduled Jobs from WebJobs to ACI

    One of the most common cost wins we see: organizations running scheduled WebJobs on a B1 App Service plan ($13.14/month) for a task that runs 5 minutes per day. Moving that to ACI reduces the cost to the seconds the job actually runs.

    WebJob to ACI Migration Cost Comparison
    ========================================
    
    B1 App Service Plan (always-on for WebJob):
      Cost: $13.14/month (24/7, regardless of job runtime)
    
    ACI (1 vCPU / 1 GB, 5 min/day, 30 days):
      vCPU:   1 x $0.0000112/sec x 300 sec x 30 days = $0.10
      Memory: 1 x $0.00000123/sec x 300 sec x 30 days = $0.01
      Total:  $0.11/month
    
    Savings: $13.03/month (99.2% reduction)
    Annual:  $156.36/year per job
    
    With 10 scheduled jobs across 5 tenants:
      Before: $657/month (10 x B1 plans, some shared)
      After:  $5.50/month (50 ACI jobs)
      Savings: $651.50/month = $7,818/year

    Implementation -- schedule ACI with Logic Apps:

    # Create a container group with restart policy "Never" (run once)
    az container create \
      --name batch-job-daily \
      --resource-group rg-batch \
      --image myregistry.azurecr.io/batch-processor:latest \
      --cpu 1 \
      --memory 1 \
      --os-type Linux \
      --restart-policy Never \
      --environment-variables JOB_TYPE=daily
    
    # Use Azure Logic Apps or Automation to trigger on schedule:
    # 1. Logic App recurrence trigger (e.g., daily at 02:00 UTC)
    # 2. Action: "Create or update a container group"
    # 3. Action: Wait for container to complete (poll state)
    # 4. Action: Delete container group (stop billing)
    
    # Or use az container start for pre-created groups:
    az container start --name batch-job-daily --resource-group rg-batch
    # ... job runs ...
    az container stop --name batch-job-daily --resource-group rg-batch

    Shift Bursty AKS Workloads to Virtual Nodes

    AKS virtual nodes (powered by ACI) let you burst Kubernetes pods to ACI without provisioning additional VM nodes. Instead of scaling up your AKS node pool for intermittent spikes, you pay per-second for the burst pods only.

    Limitations of AKS virtual nodes:

    Linux pods only -- Windows containers are not supported on virtual nodes
    No DaemonSets -- monitoring agents and log collectors will not run on virtual node pods
    Limited persistent storage -- only Azure Files is supported, not Azure Disks
    No host networking -- pods use ACI networking, which has different DNS and network policy behavior
    Startup latency -- ACI pods take 15-30 seconds to start vs. 1-5 seconds for pre-warmed node pods
    # Enable virtual nodes on an existing AKS cluster
    az aks enable-addons \
      --resource-group rg-aks \
      --name my-cluster \
      --addons virtual-node \
      --subnet-name aci-subnet
    
    # Deploy a pod that tolerates the virtual node taint
    cat <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: Pod
    metadata:
      name: burst-processor
    spec:
      containers:
      - name: processor
        image: myregistry.azurecr.io/processor:latest
        resources:
          requests:
            cpu: "2"
            memory: "4Gi"
          limits:
            cpu: "2"
            memory: "4Gi"
      tolerations:
      - key: virtual-kubelet.io/provider
        operator: Exists
      - key: azure.com/aci
        effect: NoSchedule
      nodeSelector:
        kubernetes.io/role: agent
        beta.kubernetes.io/os: linux
        type: virtual-kubelet
    EOF
    
    # Scale a deployment to virtual nodes during burst
    kubectl scale deployment burst-processor --replicas=10
    # Pods exceeding node capacity will schedule on virtual nodes (ACI)
    # Cost: per-second billing only for the burst duration

    Scheduling and Lifecycle Management

    Since ACI bills per second, every second a container runs idle is wasted spend. Lifecycle automation is the second-highest impact optimization after right-sizing.

    Automate Container Group Shutdown

    Many ACI container groups run during business hours but sit idle overnight and on weekends. Automating stop/start can eliminate 50-67% of the bill depending on the schedule.

    PowerShell Automation Runbook:

    # Azure Automation Runbook: ACI Lifecycle Management
    # Schedule: Run every hour, evaluates current time against schedule
    
    param(
        [string]$ResourceGroupName,
        [string]$TagName = "aci-schedule",
        # Tag value format: "start=08:00,stop=18:00,tz=Eastern Standard Time,days=Mon-Fri"
        [bool]$DryRun = $true
    )
    
    Connect-AzAccount -Identity
    
    # Get all container groups with the schedule tag
    $containerGroups = Get-AzContainerGroup -ResourceGroupName $ResourceGroupName |
        Where-Object { $_.Tag.ContainsKey($TagName) }
    
    foreach ($cg in $containerGroups) {
        $schedule = $cg.Tag[$TagName]
        $parts = @{}
        $schedule -split ',' | ForEach-Object {
            $kv = $_ -split '='
            $parts[$kv[0]] = $kv[1]
        }
    
        $tz = [System.TimeZoneInfo]::FindSystemTimeZoneById($parts['tz'])
        $localTime = [System.TimeZoneInfo]::ConvertTimeFromUtc((Get-Date).ToUniversalTime(), $tz)
        $currentHour = $localTime.ToString("HH:mm")
        $currentDay = $localTime.DayOfWeek.ToString().Substring(0,3)
    
        $activeDays = $parts['days'] -split '-'
        $isActiveDay = $activeDays -contains $currentDay -or
                       ($activeDays.Count -eq 2 -and
                        [array]::IndexOf(@('Mon','Tue','Wed','Thu','Fri','Sat','Sun'), $currentDay) -ge
                        [array]::IndexOf(@('Mon','Tue','Wed','Thu','Fri','Sat','Sun'), $activeDays[0]) -and
                        [array]::IndexOf(@('Mon','Tue','Wed','Thu','Fri','Sat','Sun'), $currentDay) -le
                        [array]::IndexOf(@('Mon','Tue','Wed','Thu','Fri','Sat','Sun'), $activeDays[1]))
    
        $shouldRun = $isActiveDay -and ($currentHour -ge $parts['start']) -and ($currentHour -lt $parts['stop'])
        $currentState = $cg.ProvisioningState
    
        if ($shouldRun -and $currentState -ne "Running") {
            if ($DryRun) {
                Write-Output "[DRY RUN] Would START: $($cg.Name) (schedule: $schedule)"
            } else {
                Start-AzContainerGroup -Name $cg.Name -ResourceGroupName $ResourceGroupName
                Write-Output "Started: $($cg.Name)"
            }
        } elseif (-not $shouldRun -and $currentState -eq "Running") {
            if ($DryRun) {
                Write-Output "[DRY RUN] Would STOP: $($cg.Name) (schedule: $schedule)"
            } else {
                Stop-AzContainerGroup -Name $cg.Name -ResourceGroupName $ResourceGroupName
                Write-Output "Stopped: $($cg.Name)"
            }
        }
    }

    Savings examples:

    Lifecycle Automation Savings Examples
    =====================================
    
    Scenario 1: Business hours only (Mon-Fri, 8am-6pm)
      Running hours: 10 hrs/day x 22 days = 220 hrs/month
      vs. 24/7:     730 hrs/month
      Reduction:    70% of hours eliminated
      2 vCPU/4 GB Linux: $72.12 -> $21.74/month (savings: $50.38/month, 70%)
    
    Scenario 2: Extended hours (Mon-Fri, 6am-10pm)
      Running hours: 16 hrs/day x 22 days = 352 hrs/month
      vs. 24/7:     730 hrs/month
      Reduction:    52% of hours eliminated
      2 vCPU/4 GB Linux: $72.12 -> $34.74/month (savings: $37.38/month, 52%)
    
    Scenario 3: Weekdays only, 24 hours
      Running hours: 24 hrs/day x 22 days = 528 hrs/month
      vs. 24/7:     730 hrs/month
      Reduction:    28% of hours eliminated
      2 vCPU/4 GB Linux: $72.12 -> $52.13/month (savings: $19.99/month, 28%)

    Use Spot Container Groups (Preview)

    Spot container groups run on surplus Azure capacity at up to 70% discount. They can be evicted at any time when Azure needs the capacity back, making them suitable for fault-tolerant, interruptible workloads.

    Spot ACI availability (as of early 2026): Spot container groups are in preview and available in limited regions (West US, West Europe, and Southeast Asia). Check the Azure documentation for current region availability. Spot containers can be evicted with 30 seconds notice and should only be used for workloads that can handle interruption gracefully.

    ComponentStandard RateSpot RateDiscount
    vCPU (Linux)$0.0405/hour~$0.0122/hour~70%
    Memory (Linux)$0.00445/GB-hour~$0.00134/GB-hour~70%
    # Create a Spot container group (preview)
    az container create \
      --name batch-spot-job \
      --resource-group rg-batch \
      --image myregistry.azurecr.io/batch-processor:latest \
      --cpu 2 \
      --memory 4 \
      --os-type Linux \
      --priority Spot \
      --restart-policy Never \
      --location westus
    
    # Spot container groups are billed at Spot rates
    # They can be evicted when Azure needs the capacity
    # Implement checkpointing in your application to handle eviction:
    #   1. Listen for SIGTERM (30 second warning)
    #   2. Save progress to Azure Storage
    #   3. Resume from checkpoint on next run

    Commitment Discounts

    For container groups that run steadily month after month, Azure Savings Plans provide significant discounts over pay-as-you-go rates. The ACI compute charges are eligible for compute savings plans.

    Savings plan discount tiers:

    1-year commitment: ~27% discount over pay-as-you-go
    3-year commitment: ~52% discount over pay-as-you-go

    Decision framework for savings plans:

    Savings Plan Decision Framework
    ================================
    
    Should you commit?
    
    1. Is the workload expected to run for 12+ months?
       +-- NO  --> Stay on pay-as-you-go
       +-- YES --> Continue
    
    2. Is the monthly spend consistent (within 20% variance)?
       +-- NO  --> Commit to the BASE (minimum) monthly spend only
       +-- YES --> Continue
    
    3. Can you commit to 3 years?
       +-- YES --> 3-year plan (~52% discount)
       +-- NO  --> 1-year plan (~27% discount)
    
    Example:
      2 vCPU / 4 GB Linux, 24/7 = $72.12/month pay-as-you-go
      1-year savings plan: ~$52.65/month (save $19.47/month)
      3-year savings plan: ~$34.62/month (save $37.50/month)

    CLI commands for savings plan analysis:

    # View current ACI spend to determine commitment amount
    az consumption usage list \
      --start-date $(date -d '30 days ago' +%Y-%m-%d) \
      --end-date $(date +%Y-%m-%d) \
      --query "[?contains(instanceName, 'containerGroups')].{Name:instanceName, \
        Cost:pretaxCost, Quantity:quantity, Unit:unitOfMeasure}" -o table
    
    # List existing savings plans
    az billing savings-plan list \
      --query "[].{Name:name, Term:term, Commitment:commitment.amount, \
        Utilization:utilization.aggregates[0].value}" -o table
    
    # Purchase a savings plan (use Azure Portal for guided experience)
    # Navigate to: Azure Portal > Cost Management > Savings Plans > Add
    # Select: Compute Savings Plan
    # Scope: Subscription or Resource Group
    # Term: 1 year or 3 years
    # Hourly commitment: Based on your steady-state ACI compute spend

    Five ACI Cost Anti-Patterns

    1. The Oversized Batch Job

    Pattern: A batch processing container is created with 4 vCPUs and 16 GB because the developer was not sure how much the job would need. The actual job uses 0.8 vCPUs and 2.1 GB at peak. The container runs for 2 hours daily.

    Cost impact: The oversized request costs $0.47 per run. Right-sized to 1 vCPU / 3 GB, it would cost $0.10 per run. That is $11.10/month in waste for a single job. Across 20 batch jobs, it is $222/month or $2,664/year.

    Fix: Collect metrics for 7 days, identify P95 usage, right-size to 1 vCPU / 3 GB with 20% buffer.

    2. The Accidental Always-On

    Pattern: A container group was created for a demo or development task. Nobody stopped it. It has been running 24/7 for months with zero traffic or utilization.

    Cost impact: A 2 vCPU / 4 GB Linux container running idle costs $72.12/month. Across 5 forgotten containers, that is $360.60/month or $4,327/year for zero value.

    Fix: Implement mandatory tagging with expiry dates. Run a weekly automation that stops or deletes container groups past their expiry. Tag all ACI resources with owner, purpose, and expiry date.

    3. The Windows Tax

    Pattern: A .NET application is running as a Windows container on ACI because the original codebase targeted .NET Framework. The application has since been upgraded to .NET 8 but nobody changed the container base image.

    Cost impact: A 2 vCPU / 4 GB Windows container costs $135.19/month versus $72.12 on Linux -- an extra $63.07/month for the same workload. Over a year, that is $756.84 per container.

    Fix: Audit all Windows ACI containers. For any running .NET 6+ or .NET 8, switch the base image to the Linux variant. Test thoroughly before production migration.

    4. The WebJob That Could Be a Container

    Pattern: A scheduled WebJob runs on a B1 App Service plan for 5 minutes daily. The App Service plan runs 24/7 to host the WebJob, even though the job only needs 2.5 hours of compute per month.

    Cost impact: B1 plan costs $13.14/month for 730 hours. The equivalent ACI job costs $0.11/month for 2.5 hours. That is $13.03/month of waste per job.

    Fix: Containerize the WebJob, deploy to ACI with restart-policy Never, trigger with Logic Apps or Azure Automation on a schedule.

    5. The Missing Savings Plan

    Pattern: An organization runs 10 ACI container groups 24/7 for a production API backend. The workload has been stable for 18 months. All containers are on pay-as-you-go pricing.

    Cost impact: 10 containers at 2 vCPU / 4 GB = $721.20/month pay-as-you-go. A 1-year savings plan would cost ~$526.48/month (27% savings). A 3-year plan would cost ~$346.18/month (52% savings). That is $2,337 to $4,500/year left on the table.

    Fix: Analyze the past 12 months of ACI spend. Identify the steady-state baseline. Purchase a savings plan for the baseline amount. Let pay-as-you-go cover any burst above the baseline.

    The ACI Cost Crossover

    ACI's per-second billing is an advantage for short-running workloads, but there is a crossover point where the per-second model becomes more expensive than a fixed-price alternative. Understanding this crossover helps you make platform decisions.

    Daily RuntimeMonthly HoursACI Cost (1 vCPU/2 GB)B1 App ServiceCheaper Option
    5 min2.5 hrs$0.11$13.14ACI
    1 hour30 hrs$1.35$13.14ACI
    4 hours120 hrs$5.40$13.14ACI
    8 hours240 hrs$10.80$13.14ACI
    10 hours300 hrs$13.50$13.14App Service
    18 hours540 hrs$24.30$13.14App Service
    24 hours730 hrs$38.47$13.14App Service

    Key insight: For a 1 vCPU / 2 GB workload, ACI is the cheapest option when the container runs less than ~10 hours per day (~300 hours/month). Beyond that, a B1 App Service plan at $13.14/month flat is cheaper. However, ACI remains valuable for containers that need more than 1 vCPU or more than 1.75 GB memory (which exceeds B1 limits), or when you need fast startup and no cluster management. The crossover point shifts based on container size and the alternative platform you are comparing against.

    Cost Impact Summary

    RecommendationTypical SavingsComplexityRisk
    Right-size resource requests30-70%MediumLow (with monitoring)
    Switch Windows to Linux~49% per containerMediumMedium (app compatibility)
    Automate stop/start lifecycle50-67%LowLow
    Migrate WebJobs to ACI90-99%MediumLow
    Use Spot container groups~70%MediumHigh (eviction risk)
    Apply savings plan (1-year)~27%LowLow (financial commitment)
    Apply savings plan (3-year)~52%LowMedium (long commitment)
    Delete abandoned containers100% (per container)LowLow (verify first)
    AKS virtual node bursting40-60%HighMedium

    Common Questions

    1. Does ACI bill for stopped containers?

    No. When you stop a container group with az container stop, billing stops. The container group resource remains in Azure (so you do not lose the configuration), but you pay nothing until you start it again. This is why lifecycle automation is so effective -- you only pay for the seconds the container is running.

    2. Why does ACI round vCPUs to whole numbers?

    ACI allocates dedicated CPU cores to your container group. You can request fractional vCPUs (e.g., 0.5), but the billing rounds up to the next whole number. If you request 1.1 vCPUs, you are billed for 2. This means you should always request exactly 1, 2, or 4 vCPUs to avoid paying for unused rounding. The one exception is if you request less than 1 vCPU (e.g., 0.5), which is billed as 1 vCPU.

    3. Is ACI cheaper than Container Apps?

    For always-on workloads, yes. ACI has lower per-second rates than Container Apps consumption plan. Container Apps is cheaper when you need scale-to-zero (ACI cannot scale to zero -- you either run or stop), event-driven auto-scaling, or built-in traffic splitting. For a single container running 24/7, ACI typically costs 10-15% less than Container Apps consumption tier.

    4. Can I resize an ACI container without downtime?

    No. ACI does not support in-place resource updates. You must delete the container group and recreate it with the new resource requests. For zero-downtime resizing, use a blue-green deployment: create a new container group with the desired size, route traffic to it (via Application Gateway, Azure Front Door, or DNS), then delete the old group.

    5. What is the minimum billing increment for ACI?

    ACI bills per second with a minimum of 60 seconds. If your container runs for 10 seconds, you are billed for 60 seconds. After the first 60 seconds, billing is truly per-second. This minimum is per container group creation -- if you stop and restart, the 60-second minimum applies again.

    6. How do I find abandoned ACI containers?

    Query Azure Resource Graph for container groups in "Running" state with zero or near-zero CPU utilization over the past 7 days. Cross-reference with owner tags and expiry dates. Container groups without tags are the most common source of abandoned containers. Spotto automates this detection and flags containers with sustained low utilization.

    7. Do ACI savings plans apply to Spot containers?

    No. Savings plans apply to standard (on-demand) ACI compute charges only. Spot containers are already discounted at ~70% and are not eligible for additional savings plan discounts. Use savings plans for your steady-state standard containers and Spot for interruptible batch workloads.

    8. Can I use ACI with a private container registry?

    Yes. ACI supports pulling images from Azure Container Registry (ACR), Docker Hub, and other private registries. When using ACR, enable the admin user or use a managed identity for authentication. Note that image pull time counts toward your billing (the container is "running" during pull), so use smaller images and layer caching where possible to reduce startup costs.

    9. What happens when a Spot container is evicted?

    Azure sends a SIGTERM signal 30 seconds before eviction. Your application should listen for this signal, save any in-progress work to durable storage (Azure Blob, Azure Queue), and exit gracefully. The container group is stopped after eviction. You can configure your automation to recreate Spot container groups after eviction, implementing a retry-with-checkpoint pattern.

    10. How does Spotto help with ACI cost optimization?

    Spotto continuously monitors your ACI container groups across all subscriptions, detecting oversized resource requests, abandoned containers running with zero utilization, Windows containers that could run on Linux, missing lifecycle automation, and savings plan opportunities. Rather than running one-off audits with CLI commands, Spotto provides ongoing visibility and actionable recommendations that keep your ACI costs aligned with actual workload requirements.

    Use-Case and Scenario Coverage

    Scenario 1: MSP Running Client Batch Jobs Across 15 Tenants

    Before

    $4,200/mo

    15 tenants, ~20 container groups each, oversized at 2 vCPU/4 GB, many running 24/7

    After

    $630/mo

    Right-sized to 1 vCPU/1.5 GB, lifecycle automation, batch jobs run on schedule only

    Actions taken: Right-sized all container groups based on 30-day P95 metrics. Implemented stop/start automation for dev/test containers. Converted always-on batch containers to scheduled runs with restart-policy Never. Deleted 12 abandoned container groups across tenants.

    85% savings -- $42,840/year

    Scenario 2: Enterprise Migrating Scheduled WebJobs to ACI

    Before

    $526/mo

    40 scheduled WebJobs across 8 B1/S1 App Service plans running 24/7

    After

    $32/mo

    40 ACI container groups triggered by Logic Apps, running only during execution

    Actions taken: Containerized all 40 WebJobs. Deployed to ACI with restart-policy Never. Created Logic App triggers for each schedule. Decommissioned 8 App Service plans. Total runtime per job: 3-15 minutes per execution.

    94% reduction -- $5,928/year

    Scenario 3: SaaS Platform with Bursty Event Processing

    Before

    $1,800/mo

    10 always-on ACI containers (4 vCPU/8 GB) processing events, idle 60% of the time

    After

    $660/mo

    Right-sized to 2 vCPU/4 GB, lifecycle automation stops idle containers, 3-year savings plan on steady base

    Actions taken: Right-sized from 4 vCPU/8 GB to 2 vCPU/4 GB based on P95 metrics. Implemented event-driven start/stop using Azure Event Grid. Applied 3-year savings plan for the 4 containers that run 24/7. Remaining 6 containers run on-demand only.

    63% reduction -- $13,680/year

    Scenario 4: Dev/Test Environment with Forgotten Containers

    Before

    $2,400/mo

    35 container groups in dev/test, 20 abandoned, no tagging or lifecycle policy

    After

    $360/mo

    20 abandoned deleted, 15 active with business-hours scheduling and right-sizing

    Actions taken: Audited all dev/test container groups. Deleted 20 containers with zero utilization and no owner tags. Right-sized remaining 15 containers. Implemented mandatory tagging with expiry dates. Deployed business-hours lifecycle automation (Mon-Fri, 8am-6pm).

    85% reduction -- $24,480/year

    Scenario 5: AKS Cluster with Intermittent Batch Workloads

    Before

    $3,500/mo

    5 dedicated D4s v3 nodes for batch processing, utilized 30% on average

    After

    $1,400/mo

    2 steady-state nodes + virtual node bursting to ACI for batch spikes

    Actions taken: Reduced AKS node pool from 5 to 2 nodes for steady-state workloads. Enabled virtual nodes (ACI) for batch workload bursting. Configured pod tolerations to schedule batch jobs on virtual nodes. Batch pods run on ACI per-second billing only during processing.

    60% reduction -- $25,200/year

    Implementation Guidance

    Preconditions

    Azure CLI or PowerShell Az module installed and authenticated
    Contributor role on resource groups containing ACI container groups
    Azure Monitor access for CPU and memory metrics (at least 7 days of data)
    Azure Automation account for scheduled runbooks (for lifecycle management)
    Container registry access for image inspection (to verify Linux compatibility)

    Required Inputs

    Subscription IDs for all Azure subscriptions containing ACI resources
    Container group names, resource groups, and current resource requests
    7-30 days of CPU and memory utilization metrics from Azure Monitor
    Business hours and scheduling requirements for each container group
    Application compatibility information (Linux vs. Windows requirements)

    Rollout Approach

    Step 1: Discovery and inventory -- Use az container list across all subscriptions to catalog every container group with its resource requests, OS type, state, and tags. Export to a spreadsheet for analysis.
    Step 2: Metrics collection -- Pull CPU and memory utilization from Azure Monitor for each container group. Calculate P95 utilization, average utilization, and peak utilization. Flag containers with average utilization below 20% as right-sizing candidates.
    Step 3: Quick wins -- Delete abandoned containers (zero utilization, no owner tags). Stop containers that are running but not needed. Switch Windows containers to Linux where the application supports it.
    Step 4: Right-sizing -- For each container group, calculate the right-sized resource requests based on P95 utilization plus buffer. Redeploy containers one at a time during maintenance windows. Monitor for 48 hours after each change.
    Step 5: Automation and commitments -- Deploy lifecycle automation runbooks for business-hours scheduling. After 30 days of stable optimized spend, evaluate savings plan commitments for steady-state containers.

    Failure Modes

    Under-provisioned after right-sizing: If resource requests are set too low, the container may be OOM-killed (out of memory) or CPU-throttled. Always use P95 metrics with a 20-30% buffer, not average utilization.
    IP address change on redeployment: Deleting and recreating a container group changes the public IP. Applications depending on a fixed IP will break. Use Azure Front Door, Application Gateway, or Private DNS for IP stability.
    Lifecycle automation stops a production container: Misconfigured tags or schedules can stop containers that should be running. Always run lifecycle automation in dry-run mode first and implement alerting on unexpected stops.
    Linux migration breaks application: Some .NET Framework applications and Windows-specific dependencies will not run on Linux. Always test in a staging environment before switching production containers.
    Over-committed savings plan: Purchasing a savings plan larger than your steady-state spend means paying for unused commitment. Base the commitment on your minimum monthly spend, not your average.

    Rollback Strategy

    Every optimization should have a clear rollback path:

    Right-sizing rollback: Recreate the container group with the original resource requests. Keep the previous ARM template or deployment script for at least 30 days after optimization.
    Linux migration rollback: Maintain the Windows container image in your registry. If the Linux container fails validation, redeploy with the Windows image.
    Lifecycle automation rollback: Remove the schedule tag from the container group or disable the automation runbook. The container will remain in its current state (running or stopped).
    WebJob migration rollback: Keep the original App Service plan and WebJob configuration for 30 days. If the ACI replacement fails, re-enable the App Service plan WebJob.

    What to Do Next

    Step 1: Run the inventory -- Use az container list across all subscriptions to catalog every container group with its resource requests, OS type, state, and tags. Identify abandoned containers immediately.
    Step 2: Collect utilization metrics -- Pull 7-30 days of CPU and memory metrics from Azure Monitor. Calculate P95 utilization for each container group. Flag any container where P95 utilization is less than 50% of requested resources.
    Step 3: Implement quick wins -- Delete abandoned containers. Stop idle containers. Switch eligible Windows containers to Linux. These changes can be made within a day and typically deliver 30-50% of total potential savings.
    Step 4: Automate and commit -- Deploy lifecycle automation for business-hours scheduling. After 30 days of stable optimized spend, purchase savings plans for steady-state containers. Consider Spotto for continuous, automated ACI optimization across all environments.

    Summary

    ACI bills for requests, not consumption -- Every vCPU and GB of memory you request is billed per second, regardless of actual utilization. Right-sizing resource requests to match P95 usage is the single highest-impact optimization.
    Windows containers cost nearly double -- The Windows software surcharge adds ~87-96% to the base Linux cost. Any container that can run on Linux should run on Linux.
    Lifecycle automation eliminates idle billing -- Automating stop/start for containers that only need to run during business hours can reduce costs by 50-67% with minimal effort.
    ACI beats Container Apps for always-on workloads -- Despite being positioned as a simpler service, ACI has lower per-second rates than Container Apps consumption plan for containers that run continuously.
    WebJob migration delivers 90%+ savings -- Replacing always-on App Service plans hosting scheduled WebJobs with ACI containers that run only during execution is one of the most reliable cost optimizations in Azure.
    Savings plans work for steady workloads -- For container groups that run 24/7 predictably, a 1-year savings plan saves ~27% and a 3-year plan saves ~52%. Only commit to the minimum steady-state baseline.
    Abandoned containers are free money -- Every organization has forgotten container groups running with zero utilization. A simple inventory and cleanup audit typically recovers hundreds to thousands of dollars per month in pure waste.

    The challenges outlined in this article — oversized container requests, missing lifecycle automation, Windows container surcharges, abandoned dev/test containers — are the same ones our customers work through across their Azure environments. The difference is they use Spotto to surface these opportunities automatically, rather than running the analysis by hand.

    Stop Paying for Idle Container Seconds

    Spotto continuously analyzes your Azure Container Instances to detect oversized resource requests, abandoned containers, missing lifecycle automation, and platform mismatches — giving you clear, actionable recommendations to eliminate container waste across every subscription.

    Free Trial

    Disclaimer: This article is provided for informational purposes only and does not constitute professional advice. Azure pricing, features, and service behaviour may have changed since publication. Always validate recommendations against your specific environment and consult Microsoft's official documentation before making changes to production infrastructure. Spotto is not liable for any actions taken based on the content of this article.