Laptop to Production

ECS on Fargate

Running containers with no servers to manage.

Time
60 minutes
Level
Intermediate

Part 1: The problem

QuickCart, launch month

QuickCart’s order service runs in a container on one EC2 server. Leo started it with docker run, and it worked.

At 3 a.m. on a Saturday, the container ran out of memory and stopped. Nothing started it again. Orders failed until Noor checked her phone at 8.

On Monday, releasing the fix meant Leo logging in to the server, stopping the old container and starting the new one. For about a minute, there was no container at all. Customers saw errors.

QuickCart needs something that keeps a set number of copies running, spreads them across zones, replaces any that die, and connects them to the load balancer. That’s a container orchestrator. On AWS, the simplest one is ECS.

Part 2: The idea

ECS, Elastic Container Service, runs containers for you. You describe the container once, say how many copies you want, and ECS keeps that many running.

What an orchestrator does

JobWithout oneWith ECS
Put a container somewhere to runYou pick a server and run docker runECS finds room for it
Keep it runningNobody notices when it stopsA stopped copy is replaced automatically
Run several copies across zonesYou start each one by handYou set a number; ECS spreads them
Connect copies to the load balancerYou register each serverECS registers and removes them
Release a new versionStop old, start new, by handECS replaces copies one batch at a time

The four ECS words

WordWhat it isThe EC2 version you already know
ClusterA named group for your services. On Fargate it holds no servers—
Task definitionThe recipe: which image, how much CPU and memory, which port, where logs goA launch template
TaskOne running copy of a task definitionOne EC2 instance
ServiceKeeps a set number of tasks running, and connects them to a load balancerAn Auto Scaling group

Every change to a task definition makes a new numbered revision, like order-service:1, order-service:2. Old revisions stay exactly as they were, so going back is just choosing an earlier number.

Fargate: no servers of your own

ECS can run tasks in two ways:

FargateEC2
ServersNone that you see. AWS runs themEC2 instances in your account
Patching serversAWS does itYou do it
You pay forThe CPU and memory each task asks for, while it runsEvery instance, busy or not
Cheapest whenLoad is small, or goes up and downLoad is large and steady

QuickCart uses Fargate. It costs a little more per hour of computing, but there is no server to patch, resize or replace. Auto scaling showed how much work servers are.

The task definition, every field explained

This is QuickCart’s task definition as JSON. The console builds exactly this from its form, and you can also paste it in directly, which you’ll do in the lab.

