dissected.io
Lesson 10 of 23
Intermediateintermediateecscloud-runcontainer-appsserverless-containers11 min read

Running Containers — ECS vs Cloud Run vs Container Apps

You've built a container image and pushed it to a registry. Now something needs to pull it, run it, keep it healthy, scale it, and route traffic to it. The question is how much of that you want to manage yourself. The spectrum runs from serverless containers (you provide the image, the platform does everything else) through managed orchestration (you define services and scaling rules) to self-managed Kubernetes (you control almost everything). This lesson covers the first two; Kubernetes gets its own.

Running Containers — Cloud Run Scaling

Traffic Pattern

Instances

0
$0 cost

Shared Concepts

Before diving into provider specifics, understand the concepts they all share. Revisions (or task definitions, or versions) are immutable snapshots of your deployment configuration — the image, environment variables, resource limits, and scaling rules. When you deploy, you create a new revision. Traffic splitting lets you send a percentage of traffic to the new revision for canary testing. Health checks verify your container is ready to serve — liveness probes restart stuck containers, readiness probes stop routing traffic to unhealthy instances. Autoscaling adjusts instance count based on triggers like CPU, memory, request count, or queue depth. Secrets injection provides credentials at runtime via environment variables or mounted volumes, sourced from a secrets manager.

Cloud Run (GCP)

Cloud Run is the cleanest serverless container story. Give it a container image, tell it how much CPU and memory each instance gets, and it handles everything else. It scales based on concurrent requests — if your concurrency setting is 80 and you get 240 simultaneous requests, Cloud Run runs 3 instances.

The defining feature is scale to zero. No requests, no instances, no cost. When a request arrives, Cloud Run spins up an instance (cold start), and that instance handles requests until traffic subsides. You pay per request plus CPU/memory time while the instance is active.

Cold starts typically add 300ms-2s depending on image size, runtime, and initialization work. Mitigations: set minimum instances (you pay for idle), use a slim base image, lazy-load heavy dependencies, and keep initialization code fast. Cloud Run also supports "CPU always allocated" mode for background work and WebSocket connections, but this disables scale-to-zero.

# Cloud Run service config (gcloud)
gcloud run deploy myapp \
  --image gcr.io/myproject/myapp:v1.2.3 \
  --concurrency 80 \
  --min-instances 1 \
  --max-instances 100 \
  --memory 512Mi \
  --cpu 1 \
  --set-secrets DB_PASSWORD=db-pass:latest

ECS (AWS)

ECS (Elastic Container Service) gives you two launch types. Fargate is serverless — you specify CPU and memory per task, AWS handles the infrastructure. EC2 launch type runs containers on EC2 instances you manage, giving you GPU access, specific instance types, and more control at the cost of managing a cluster.

The core abstraction is a task definition — a JSON document specifying the container image, resource limits, port mappings, environment variables, and logging config. A service wraps a task definition with a desired count, scaling policy, and load balancer integration. ECS places tasks across availability zones and replaces failed ones automatically.

ECS doesn't scale to zero natively. The minimum desired count is 1 (or 0 if you're willing to accept cold starts via EventBridge-triggered scaling). Fargate pricing is per-vCPU-hour and per-GB-hour, billed per second with a 1-minute minimum. EC2 launch type pricing is whatever your EC2 instances cost.

// ECS task definition (simplified)
{
  "containerDefinitions": [{
    "name": "myapp",
    "image": "123456.dkr.ecr.us-east-1.amazonaws.com/myapp:v1.2.3",
    "cpu": 256,
    "memory": 512,
    "portMappings": [{ "containerPort": 8080 }],
    "secrets": [{
      "name": "DB_PASSWORD",
      "valueFrom": "arn:aws:secretsmanager:us-east-1:123456:secret:db-pass"
    }]
  }]
}

Azure Container Apps (Azure)

Container Apps is Azure's serverless container platform, built on Kubernetes and KEDA (Kubernetes Event-Driven Autoscaling) under the hood — but you never interact with Kubernetes directly. It supports scale to zero, event-driven scaling (HTTP, queues, custom metrics), and optional Dapr integration for service-to-service communication, state management, and pub/sub.

