
August 17, 2026
10 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
You want to run Docker containers in AWS without managing EC2 instances, patching operating systems, or debugging cluster capacity. The standard answer is to deploy containers on Amazon ECS with Fargate, but most tutorials stop at "hello world" and ignore the networking, IAM, and cost realities of production workloads. This guide walks through the exact configuration I use for client applications, focusing on the decisions that actually determine whether your deployment survives traffic spikes and stays within budget.
Before touching the AWS console, understand that Fargate is a serverless compute engine for containers. You specify CPU and memory; AWS provisions the underlying infrastructure. This abstraction removes operational overhead but introduces new constraints around cold starts, networking latency, and granular billing. For teams transitioning from traditional VPS or shared hosting environments, the shift requires rethinking how you approach cloud hosting versus shared infrastructure. The mental model changes from "provision a server and deploy code" to "define a task and let the platform schedule it."
How do you configure an ECS task definition for Fargate?
The task definition is the blueprint for your container. It specifies the Docker image, resource allocation, environment variables, logging configuration, and IAM permissions. Getting this wrong leads to either wasted spend (over-provisioning) or runtime failures (OOM kills, permission errors).
Resource allocation and compatibility
Fargate supports specific CPU/memory combinations. You cannot arbitrarily assign resources. Valid configurations include 0.25 vCPU with 0.5/1/2 GB, 0.5 vCPU with 1-4 GB, 1 vCPU with 2-8 GB, and so on up to 16 vCPU / 120 GB. In practice, most web applications start at 0.5 vCPU / 1 GB and scale horizontally rather than vertically.
{
"family": "web-app-task",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::123456789012:role/webAppTaskRole",
"containerDefinitions": [
{
"name": "web-app",
"image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/web-app:v1.2.0",
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/web-app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
},
"environment": [
{ "name": "APP_ENV", "value": "production" },
{ "name": "DB_HOST", "value": "db.cluster-xyz.us-east-1.rds.amazonaws.com" }
],
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/db-creds:password::"
}
]
}
]
} Key points often missed in documentation:
- Network mode must be
awsvpcfor Fargate. Each task gets its own ENI and private IP. - Separate execution role from task role. The execution role allows ECS to pull images and write logs. The task role grants your application access to S3, DynamoDB, Secrets Manager, etc. Never combine these.
- Use Secrets Manager or Parameter Store for sensitive values. Never hardcode credentials in environment variables. The
secretsblock injects them at runtime without exposing them in the task definition JSON. - Pinning image tags matters. Use immutable tags (git SHA, semantic version) instead of
latest. Fargate caches layers; mutable tags cause unpredictable deployments.
What VPC networking setup does Fargate require?
Fargate tasks run inside your VPC. They do not get public IPs by default, even if you place them in a public subnet. This is the most common source of deployment failures for engineers new to ECS.
Private subnets with NAT Gateway
For production workloads, place Fargate tasks in private subnets across at least two availability zones. Tasks need outbound internet access to pull container images from ECR, reach external APIs, or download dependencies. A NAT Gateway in each AZ provides this egress path.
# Example Terraform snippet for Fargate-friendly VPC
resource "aws_subnet" "private_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
tags = { Name = "fargate-private-a" }
}
resource "aws_subnet" "private_b" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "us-east-1b"
tags = { Name = "fargate-private-b" }
}
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public_a.id
tags = { Name = "fargate-nat" }
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
} Security groups and ALB integration
Each Fargate task attaches to a security group. If fronted by an Application Load Balancer, the ALB's security group must allow inbound traffic to the task's container port. The task's security group should only accept traffic from the ALB, not directly from the internet.
A frequent mistake is opening port 8080 to 0.0.0.0/0 "for testing" and forgetting to restrict it later. Always scope ingress to the ALB security group ID. For backend services communicating internally, use separate security groups with explicit rules rather than broad CIDR allowances.
VPC Endpoints for cost reduction
NAT Gateways charge per GB processed. If your tasks pull large images frequently or communicate heavily with S3/DynamoDB/ECR, add VPC endpoints. An S3 gateway endpoint is free and eliminates NAT charges for S3 traffic. ECR and Secrets Manager interface endpoints cost hourly but can save significant bandwidth fees at scale. On a recent legal-tech portal handling document uploads, adding S3 and ECR endpoints reduced monthly NAT costs from ~$95 to ~$32.
How do you manage costs when running Fargate in production?
Fargate pricing is straightforward: you pay per second for the vCPU and memory your task uses while running. But "straightforward" doesn't mean cheap. Without deliberate optimization, Fargate bills surprise teams accustomed to fixed-cost VPS pricing. Understanding cloud cost structures helps set realistic expectations before committing to serverless containers.
| Strategy | Savings Potential | Trade-off | Best For |
|---|---|---|---|
| Fargate Spot | Up to 70% | Tasks may be interrupted with 2-minute warning | Batch jobs, dev/staging, fault-tolerant workers |
| Compute Savings Plans | Up to 50% | 1- or 3-year commitment | Stable baseline production workloads |
| Right-sizing tasks | 20–40% | Requires load testing and monitoring | All workloads after initial deployment |
| VPC Endpoints | $50–200/mo on NAT | Endpoint hourly cost + complexity | High-bandwidth S3/ECR/DynamoDB usage |
| Auto-scaling to zero | 100% during idle | Cold start latency on first request | Low-traffic internal tools, staging |
Fargate Spot implementation
Fargate Spot uses spare AWS capacity at steep discounts. Enable it by setting the capacity provider strategy on your service:
capacity_provider_strategy {
capacity_provider = "FARGATE_SPOT"
weight = 4
}
capacity_provider_strategy {
capacity_provider = "FARGATE"
weight = 1
} This configuration runs ~80% of tasks on Spot and 20% on-demand as a safety net. Your application must handle SIGTERM gracefully and complete shutdown within 120 seconds. For Laravel queue workers or background processors, this works naturally since jobs are idempotent. For stateful HTTP handlers behind an ALB, ensure connection draining is configured and health checks pass quickly after startup.
Right-sizing with CloudWatch Container Insights
Most developers over-provision initially. After two weeks of runtime, review Container Insights metrics for actual CPU/memory utilization. If peak usage stays below 40%, drop to the next tier. A task running at 1 vCPU / 2 GB that only needs 0.5 vCPU / 1 GB wastes ~$30/month per instance. Across five replicas, that's Rs 22,500 (~$170) annually—enough to fund meaningful feature development.
When should you choose Fargate over EC2 or Lambda?
Fargate occupies a middle ground. It's not always the right choice. Understanding the trade-offs prevents costly architectural missteps, especially when evaluating options alongside serverless alternatives like Lambda or traditional EC2 deployments.
Fargate vs EC2 ECS
Choose EC2 when you have predictable, steady-state workloads that fully utilize instance capacity. If you're running eight tasks that collectively consume 14 vCPU / 28 GB continuously, a single c6g.4xlarge instance (~$0.54/hr) undercuts equivalent Fargate spend by 40-60%. EC2 also supports GPU instances, local NVMe storage, and custom kernel modules—none available on Fargate.
Choose Fargate when workload is variable, team size is small, or operational simplicity outweighs raw cost efficiency. For agencies or freelancers managing multiple client projects, the zero-maintenance aspect justifies the premium. I've migrated several Nepal-based legal-tech portals to Fargate specifically because clients lacked in-house DevOps staff to manage EC2 patching and scaling.
Fargate vs Lambda
Lambda excels for event-driven, short-lived executions under 15 minutes with bursty traffic. Fargate wins for long-running processes, consistent low-latency HTTP serving, or applications requiring custom runtime dependencies Lambda struggles with. If your Laravel app serves synchronous web requests with 200ms p99 latency targets, Fargate provides more predictable performance than Lambda's cold-start variability. For async queue processing triggered by S3 uploads or SQS messages, Lambda often costs less and scales faster.
How do you implement CI/CD for Fargate deployments?
Automated deployments prevent configuration drift and enable rapid rollbacks. The pattern I use across client projects combines GitLab CI with AWS CLI updates, avoiding heavy CDK/Terraform cycles for simple image bumps. Teams exploring CI/CD pipeline setups will find this approach balances automation with operational transparency.
# .gitlab-ci.yml excerpt for Fargate deployment
deploy-production:
stage: deploy
image: amazon/aws-cli:2.15.0
script:
- aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
- docker build -t web-app:${CI_COMMIT_SHORT_SHA} .
- docker tag web-app:${CI_COMMIT_SHORT_SHA} 123456789012.dkr.ecr.us-east-1.amazonaws.com/web-app:${CI_COMMIT_SHORT_SHA}
- docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/web-app:${CI_COMMIT_SHORT_SHA}
- aws ecs register-task-definition --cli-input-json file://task-def.json --query 'taskDefinition.taskDefinitionArn' --output text > new-task-arn.txt
- aws ecs update-service --cluster prod-cluster --service web-app-service --task-definition $(cat new-task-arn.txt) --force-new-deployment
only:
- main Critical details:
- Force new deployment ensures ECS replaces running tasks even if the task definition ARN hasn't changed (e.g., same image tag reused accidentally).
- Rollback is one command:
aws ecs update-service --task-definition PREVIOUS_ARN. Keep the last three ARNs in SSM Parameter Store for quick recovery. - Blue/green isn't necessary for most apps. ECS rolling updates with proper health checks and deregistration delay achieve zero-downtime without CodeDeploy complexity.
- Build assets before containerization. For Laravel/Vue apps, compile frontend assets in CI and commit artifacts. Don't run Node inside production containers—it bloats images and slows deploys.
Deploy Containers on Amazon ECS with Fargate: Next Steps
Start with a single service in a properly networked VPC. Resist the urge to over-engineer multi-cluster or multi-region architectures until traffic demands it. Monitor costs weekly for the first month; Fargate's pay-per-second model rewards attention. Right-size aggressively, adopt Spot for tolerant workloads, and automate deployments early. If you're evaluating whether Fargate fits your project's infrastructure needs or need help migrating an existing application, reach out to discuss your specific requirements.