order-service-task.json
{
  "family": "order-service",
  "requiresCompatibilities": ["FARGATE"],
  "networkMode": "awsvpc",
  "cpu": "256",
  "memory": "512",
  "runtimePlatform": {
    "operatingSystemFamily": "LINUX",
    "cpuArchitecture": "X86_64"
  },
  "executionRoleArn": "arn:aws:iam::111122223333:role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {
      "name": "app",
      "image": "111122223333.dkr.ecr.us-east-1.amazonaws.com/quickcart-orders:v1",
      "essential": true,
      "portMappings": [
        { "containerPort": 8080, "protocol": "tcp" }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/order-service",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ]
}
FieldWhat it doesIf you leave it outWhen you’d change it
familyThe task definition’s name. Each save adds a revision to this familyRequiredNever; a new name is a new task definition
requiresCompatibilitiesCheck that this recipe can run on FargateECS doesn’t check Fargate’s rules until you try to run itTo EC2 if you run on your own instances
networkModeawsvpc: each task gets its own network card and private IP in your subnetRequired on Fargate — it’s the only mode Fargate supportsOnly on the EC2 launch type
cpuCPU for the whole task, in units where 1024 is one vCPU. 256 is a quarterRequired on FargateWhen the app needs more. Load tests tell you
memoryMemory for the whole task, in MB. Must be a size Fargate allows for that CPURequired on FargateWith cpu. Too little and the task is killed for running out of memory
runtimePlatformLinux on Intel/AMD processors — matches --platform linux/amd64 from the buildDefaults to Linux on X86_64To ARM64 if you build ARM images. Fargate on ARM costs less
executionRoleArnThe IAM role ECS uses before your app starts: pull the image from ECR, send logsECS can’t pull from a private ECR repository or send logs, so the task fails to startOnly to point at a different role
taskRoleArn (not used here)The IAM role your code uses for its own AWS callsYour code has no AWS permissions. Fine here — the order service doesn’t call AWSWhen the app reads S3, a secret or a queue
containerDefinitionsThe containers in the task. Usually oneRequiredTo add a helper container, such as a log shipper
nameThe container’s name. The service uses it to know which container gets load balancer trafficRequiredRarely
imageThe full image URI to pull, with its tagRequiredFor every release: v1, then v2
essentialIf this container stops, stop the whole taskDefaults to trueSet false only for optional helper containers
portMappingsThe port the app listens on inside the container, and its protocolThe load balancer has no port to send traffic toIf the app’s port changes
logConfiguration → logDriverawslogs: send everything the app prints to CloudWatch LogsOutput is thrown away. You can’t see why a task crashedRarely
awslogs-groupThe CloudWatch log group to write to. It must exist alreadyRequired with awslogsOne group per service
awslogs-regionThe region of that log groupRequired with awslogsIf logs go to another region
awslogs-stream-prefixStart of each log stream’s name. Streams are named ecs/app/TASK_ID, so you can find one task’s outputRequired on FargateRarely

Fargate only accepts certain CPU and memory pairs. For the small sizes:

cpuAllowed memory
256 (0.25 vCPU)512, 1024, 2048
512 (0.5 vCPU)1024 to 4096, in steps of 1024
1024 (1 vCPU)2048 to 8192, in steps of 1024

Every task gets its own IP address

With awsvpc, a task is a full member of your VPC, like a small server:

  • Its security group applies to the task itself.
  • The load balancer’s target group must use IP addresses as targets, not instances. There’s no instance of yours to register.
  • Each task uses one IP in its subnet. A busy service can run a small subnet out of addresses.

Two roles, easy to mix up

RoleUsed byWhenForIf it’s wrong
Execution roleECS itselfBefore your app startsPull the image, send logs, fetch secretsThe task never starts. No app logs. The stopped reason says why
Task roleYour codeWhile your app runsAnything your code does in AWSThe task runs fine, then your app logs AccessDenied errors

The service and the load balancer

A service holds a desired count — how many tasks you want. It keeps comparing that number with what’s running and closes the gap. A task stops, a zone has a problem, a release starts: it’s always the same loop.

When a task starts, the service registers its IP with the target group. The load balancer only sends it traffic once it passes the health check on /health. When a task stops, the service removes it from the target group first.

Key point: You never start tasks yourself. You set a desired count, and the service keeps reality matching it.

Part 3: See it

Here is QuickCart’s service with a desired count of 2. Stop a task and watch the service replace it. Change the desired count. Then break the execution role and stop a task again.

Desired tasks
Execution role

Task definition

order-service:1

image quickcart-orders:v1, 0.25 vCPU, 0.5 GB, port 8080

Service order-service

Desired 2Running 2

Desired and running match. Nothing to do.

Subnet in us-east-1a

  • task 3f9a1cRUNNING172.31.8.41

Subnet in us-east-1b

  • task b27e4dRUNNING172.31.20.17

Stopped tasks (newest first)

  • None yet
ReadyTwo tasks are running, one in each zone. Choose Stop a task, or change the desired count.

What to notice: with the execution role broken, the running tasks carried on — they had pulled their image long before. Only new tasks failed, and they left no app logs at all. The only clue was the stopped reason. That’s why it’s the first place to look when a task won’t start.

Part 4: Do it

You’ll run QuickCart’s order service on Fargate: two tasks in two zones, behind a load balancer, with logs in CloudWatch. Then you’ll stop a task and watch it come back. About 45 minutes.

You need the quickcart-orders repository with the v1 image from Storing images: ECR. Check the region selector says N. Virginia (us-east-1).

To save money, this lab uses the default VPC that every account has, and gives each task a public IP, so it can pull its image from ECR without a NAT gateway. That’s safe because the task’s security group only lets the load balancer in. The routing lesson shows the stricter version, with private subnets and a NAT gateway.

  1. Check the image is in ECR

    In the AWS console: Amazon ECRPrivate registryRepositoriesquickcart-orders

    You should see

    An image tagged v1. Copy the repository URI — you’ll need it for the task definition.

  2. Create the execution role

    Many accounts already have a role called ecsTaskExecutionRole. Look for it in IAM → Roles first. If it’s there, skip to the next step.

    In the AWS console: IAMRolesCreate role

    FieldEnter
    Trusted entity typeAWS service
    Service or use caseElastic Container Service
    Use caseElastic Container Service Task

    Choose Next. Search for and tick AmazonECSTaskExecutionRolePolicy, then Next.

    FieldEnter
    Role nameecsTaskExecutionRole

    Choose Create role.

    You should see

    The role in the list. Open it and copy its ARN. It looks like arn:aws:iam::111122223333:role/ecsTaskExecutionRole, with your account ID.

    That managed policy allows exactly the execution role’s job: pulling images from ECR and writing to CloudWatch Logs.

  3. Create the log group

    In the AWS console: CloudWatchLogsLog groupsCreate log group

    FieldEnter
    Log group name/ecs/order-service
    Retention setting1 week

    Choose Create. Without a retention setting, logs are kept, and billed, forever.

  4. Create the two security groups

    In the AWS console: EC2Security GroupsCreate security group

    First, the load balancer’s:

    FieldEnter
    Security group namequickcart-alb-sg
    DescriptionQuickCart load balancer
    VPCthe default VPC
    Inbound rule typePortSource
    HTTP80Anywhere-IPv4

    Choose Create security group. Then create a second one, for the tasks:

    FieldEnter
    Security group namequickcart-app-sg
    DescriptionQuickCart order service tasks
    VPCthe default VPC
    Inbound rule typePort rangeSource
    Custom TCP8080Custom, then choose quickcart-alb-sg

    Leave outbound rules as they are on both. The tasks need to reach ECR on the way out.

    You should see

    Two security groups. quickcart-app-sg has one inbound rule, naming the load balancer’s group rather than any address. Tasks can come and go; the rule still fits.

  5. Create the target group

    In the AWS console: EC2Target groupsCreate target group

    FieldEnter
    Choose a target typeIP addresses
    Target group namequickcart-orders-tg
    Protocol : PortHTTP : 8080
    VPCthe default VPC
    Health check path/health

    Open Advanced health check settings:

    FieldEnter
    Healthy threshold2
    Unhealthy threshold2
    Interval10 seconds

    Choose Next. On the Register targets page, don’t register anything — remove any IP the page filled in. The ECS service adds its tasks itself. Choose Create target group.

    Then make it drain faster:

    In the AWS console: EC2Target groupsquickcart-orders-tgAttributesEdit

    FieldEnter
    Deregistration delay30 seconds

    Choose Save changes. The default of 300 seconds makes every stop and every release wait five minutes. Releases without downtime explains how to choose the number.

  6. Create the load balancer

    In the AWS console: EC2Load balancersCreate load balancerApplication Load BalancerCreate

    FieldEnter
    Load balancer namequickcart-alb
    SchemeInternet-facing
    Load balancer IP address typeIPv4
    VPCthe default VPC
    Availability Zones and subnetstick us-east-1a and us-east-1b
    Security groupsquickcart-alb-sg only — remove default
    ListenerHTTP : 80, default action Forward to quickcart-orders-tg

    Choose Create load balancer. It takes a few minutes to become Active. Carry on meanwhile.

  7. Create the cluster

    In the AWS console: Amazon Elastic Container ServiceClustersCreate cluster

    FieldEnter
    Cluster namequickcart
    InfrastructureAWS Fargate (serverless) only

    Choose Create.

    You should see

    The cluster quickcart, active, with 0 services and 0 tasks. Look at EC2 → Instances: nothing new. On Fargate a cluster is just a name.

  8. Create the task definition

    In the AWS console: Amazon Elastic Container ServiceTask definitionsCreate new task definitionCreate new task definition with JSON

    Replace everything in the editor with the task definition from the idea, above. Put your account ID in both highlighted places: the role ARN and the image URI.

    Choose Create.

    You should see

    order-service:1 — revision 1 of the order-service family. The JSON tab shows your definition, with a few extra fields ECS filled in with their defaults.

  9. Create the service

    In the AWS console: Amazon Elastic Container ServiceClustersquickcartServicesCreate

    The form is long, and AWS moves its sections around from time to time. Find each field by its name.

    FieldEnter
    Task definition familyorder-service
    Task definition revisionthe latest, 1
    Service nameorder-service
    Compute optionsLaunch type
    Launch typeFARGATE
    Platform versionLATEST
    Desired tasks2
    Health check grace period30 seconds

    Leave Deployment configuration on its defaults. Releases without downtime goes through every setting in it.

    Under Networking:

    FieldEnter
    VPCthe default VPC
    Subnetsonly the us-east-1a and us-east-1b subnets — remove the others
    Security groupUse an existing security group, then quickcart-app-sg only — remove default
    Public IPTurned on

    Under Load balancing:

    FieldEnter
    Use load balancingticked
    Load balancer typeApplication Load Balancer
    Containerapp 8080:8080
    Application Load BalancerUse an existing load balancer, then quickcart-alb
    ListenerUse an existing listener, then 80:HTTP
    Target groupUse an existing target group, then quickcart-orders-tg

    Choose Create.

    SettingIn plain words
    Desired tasks 2Always keep two copies running
    Health check grace period 30Give a new task 30 seconds to start before its failed health checks count against it
    Public IP onLets the task reach ECR to pull its image, without a NAT gateway
    Security group quickcart-app-sgOnly the load balancer may reach the task
    You should see

    After one to three minutes, the service shows 2/2 tasks running. On the Tasks tab, two tasks, one in each zone, each with its own private IP. In EC2 → Target groups → quickcart-orders-tg → Targets, two IP addresses, both healthy.

  10. Reach it through the load balancer

    In the AWS console: EC2Load balancersquickcart-alb

    Copy the DNS name. It looks like quickcart-alb-1234567890.us-east-1.elb.amazonaws.com.

    Terminal
    curl ALB_DNS_NAME/
    PartWhat it means
    curlfetch a web page and print it
    ALB_DNS_NAMEreplace this with the DNS name you copied. With no http:// in front, curl uses plain HTTP
    /the home page, which says which task answered
    You should see
    {"served_by":"ip-172-31-8-41.ec2.internal","service":"order-service","version":"v1"}

    Run it a few more times. served_by is the name of the task that answered — yours will differ — and it switches between your two tasks. The load balancer is sharing the traffic.

  11. Stop a task and watch it come back

    In the AWS console: Amazon Elastic Container ServiceClustersquickcartServicesorder-serviceTasks

    Tick one task, then choose Stop and Stop selected.

    You should see

    The service shows 1 running and 2 desired. Within about a minute a new task appears, goes from Provisioning to Pending to Running, and the count is back to 2. Nobody started it.

    Change the filter from running tasks to stopped tasks, and open the one you stopped. Its Stopped reason says it was stopped by a user. When a task fails for any other reason — a missing image, a missing permission — this is where ECS writes why.

  12. Read the logs

    In the AWS console: CloudWatchLogsLog groups/ecs/order-service

    Open the newest log stream. Its name is ecs/app/ followed by the task’s ID.

    The stream shows gunicorn starting
    [INFO] Starting gunicorn 23.0.0
    [INFO] Listening at: http://0.0.0.0:8080
    [INFO] Using worker: sync
    [INFO] Booting worker with pid: 7
    [INFO] Booting worker with pid: 8

    Each real line starts with a time and a process number, and yours differ. Everything the app prints ends up here, and it stays after the task is gone.

  13. Change the desired count

    In the AWS console: Amazon Elastic Container ServiceClustersquickcartServicesorder-serviceUpdate service

    FieldEnter
    Desired tasks3

    Choose Update.

    You should see

    A third task starts, and a third target appears in the target group. Change it back to 2 the same way: the service stops one task, draining it from the load balancer first.

  14. Clean-up step: Clean up

    If you’re going straight on to Releases without downtime, keep everything and clean up at the end of that lesson. Otherwise, delete it all now, in this order.

    1. The service.

    In the AWS console: Amazon Elastic Container ServiceClustersquickcartServicesorder-serviceDelete service

    If the console asks, tick Force delete (or first update the service to 0 desired tasks), then confirm. Wait until the service and its tasks are gone.

    2. The cluster.

    In the AWS console: Amazon Elastic Container ServiceClustersquickcartDelete cluster

    Type the confirmation text it asks for, and delete.

    3. The task definition. Open Task definitions → order-service, tick every revision, and choose Actions → Deregister. Then, with them still ticked, choose Actions → Delete.

    4. The load balancer. In EC2 → Load balancers, tick quickcart-alb, choose Actions → Delete load balancer, and type confirm.

    5. The target group. In EC2 → Target groups, tick quickcart-orders-tg and choose Actions → Delete.

    6. The security groups. In EC2 → Security Groups, delete quickcart-app-sg first, then quickcart-alb-sg. The app’s group refers to the load balancer’s, so it has to go first. If one won’t delete, wait a minute — a network card from a stopped task is still letting go of it.

    7. The log group. In CloudWatch → Log groups, tick /ecs/order-service, then Actions → Delete log group(s).

    8. The image repository, unless a later lesson needs it: Amazon ECR → Repositories, tick quickcart-orders, Delete, and type delete.

    The ecsTaskExecutionRole role is free to keep. Later lessons use it.

    You should see

    ECS → Clusters is empty. EC2 → Load balancers and EC2 → Target groups are empty. EC2 → Network interfaces shows nothing that mentions ECS. Nothing from this lab is still billing.

Part 5: Check yourself

A new task stops within a minute of starting, and its log stream in CloudWatch is empty. Where do you look first?

The order service needs to read a file from S3 while it runs. Which role gets that permission?

Why must the target group's target type be "IP addresses" for Fargate?

The service's desired count is 2. You stop one of its tasks by hand. What happens?

QuickCart's tasks run in public subnets with a public IP, but no NAT gateway. What stops anyone on the internet reaching port 8080 on a task directly?

In the task definition, what does cpu set to 256 mean?

Part 6: Cheat sheet

On one card
Term or settingIn one line
ClusterA named group for services. Holds no servers on Fargate
Task definitionThe recipe: image, CPU, memory, port, logs, roles. Every change is a new revision
TaskOne running copy of a task definition
ServiceKeeps the desired count of tasks running and registered with the load balancer
FargateAWS runs the servers. You pay per task, for what it asks for
awsvpcEach task gets its own network card, private IP and security group
Target typeIP addresses for Fargate
cpu 256 / memory 512A quarter of a vCPU and 512 MB
Execution roleECS’s role: pull the image, send logs, before the app starts
Task roleYour code’s role, while it runs
Task stops with no logsRead the stopped reason
Targets say “Request timed out”Security groups or port, not code
exec format errorImage built for the wrong processor. Build with --platform linux/amd64

Finished the build and the quiz? Mark it done to fill in its stop on the route map.