Container Apps uses a revision-based model. Each deployment creates an immutable revision, and you can split traffic between revisions for blue-green or canary deployments. Scaling rules are flexible — scale on HTTP concurrency, Azure Service Bus queue length, Kafka lag, or custom metrics.

# Container Apps config (az cli)
az containerapp create \
  --name myapp \
  --resource-group mygroup \
  --environment myenv \
  --image myregistry.azurecr.io/myapp:v1.2.3 \
  --min-replicas 0 \
  --max-replicas 10 \
  --cpu 0.5 \
  --memory 1.0Gi \
  --secrets db-pass=keyvaultref:db-password,identityref:/sub/.../identity \
  --scale-rule-name http-rule \
  --scale-rule-type http \
  --scale-rule-http-concurrency 50

Cold Starts — The Real Story

Cold starts happen when the platform needs to provision a new instance: pull the image (if not cached), start the container, run initialization code, and pass a health check. The dominant factors are image size and application startup time.

Practical mitigations across all providers: keep images under 200MB, pre-warm with minimum instances for latency-sensitive paths, move heavy initialization out of the request path, and use health check grace periods so the platform doesn't kill slow-starting containers.

For background workers processing queues, cold starts don't matter — a few seconds of extra latency on one message is invisible. For user-facing HTTP endpoints, cold starts at the P99 matter a lot.

Concurrency vs Instance Count

This is where teams get confused. Concurrency is how many simultaneous requests one instance handles. Instance count is how many instances are running. Cloud Run and Container Apps let you tune concurrency per instance — set it to 1 and you get Lambda-like behavior (one request per instance), set it to 100 and each instance handles 100 concurrent requests.

Higher concurrency means fewer instances, lower cost, and better resource utilization — but your application must be thread-safe and handle concurrent requests without blocking. CPU-bound workloads need low concurrency. I/O-bound web servers handle high concurrency naturally.

Side-by-Side Comparison

AspectECS Fargate (AWS)Cloud Run (GCP)Container Apps (Azure)
Scale to zeroNot nativeYesYes
BillingPer vCPU-hour + GB-hourPer request + CPU/memory timePer vCPU-second + GB-second
ConcurrencyDefined by appConfigurable (1-1000)Configurable via KEDA
Traffic splittingVia ALB weighted targetsBuilt-in (revision %)Built-in (revision %)
GPU supportYes (EC2 launch type)Yes (limited)No
VPC integrationNativeVPC connector / Direct VPCVNet integration
Max timeoutNo limit60 min (HTTP)No limit
Event-driven scalingApplication Auto ScalingHTTP-basedKEDA (any event source)

When to Graduate to Kubernetes

These managed platforms handle 80% of container workloads well. Consider Kubernetes when you need: custom networking (service mesh, network policies), stateful workloads with complex scheduling, multi-tenant isolation, workloads spanning hundreds of services with complex dependencies, or GPU scheduling at scale. If you're running fewer than 10 services and don't need these, managed container platforms save you significant operational overhead.

Cost at Different Traffic Levels

At low traffic (sporadic requests), scale-to-zero platforms (Cloud Run, Container Apps) are dramatically cheaper than ECS Fargate, which charges for always-on instances. At sustained high traffic (millions of requests/day), the per-request overhead of serverless platforms adds up, and ECS Fargate or even EC2-backed containers become more economical. The crossover point varies, but roughly: if your containers would be idle more than 50% of the time, serverless wins on cost.

Key Takeaways

  • Cloud Run, ECS Fargate, and Container Apps all run your container images without you managing servers — they differ in scaling model, billing, and ecosystem integration
  • Scale to zero eliminates cost during idle periods but introduces cold starts — use minimum instances for latency-sensitive workloads
  • Concurrency per instance is a critical tuning parameter: too low wastes money on extra instances, too high overloads each instance
  • Traffic splitting across revisions enables safe deployments — canary with 5% traffic before rolling to 100%
  • VPC/network attachment is available on all platforms but adds complexity and can increase cold start times
  • Graduate to Kubernetes only when you need its flexibility — most workloads are better served by managed container platforms

Next, we'll cover Kubernetes itself — the orchestrator that won, and the complexity it brings.

Enjoyed this breakdown?

Get new lessons in your inbox.