Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Deploy Containers on Amazon ECS with Fargate

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 awsvpc for 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 secrets block 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.
Task Definition AnatomyContainer ImageECR / Docker HubPinned Tag (v1.2.0)Resources0.5 vCPU / 1 GBFargate CompatibleIAM RolesExecution RoleTask Role (App)SecretsSecrets ManagerInjected at RuntimeLog Configuration (awslogs → CloudWatch)Stream Prefix + Region + Log GroupNetwork Mode: awsvpc (Private ENI per Task)Required for Fargate • Security Group Attachment
Core components of an ECS task definition when you deploy containers on Amazon ECS with Fargate

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.

Fargate VPC Networking TopologyPublic Subnet ANAT GatewayALB (Public Facing)Private Subnet AFargate Task 1S3 VPC EndpointPrivate Subnet BFargate Task 2ECR VPC EndpointInternet Gateway (IGW)Inbound HTTPS → ALB • Outbound via NATSecurity Group RulesALB SG → Task SG (port 8080)Task SG → Internet (via NAT)Cost OptimizationVPC Endpoints bypass NAT chargesS3 Gateway = Free • ECR Interface = $/hr
Recommended VPC topology when you deploy containers on Amazon ECS with Fargate in production

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.

StrategySavings PotentialTrade-offBest For
Fargate SpotUp to 70%Tasks may be interrupted with 2-minute warningBatch jobs, dev/staging, fault-tolerant workers
Compute Savings PlansUp to 50%1- or 3-year commitmentStable baseline production workloads
Right-sizing tasks20–40%Requires load testing and monitoringAll workloads after initial deployment
VPC Endpoints$50–200/mo on NATEndpoint hourly cost + complexityHigh-bandwidth S3/ECR/DynamoDB usage
Auto-scaling to zero100% during idleCold start latency on first requestLow-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.

Fargate Cost Optimization Decision TreeIs workload fault-tolerant?YESNOUse Fargate Spot (70% off)On-Demand + Savings PlanCan scale to zero?Stable baseline load?Auto-scale min=0 (idle savings)Commit Savings Plan (50% off)Always: Right-size + VPC Endpoints + Monitor Utilization
Decision framework for selecting cost strategies when you deploy containers on Amazon ECS with Fargate

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.

Frequently Asked Questions

Fargate is a serverless compute engine for containers that removes EC2 instance management. You specify CPU and memory per task, and AWS provisions infrastructure automatically. Unlike EC2 launch type where you manage instances, patching, and bin-packing, Fargate bills per second of actual container usage with zero host-level access or maintenance overhead.

Pricing depends on vCPU and memory configuration per task. As of 2026, us-east-1 rates are approximately $0.04048 per vCPU-hour and $0.004445 per GB-hour. A typical Laravel web task with 0.5 vCPU and 1GB RAM costs roughly $15/month (~NPR 2,000) running continuously, excluding load balancer, ECR storage, CloudWatch logs, and NAT gateway data transfer charges which often double the base compute bill.

Choose Fargate when your team lacks dedicated platform engineers and you need standard container orchestration without cluster management overhead. EKS makes sense for complex multi-cluster setups, custom operators, or workloads requiring node-level control. In my experience shipping production applications for Nepal-based businesses, Fargate reduces operational burden significantly compared to managing EKS control planes, node groups, and networking complexity for teams under five developers.

Valid configurations require specific CPU-to-memory ratios. Minimum is 0.25 vCPU with 0.5GB or 1GB RAM. Maximum is 16 vCPU with 120GB RAM. Common Laravel application tasks run at 0.5 vCPU/1GB or 1 vCPU/2GB. Memory must be integer multiples of 1GB above 1GB, or exact values at lower tiers. Invalid combinations fail deployment with validation errors during task definition registration.

Define HTTP health checks in your task definition targeting /up or a custom endpoint returning 200 OK. Set interval to 30 seconds, timeout to 5 seconds, healthy threshold to 2, unhealthy threshold to 3. Ensure your Laravel app responds quickly without database dependencies in the health route. Container-level health checks restart individual tasks; ALB target group health checks determine traffic routing. Both should be configured independently for reliable operation.

This error typically indicates VPC networking misconfiguration preventing ECR image pulls. Verify your private subnets have NAT gateway routes for internet access, or configure VPC endpoints for ecr.api, ecr.dkr, and s3 if using private subnets without NAT. Check security group egress rules allow HTTPS outbound. Confirm IAM task execution role has ecr:GetAuthorizationToken, ecr:BatchGetImage, and ecr:GetDownloadUrlForLayer permissions. Test connectivity using ecs-exec to debug network issues inside running tasks.

