Deploy Containers with Amazon ECS Fargate
Deploy containers with Amazon ECS Fargate — AWS Cloud & DevOps with Python tutorial. Hands-on steps, troubleshooting, and what to study next.
Focus: deploy containers with amazon ecs fargate
You've built and tested a Python microservice in a container on your laptop, but now it's time to make it run in the cloud 24/7 — and you're staring at EC2 instances, load balancers, and scaling groups, dreading the operational overhead. The pain is real: managing virtual machines just to run containers means patching OS packages, babysitting Auto Scaling groups, and paying for idle compute. That's where Amazon ECS Fargate shines — AWS Fargate is a serverless compute engine for containers that lets you focus on your Python code, not on the servers underneath. In this lesson, you'll learn how to deploy containers with Amazon ECS Fargate using the AWS CLI and Python, turning a messy infrastructure problem into a clean, reproducible deployment process.
The problem this lesson solves
Before Fargate, running containers on AWS typically meant provisioning an EC2 instance, installing Docker and an ECS agent, joining an ECS cluster, and then managing the instance's lifecycle yourself. The problems compounded quickly: you had to worry about OS security patches, disk space, network configuration, and scaling the instance fleet — all while your Python app's dependency graph was the last thing on your mind.
The core pain points developers and DevOps engineers hit without Fargate include:
- Undifferentiated heavy lifting: You spend more time on server patches and AMI updates than on the Python application itself.
- Over-provisioning cost: To handle spikes, you keep extra EC2 capacity idle, flushing money down the drain.
- Scaling complexity: Tuning scaling policies for a group of instances, not just your containers, is brittle and time-consuming.
- Inconsistent environments: The container that passes tests locally might behave differently in production because the host OS or Docker version differs.
Amazon ECS Fargate solves all of this by abstracting away the servers entirely. You define your service and task, and AWS runs your containers on a shared, managed infrastructure — you don't see or manage a single virtual machine. You pay only for the vCPU and memory your task actually consumes, and scaling becomes a matter of adjusting the desired task count, not adding servers.
Pro tip: Fargate isn't a separate orchestrator — it's a compute launch type for Amazon ECS (and later you'll see it in Kubernetes conversations), meaning you use the same ECS APIs you already know, but you skip the EC2 management layer.
Core concept / mental model
Think of ECS Fargate as a task restaurant. You — the DevOps chef — hand the kitchen (Fargate) a recipe called a task definition. The kitchen doesn't care about the stove brand or the pans; it only cares about the recipe's ingredients and requirements (CPU, memory, image, ports). Each time the kitchen runs your recipe, it produces a task — a running container with your Python app inside. If your app needs to always be running, you ask the restaurant to keep making the dish continuously — that's a service.
Let's map the terms you'll need:
- Task definition: A JSON document that describes your container — the Docker image, port mappings, CPU and memory limits, environment variables, and startup commands. It's the blueprint.
- Task: A single instantiation of a task definition — one running container or a set of containers.
- Service: A scheduler that keeps a desired number of tasks running, restarts failed ones, and can integrate with a load balancer.
- Cluster: A logical grouping of tasks and services — in Fargate, it's just a namespace; you don't provision any instances.
- Container agent: On Fargate, AWS runs it for you — you never interact with it.
A helpful analogy: imagine your Python app is a pizza recipe. The task definition is the written recipe with exact ingredients (image). The Fargate kitchen bakes a pizza (task) each time you order. A service is like a subscription that ensures at least one pizza is always hot and ready, and if one gets eaten (crashes), the kitchen immediately bakes a new one.
Thus, the mental model is: write a task definition → register it with ECS → tell ECS to run it as a service → let Fargate handle placement, scaling, and restarts.
How it works step by step
To deploy containers with Amazon ECS Fargate, you'll typically follow this sequence:
- Build and push your Docker image to Amazon ECR (Elastic Container Registry) or any public registry. Fargate pulls from here.
- Create an ECS cluster (Fargate type) to organize your services. You can do this once and reuse it.
- Define the task definition — specify the container image, ports, environment variables, and required CPU/memory. This JSON is the heart of the deployment.
- Register the task definition with ECS using the API or CLI; store it under a name and revision number (e.g.,
my-app:1). - Create a service that references the task definition and sets the desired count (e.g., 2 tasks). The service ensures tasks stay at that count.
- Let Fargate provision the resources — it finds suitable capacity in your account, launches the containers, and attaches networking (VPC, subnets, security groups).
- Monitor and scale — update the desired count or set auto-scaling policies based on CPU/memory or custom metrics.
A cause-and-effect chain is important: if you define CPU as 0.25 vCPU and memory as 512 MB, Fargate uses those to decide how many tasks fit into available capacity, and if you set security groups too restrictively, your app won't receive traffic — a common post-deployment surprise.
Let's walk through a concrete example with the AWS CLI.
Hands-on walkthrough
You'll need: an AWS account, the AWS CLI configured, and a simple Python Flask app in a container. We'll deploy a minimal "hello-world" service to see the full path.
1. Define your task definition
Create a file task-definition.json that describes your container:
{
"family": "flask-hello",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "web",
"image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/flask-hello:latest",
"portMappings": [
{
"containerPort": 80,
"protocol": "tcp"
}
],
"environment": [
{ "name": "APP_ENV", "value": "production" }
]
}
]
}
requiresCompatibilitiesmust includeFARGATE.networkModefor Fargate is alwaysawsvpc, giving each task its own ENI and IP.- The execution role allows ECS to pull the image and send logs to CloudWatch.
2. Register the task definition
aws ecs register-task-definition --cli-input-json file://task-definition.json
This returns a task definition ARN like flask-hello:1. Each registration creates a new revision — change your code, register revision 2, and roll out with zero downtime if your service supports rolling deployments.
3. Create the ECS cluster (if none exists)
aws ecs create-cluster --cluster-name my-fargate-cluster
Fargate clusters require no nodes, so this creates an empty logical space. Confirm with:
aws ecs list-clusters
4. Create a service
Save as service.json:
{
"cluster": "my-fargate-cluster",
"serviceName": "flask-hello-service",
"taskDefinition": "flask-hello",
"desiredCount": 2,
"launchType": "FARGATE",
"networkConfiguration": {
"awsvpcConfiguration": {
"subnets": ["subnet-abc123", "subnet-def456"],
"securityGroups": ["sg-789xyz"],
"assignPublicIp": "ENABLED"
}
}
}
Then run:
aws ecs create-service --cli-input-json file://service.json
Within a minute, Fargate starts two tasks. Check status with:
aws ecs list-tasks --cluster my-fargate-cluster
aws ecs describe-tasks --cluster my-fargate-cluster --tasks <task-id>
Look for lastStatus: RUNNING and healthStatus: HEALTHY.
5. Update your app with a Python script
Automate the deployment loop with boto3 — the AWS SDK for Python:
import boto3
ecs = boto3.client('ecs')
def update_service(cluster, service, task_definition):
resp = ecs.update_service(
cluster=cluster,
service=service,
taskDefinition=task_definition,
forceNewDeployment=True
)
print('Service updated:', resp['service']['status'])
if __name__ == '__main__':
update_service(
cluster='my-fargate-cluster',
service='flask-hello-service',
task_definition='flask-hello:2' # new revision
)
Run it and observe status: ACTIVE. This script is your CI/CD hook for rolling updates.
Compare options / when to choose what
When containerizing on AWS, you have several paths. Here's a quick comparison with Fargate in mind:
| Option | Management overhead | Scaling granularity | Cost model | Best for |
|---|---|---|---|---|
| ECS on EC2 | High — manage instances | Instance-level groups | Pay for EC2 capacity (idle time bills) | Heavy, steady workloads needing custom hosts |
| ECS Fargate | Low — no servers | Per-task (container) | Pay per vCPU/memory used by tasks | Most teams, especially Python microservices |
| EKS (Kubernetes) | High — control plane and nodes | Pod-level | Cluster fees + node compute | Standardized Kubernetes ecosystem |
| Lambda (containers) | Very low | Function-level | Pay per request/duration | Event-driven, short-lived tasks |
When to choose Fargate: You want to run long-lived web services (APIs, workers) without managing servers, need automatic restarts, and want the simplicity of defining your recipe as a task definition. It's the sweet spot for microservices.
When to avoid Fargate: You need GPU instances, custom host kernels, or direct docker socket access — then EC2 or EKS might be necessary. Also, for very predictable, high-utilization workloads, reserved EC2 instances may be cheaper — but the trade-off is operational overhead.
Variations: Some teams prefer ECS with Copilot (an AWS CLI tool) to generate task definitions and infrastructure from simple manifests, or Terraform to provision clusters and task definitions as infrastructure as code. You can also use App Runner, which sits atop a container and handles deployments even more abstractly — but it offers less control over networking and scaling.
Troubleshooting & edge cases
Even with Fargate, things go wrong. Here are the most common issues and how to fix them:
- Service creation fails with
InvalidParameterException— usually a malformed task definition ARN or missing required permissions for the execution role. Double-check the IAM role hasecr:GetAuthorizationToken,ecr:BatchGetImage, andlogs:CreateLogStream. - Task stays in
PENDINGforever — check your IAM execution role trustsecs-tasks.amazonaws.com, and your security group allows outbound traffic to pull the image (port 443). - Task stops immediately with
EssentialContainerInExitedState— your container exits because it's not a long-running process. For Flask, runflask run --host=0.0.0.0or use gunicorn in the Docker CMD. Useaws ecs describe-tasksand check thestopReason. - Container cannot reach the internet — ensure security group allows outbound on port 443 and 80, and that your subnet has a route to an internet gateway (NAT for private subnets).
- Can't reach your app locally — verify the security group opens the container port, you enabled
assignPublicIp, and you are hitting the public IP of the task if it has one. Resource: memory exceeded— your task definition's memory limit too low for your app? Update the task definition with a new revision and redeploy.
Always check logs in CloudWatch: if you set logConfiguration in the task definition, you'll see stdout/stderr from your Python app in a log stream.
What you learned & what's next
You now understand how to deploy containers with Amazon ECS Fargate: you learned how to define a task definition, register it, create a service, and trigger deployments using both the AWS CLI and a Python script with boto3. You can explain the mental model of tasks and services, compare Fargate with other compute options, and troubleshoot common deployment failures — hitting both learning objectives for this lesson.
Next in the AWS Cloud & DevOps with Python track, you'll learn how to push your Docker image to Amazon ECR and connect it to a CI/CD pipeline with GitHub Actions — so your Fargate deployments become a one-command or even zero-command process. That's where your deployment story becomes truly automated.
Practice recap
Now practice on your own: create a simple Flask app with a Dockerfile, push its image to Amazon ECR, and deploy it using a Fargate service in your AWS account. Use a Python script to script updates to the service, and experiment with changing the desired count to see auto-scaling in action. Once that's working, clean up by deleting the service and the cluster to avoid charges.
Common mistakes
- Forgetting the execution role IAM policy — your task stays PENDING because Fargate can't pull the image or write logs.
- Setting
networkModetobridge— Fargate requiresawsvpc; otherwise the task fails to register. - Using a container that exits immediately (like a quick script) without setting a long-running process — the task stops with
EssentialContainerInExitedState. - Not opening the security group to the container port — your app runs but is unreachable from the internet.
- Deploying a new task definition but forgetting to update the service — the old revision keeps running until you point the service to the new one.
Variations
- Use AWS Copilot CLI — it generates ECS services, task definitions, and networking from a simple
copilot initflow, reducing manual JSON setup. - Adopt Terraform or AWS CloudFormation — define the cluster, task definition, and service as infrastructure as code, enabling reproducibility and code reviews.
- Explore AWS App Runner — an even more managed service that from source or container image handles scaling and TLS automatically, with less control over VPC placement.
Real-world use cases
- Run a Python Flask web API behind an Application Load Balancer, using a Fargate service with min=2 and max=10 for auto-scaling on CPU.
- Schedule a cron-like Python worker with a Fargate task and no service — using
aws ecs run-taskonce a day via EventBridge. - Deploy a Django-based content management system in a Fargate service with a managed database and Redis, enabling zero-ops scaling for marketing blogs.
Key takeaways
- Fargate lets you run containers without managing servers — you only define a task and a service, and AWS handles placement.
- A task definition is the blueprint: image, CPU/memory, ports, env vars; each registration creates a new revision.
- A service keeps your desired number of tasks running, restarts failures, and enables rolling updates with zero downtime.
- Always use the
awsvpcnetworking mode and provide a correct IAM execution role with ECR and CloudWatch permissions. - Automate deployments with Python and boto3 by calling
update_servicewith the new task definition revision — perfect for CI/CD. - Compare Fargate to EC2, EKS, and Lambda based on operational overhead, scaling granularity, and cost model to pick the right tool.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.