Deploying to Amazon ECS
Complete the full lesson to earn 25 points
Work through each section, then tap “Mark as Complete” on the last one.
✦ Skip the page breaks and see fewer ads — read each lesson on a single page with Pro
Lesson: Deploying to Amazon Elastic Container Service (ECS)
Introduction: The Evolution of Deployment
In the modern software development lifecycle (SDLC), the ability to deploy applications consistently and reliably is not just a technical advantage—it is a business necessity. Amazon Elastic Container Service (ECS) serves as the backbone for many organizations running containerized workloads on AWS. Unlike managing raw virtual machines or dealing with the complexity of Kubernetes, ECS provides a managed container orchestration service that simplifies the process of running, stopping, and managing containers on a cluster.
Understanding how to deploy to ECS is fundamental for any engineer looking to bridge the gap between "it works on my machine" and "it works in production." When we deploy to ECS, we are essentially defining the desired state of our infrastructure—how many copies of a service should run, what resources they need, and how they should be updated. This lesson explores the architecture of ECS, the mechanics of deployment, and the strategies you must employ to ensure your services remain available and performant during transitions.
Understanding the ECS Architecture
To deploy effectively, you must first understand the components that make up an ECS environment. ECS is not a single tool but a collection of resources that work together to host your applications.
The Cluster
A cluster is a logical grouping of tasks or services. You can think of it as a sandbox where your containers live. Clusters do not inherently cost money; they are simply organizational units. You can have a single cluster for your entire organization, or you can segment them by environment (e.g., Development, Staging, Production).
Task Definitions
The Task Definition is the blueprint for your application. It is a JSON-formatted file that tells ECS which Docker image to use, how much CPU and memory to allocate, which ports to open, and what environment variables the container needs. Without a task definition, ECS has no instructions on how to instantiate your application.
Services
While a task is a single instance of a container, a Service is the manager that keeps a specified number of tasks running. If a task crashes or a container exits, the Service scheduler detects this state mismatch and automatically launches a new task to maintain the desired count. Services also handle integration with load balancers, ensuring that incoming traffic is routed to healthy containers.
Callout: Task vs. Service A Task is a single running instance of your Docker container. Think of it as a process. A Service is the supervisor. If you want to run three web servers, you create a Service with a desired count of three. If one web server fails, the Service restarts it. If you only run a task manually, it will not restart automatically if it fails.
Choosing a Launch Type: Fargate vs. EC2
When you deploy to ECS, you must choose how your containers are hosted. AWS offers two primary launch types: Fargate and EC2.
AWS Fargate
Fargate is a serverless compute engine for containers. You do not manage the underlying servers. You specify the CPU and memory requirements in your task definition, and AWS handles the provisioning of the infrastructure. This is generally the preferred choice for most teams because it removes the operational burden of patching and scaling virtual machines.
EC2 Launch Type
In the EC2 launch type, you are responsible for managing the cluster of EC2 instances. You must handle OS patching, security updates, and capacity planning. This option provides more granular control over the underlying hardware, which may be necessary for specialized workloads requiring specific GPU configurations or custom kernel parameters.
| Feature | AWS Fargate | EC2 Launch Type |
|---|---|---|
| Management | Serverless (AWS managed) | Manual (User managed) |
| Scaling | Automatic per task | Requires Cluster Auto Scaling |
| Cost Model | Pay per vCPU/GB per hour | Pay for underlying EC2 instances |
| Control | Standardized | Full root access to hosts |
Preparing Your Application for Deployment
Before you can deploy, your application must be container-ready. This means it should follow the principles of the "Twelve-Factor App," specifically regarding configuration and process management.
Containerizing the Application
Your application must be packaged as a Docker image and pushed to a registry, such as the Amazon Elastic Container Registry (ECR). ECS cannot "see" your source code; it only sees the container image in the registry.
- Create a
Dockerfilein the root of your project. - Build the image locally using
docker build -t my-app:latest .. - Tag the image for your ECR repository.
- Push the image to ECR using the AWS CLI:
docker push <aws-account-id>.dkr.ecr.<region>.amazonaws.com/my-app:latest.
Environment Configuration
Never hardcode secrets like database passwords or API keys in your Dockerfile. Instead, use environment variables. In your ECS Task Definition, you can reference these variables directly or pull them from AWS Systems Manager Parameter Store or AWS Secrets Manager. This approach ensures that your code remains portable and your sensitive information stays secure.
The Deployment Process: Step-by-Step
Deploying to ECS involves updating the Task Definition and then updating the Service to use the new version.
Step 1: Updating the Task Definition
You should treat your Task Definition as code. When you want to deploy a new version of your application, you create a new revision of the Task Definition. This revision will point to the new image tag (e.g., my-app:v2 instead of my-app:v1).
Step 2: Updating the Service
Once the new Task Definition is registered, you update the Service to use this new revision. The ECS scheduler will then begin the deployment process. It will stop the old tasks and start new ones based on the deployment configuration you have defined.
Step 3: Deployment Configurations
You can control how the deployment happens using two parameters:
- Minimum Healthy Percent: This ensures a minimum number of tasks are running during a deployment. If you set this to 100%, ECS will start the new tasks before stopping the old ones.
- Maximum Percent: This limits how many tasks can run during the deployment. If you set this to 200%, ECS can double the capacity temporarily to ensure a smooth transition.
Note: A common mistake is setting the Minimum Healthy Percent to 0% during a rolling update. This will cause your service to stop all old tasks before starting new ones, resulting in downtime. Always keep this value at 100% (or at least 50% for smaller clusters) to ensure availability.
Deployment Strategies
The way you roll out changes determines the impact on your users. Choosing the right strategy is critical for high-availability systems.
Rolling Updates
This is the default strategy in ECS. The service scheduler replaces the currently running version of the service with the latest version. It is simple to implement but requires your application to handle traffic gracefully while both old and new versions exist simultaneously.
Blue/Green Deployments
For mission-critical applications, Blue/Green deployments are the industry standard. This involves using AWS CodeDeploy to manage the transition. You maintain two identical environments: "Blue" (the current live version) and "Green" (the new version). CodeDeploy routes traffic to the Green environment only after health checks pass. If something goes wrong, you can instantly shift traffic back to the Blue environment.
Canary Deployments
Canary deployments are a variation of Blue/Green where you shift traffic in increments. For example, you might send 10% of your traffic to the new version, monitor error rates, and then gradually increase the traffic until the new version handles 100% of the load. This minimizes the "blast radius" of a failed deployment.
Best Practices for ECS Deployments
To ensure your deployments are successful and predictable, follow these established industry practices.
1. Use Immutable Infrastructure
Never modify a running container. If you need to change a configuration, build a new image, create a new Task Definition revision, and deploy it. This prevents "configuration drift" where servers end up in states that are difficult to replicate or troubleshoot.
2. Implement Health Checks
ECS relies on health checks to determine if a task is ready to receive traffic. Ensure your application has a dedicated /health or /status endpoint. Configure this in your Load Balancer target group and your Task Definition. If the health check fails, ECS will automatically kill the unhealthy task and start a new one.
3. Graceful Shutdowns
When ECS stops a task, it sends a SIGTERM signal to the container. Your application should be designed to catch this signal, finish processing existing requests, close database connections, and exit cleanly. If your application does not handle SIGTERM, ECS will force-kill the container after a timeout, potentially leading to data corruption or dropped requests.
4. Centralized Logging
Containers are ephemeral; when they stop, their local logs disappear. Always configure the awslogs log driver in your Task Definition to send logs to Amazon CloudWatch. This allows you to inspect logs even after a container has been decommissioned.
5. IAM Roles and Least Privilege
Every task should have its own IAM execution role. Do not use the same role for all services. Grant the task only the permissions it needs—for example, if a service only needs to read from an S3 bucket, do not give it broad access to the entire AWS account.
Warning: Avoid using the default "EC2 Instance Profile" for your task permissions. While it might seem convenient, it grants the container the same permissions as the underlying host, which creates a significant security risk. Always use "Task Role" for application permissions and "Execution Role" for infrastructure-level permissions (like pulling images from ECR).
Handling Common Pitfalls
Even with a solid strategy, issues can arise. Understanding how to troubleshoot these common scenarios will save you hours of debugging.
The "Crash Loop"
If your task starts and immediately stops, check the ECS console for "Stopped Reasons." This is often caused by an incorrect environment variable, a failure to connect to a database, or insufficient memory. Use CloudWatch Logs to view the stderr output of your application during startup.
Load Balancer Timeouts
If your application takes a long time to start, the Load Balancer might mark it as unhealthy and stop traffic before the application is ready. Adjust the "Deregistration Delay" and "Health Check Interval" in your Target Group settings to provide enough buffer for your application to initialize.
Resource Exhaustion
If your tasks are frequently being killed, check if you have hit the memory limits defined in the Task Definition. If your application has a memory leak, the container will be killed by the kernel (OOMKilled). Monitor these metrics in CloudWatch to determine if you need to increase the memory allocation.
Comparison: Deployment Tools
While you can use the AWS CLI to deploy, most teams use automation tools to manage the process.
| Tool | Approach | Best For |
|---|---|---|
| AWS CLI | Manual/Scripted | Small projects, learning |
| AWS CodePipeline | CI/CD Service | End-to-end automation |
| Terraform/CDK | Infrastructure as Code | Managing complex environments |
| GitHub Actions | CI/CD Pipeline | Teams using GitHub for source control |
Example: Deploying via GitHub Actions
Using a CI/CD pipeline is the professional standard. Here is a simplified workflow configuration for deploying an ECS service using GitHub Actions:
# .github/workflows/deploy.yml
name: Deploy to ECS
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Build and Push Image
run: |
docker build -t my-app .
docker tag my-app:latest <account-id>.dkr.ecr.us-east-1.amazonaws.com/my-app:latest
docker push <account-id>.dkr.ecr.us-east-1.amazonaws.com/my-app:latest
- name: Update ECS Service
run: |
aws ecs update-service --cluster my-cluster --service my-service --force-new-deployment
The --force-new-deployment flag is a powerful tool in the CLI. It tells ECS to ignore the current task definition revision and restart the service using the same image/configuration, which is useful if you have updated an image tag without changing the Task Definition itself.
Advanced Concepts: Service Discovery and Scaling
As your architecture grows, you will need to look beyond simple deployments.
Service Discovery
ECS integrates with AWS Cloud Map to provide service discovery. This allows your services to find each other by name (e.g., order-service.local) rather than needing to know IP addresses. This is vital for microservices architectures where dependencies are dynamic.
Auto Scaling
Static task counts are rarely sufficient. Configure "Service Auto Scaling" to automatically adjust the number of tasks based on CPU or memory utilization. You can set a target tracking policy, such as "keep average CPU utilization at 60%." ECS will automatically add or remove tasks to meet this target, ensuring your application remains responsive during traffic spikes.
Callout: Scaling vs. Over-provisioning Many teams over-provision their services by setting a high task count to handle potential traffic spikes. This leads to wasted costs. Instead, use Auto Scaling. It is more cost-effective and ensures you have the right amount of compute capacity exactly when you need it.
Troubleshooting Checklist
When a deployment fails, run through this mental checklist:
- Is the image in ECR? Ensure the tag you are using in the Task Definition actually exists in your registry.
- Are the IAM roles correct? Does the task have permission to access the resources it needs (e.g., S3, RDS, Parameter Store)?
- Check the Load Balancer health checks. Does the target group see the tasks as "Healthy"?
- Inspect the "Stopped" tasks. Go to the ECS console, select the cluster, click on the service, and look at the "Tasks" tab. Switch the filter to "Stopped" to see the error codes.
- Review logs. Are there application-level errors causing the container to exit immediately?
The Future of ECS Deployments
The ecosystem around ECS is continuously improving. With the introduction of AWS Copilot, deploying to ECS has become significantly easier. Copilot is an open-source command-line tool that abstracts away much of the boilerplate of creating task definitions, load balancers, and IAM roles. If you are starting a new project, exploring Copilot can save significant time and ensure your environment follows AWS best practices by default.
Furthermore, the rise of "GitOps" patterns—where the state of your infrastructure is defined in a Git repository and automatically reconciled by a controller—is becoming common in the ECS world. Tools like Flux or custom Lambda-based reconcilers are increasingly used to ensure that the production environment always matches the configuration stored in version control.
Key Takeaways
- Task Definitions are Blueprints: Your Task Definition is the core of your deployment. Treat it as code, version it, and never modify it manually in the console.
- Availability is Priority: Always use a rolling update strategy with a Minimum Healthy Percent of at least 100% to avoid downtime during deployments.
- Leverage Fargate: Unless you have specific hardware or kernel requirements, use Fargate to reduce your operational overhead and simplify scaling.
- Automate Everything: Manual deployments are prone to human error. Use CI/CD pipelines (GitHub Actions, CodePipeline) to ensure consistency across environments.
- Observe and Monitor: Use
awslogsto stream logs to CloudWatch and set up alarms for service health. You cannot fix what you cannot see. - Design for Termination: Ensure your applications handle
SIGTERMgracefully so they can clean up connections and finish requests before the container shuts down. - Security First: Always use dedicated Task Roles and follow the principle of least privilege. Never share roles between services or use the host-level EC2 instance profile for container permissions.
By mastering these concepts, you transition from simply "launching containers" to building a resilient, scalable, and automated deployment pipeline. The goal is to make deployments boring—an everyday, low-risk event that allows your team to focus on shipping features rather than fighting infrastructure fires.
Continue the course
Enjoying the courses?
Everything stays free. Pro shows fewer ads, doubles your daily points limit so you progress twice as fast, and lets you read each lesson on one page.
- ✓ Fewer advertisements
- ✓ 2× daily points limit
- ✓ Distraction-free lessons