Use AWS Secrets Manager or Systems Manager Parameter Store referenced directly in task definitions via valueFrom field. Never embed credentials in Docker images or environment variables. Grant task execution role secretsmanager:GetSecretValue permission scoped to specific secret ARNs. For Laravel, reference DATABASE_URL, CACHE_REDIS_HOST, and API keys as secret references. Secrets resolve at task startup with no application code changes required. Rotate credentials in Secrets Manager without redeploying containers.

Fargate supports EFS volumes mounted via NFS for persistent shared storage. Configure EFS access points with proper POSIX permissions matching your container UID/GID. Mount at /var/www/html/storage for Laravel file uploads. Expect higher latency than local disk; avoid storing session files or cache on EFS. Use S3 for media assets when possible. EFS throughput modes affect performance and cost; burst mode suffices for low-traffic apps but provisioned throughput prevents throttling during peak loads.

Run separate task definitions for web and queue processes rather than combining them. Scale queue tasks independently based on SQS ApproximateNumberOfMessagesVisible metric using Application Auto Scaling. Use Redis or SQS as queue driver; avoid database queues in production. Configure supervisor or Laravel's built-in queue:work with --tries and --timeout flags. Monitor failed jobs via Horizon dashboard. Queue tasks can use smaller CPU allocations than web tasks since they process sequentially, reducing costs significantly for background job workloads.

Use awslogs driver sending stdout/stderr to CloudWatch Logs with log group per service. Set retention policies to control costs; 14 days suffices for most debugging. Structure logs as JSON for CloudWatch Insights queries. Avoid logging sensitive data. For high-volume applications, consider Firelens with Fluent Bit routing to S3 or OpenSearch to reduce CloudWatch ingestion costs. Laravel Monolog cloudwatch handler provides native integration. Always include task ID and container name in log streams for correlation during incident response.

Use CodeDeploy with ECS blue-green deployment type. Configure listener rules routing test traffic to green task set before switching production traffic. Set termination wait period allowing rollback window. Define pre-hooks validating new version health via Lambda or SNS. Post-deployment hooks can trigger cache warming or smoke tests. Rollback occurs automatically if health checks fail during bake time. This approach eliminates downtime compared to rolling updates and provides safe verification before full cutover for critical business applications.

PHP-FPM worker count and Laravel memory limits often exceed allocated task memory. Calculate required memory as base PHP process plus pm.max_children times average request memory plus buffer. Monitor with CloudWatch ContainerInsights or docker stats via ecs-exec. Reduce pm.max_children or switch to static process manager with conservative counts. Enable OPcache to reduce per-worker memory footprint. Profile with Xdebug or Blackfire identifying memory leaks. Increase task memory allocation if profiling confirms legitimate usage; OOM kills indicate configuration mismatch not application bugs necessarily.

Minimize image size using multi-stage builds and alpine base images. Pre-warm OPcache during build with php artisan optimize and composer dump-autoload -o. Use ECR pull-through caching reducing cross-region latency. Provisioned concurrency keeps warm tasks ready eliminating cold starts entirely at additional cost. Lazy-load heavy services only when needed. Cache config, routes, views, and events in Dockerfile. Cold starts typically range 5-15 seconds unoptimized; proper caching reduces to 1-3 seconds acceptable for most web workloads.

Separate task role and task execution role following least privilege. Task execution role needs ecr:GetAuthorizationToken, ecr:BatchGetImage, ecr:GetDownloadUrlForLayer, logs:CreateLogStream, logs:PutLogEvents, and secrets access. Task role grants application-level permissions like s3:GetObject, dynamodb:Query, or sqs:SendMessage specific to your workload. Never attach AdministratorAccess or broad wildcards. Use IAM Access Analyzer identifying unused permissions. Scope resource ARNs to specific buckets, queues, or secrets. Audit CloudTrail regularly detecting overprivileged tasks in production environments.

PENDING indicates insufficient resources or configuration blocking placement. Check CloudFormation or ECS service events for specific reasons. Common causes include invalid CPU/memory combinations, missing subnet availability zones, exhausted ENI capacity in subnet, or EFS mount target absence in selected AZ. Verify security groups allow required ports. Inspect VPC endpoint connectivity if using private subnets. Use describe-tasks API showing stoppedReason and container exit codes. Temporarily increase desired count triggering placement across multiple AZs revealing zone-specific constraints causing scheduling failures.

Share this article

Quick Contact Options
Choose how you want to connect me: