I Stopped Managing Servers Manually — Understanding Docker, Amazon ECR, ECS & Fargate
I had already learned how a web application can be built on AWS using EC2, Load Balancers, Auto Scaling, RDS, S3, SQS, and other services.
But there was still one question I kept coming back to:
What if I don't want to manage the server itself?
With EC2, I am still responsible for things like the operating system, installed software, runtime environment, and server configuration.
But modern applications are increasingly built around containers.
That led me to the next part of my AWS learning journey:
Docker → Amazon ECR → Amazon ECS → AWS Fargate
And once I understood how these four pieces connect, container deployment on AWS started making much more sense.
First, What Problem Are Containers Solving?
Imagine I build a Node.js application on my laptop.
It works perfectly.
Node.js version: 22
Express: installed
Dependencies: installed
Environment: configured
Application: running
Then I give the application to another developer.
Suddenly:
“It works on my machine.”
Maybe they have a different Node.js version.
Maybe a dependency is missing.
Maybe an environment variable isn't configured.
Maybe the operating system behaves differently.
This is one of the problems containers help solve.
Instead of only shipping our application code, we can package the application together with the environment it needs to run.
That's where Docker comes in.
1. Docker — Package the Application
Docker allows us to package an application into a container image.
For a Node.js backend, the image can contain:
Node.js
Application code
Dependencies
Configuration needed to run
A simplified flow looks like this:
Application Code
│
▼
Dockerfile
│
▼
Docker Image
│
▼
Container
The important distinction is:
Image = packaged blueprint
Container = running instance of that image
For example:
Docker Image
│
├── Container 1
├── Container 2
└── Container 3
This makes it much easier to run the same application consistently across different environments.
2. Dockerfile — The Instructions for the Image
A Docker image is usually created from a Dockerfile.
A simplified Node.js example could look like:
FROM node:22
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
I don't need to manually install Node.js on every server.
The Docker image already defines what the application needs.
So instead of saying:
“Please configure a server exactly like my development machine.”
I can say:
“Run this container.”
That is a much more repeatable deployment model.
3. But Where Does the Docker Image Go?
Now we have a Docker image.
But AWS needs somewhere to store it.
This is where Amazon Elastic Container Registry (ECR) comes in.
ECR is AWS's managed container image registry.
The flow becomes:
Developer
│
▼
Dockerfile
│
▼
Docker Build
│
▼
Docker Image
│
▼
Amazon ECR
Think of ECR as a private warehouse for container images.
For example, we might have:
my-backend
my-worker
my-payment-service
inside an ECR repository.
When we build a new version of the application, we can push a new image to ECR.
4. ECR Doesn't Run Our Application
This distinction is important.
ECR stores the image.
It doesn't run the application.
For example:
ECR
│
│ stores
▼
Docker Image
We still need a service that can take that image and actually run it.
That's where Amazon ECS comes in.
5. Amazon ECS — Running Containers on AWS
Amazon Elastic Container Service, or ECS, is a container orchestration service.
In simple terms:
ECS manages running containers for our application.
The architecture now starts looking like:
Code
│
▼
Docker Image
│
▼
ECR
│
▼
ECS
│
▼
Running Container
But ECS introduces a few concepts that are worth understanding.
6. ECS Cluster
An ECS Cluster is a logical grouping of resources used to run our ECS workloads.
Think of it as the environment where our services run.
For example:
Production Cluster
│
├── Backend Service
├── Worker Service
└── Payment Service
A cluster itself isn't simply “one server.”
It's more useful to think of it as a logical home for our container workloads.
7. ECS Task Definition
Now ECS needs to know how our container should run.
That's where a Task Definition comes in.
It can describe things such as:
Which Docker image?
How much CPU?
How much memory?
Which port?
Environment variables?
IAM permissions?
Logging configuration?
For example:
Task Definition
│
├── Image → ECR
├── CPU
├── Memory
├── Port
└── Environment
This is similar to giving ECS a set of instructions:
“This is the container I want you to run, and this is how it should run.”
8. ECS Service — Keeping Containers Running
A task definition tells ECS how to run a container.
But what if I want three copies of my backend running continuously?
That's where an ECS Service comes in.
For example:
ECS Service
│
├── Task 1
├── Task 2
└── Task 3
The service maintains the desired number of tasks.
If a running task fails, ECS can replace it.
This is one of the important differences between simply running a container and having a container orchestration system manage it.
9. Where Does the Container Actually Run?
This is where AWS Fargate becomes interesting.
ECS can run containers using different compute options.
One option is managing the underlying EC2 instances yourself.
Another option is AWS Fargate.
Fargate is a serverless compute engine for containers.
With Fargate, AWS manages the underlying infrastructure required to run the containers.
So instead of thinking:
ECS
↓
EC2
↓
Operating System
↓
Container
we can use:
ECS
↓
Fargate
↓
Container
We still define the container's CPU, memory, networking, and other configuration, but we don't have to manage the underlying EC2 servers ourselves.
10. ECS + Fargate Architecture
Now our application starts looking like this:
USERS
│
▼
Route 53
│
▼
CloudFront
│
▼
ALB
│
▼
ECS Service
│
┌────────────┼────────────┐
▼ ▼ ▼
Fargate Fargate Fargate
Task Task Task
│ │ │
└────────────┼────────────┘
│
▼
RDS
The ALB distributes requests between the running ECS tasks.
If one task becomes unhealthy, the service can replace it.
If traffic increases, we can increase the desired number of tasks.
11. What Happens When We Deploy a New Version?
This is where the whole model becomes really powerful.
Suppose we update our Node.js application.
We make some changes:
Version 1
↓
Code changes
↓
Version 2
We then build a new Docker image.
Source Code
│
▼
Docker Build
│
▼
New Docker Image
│
▼
Amazon ECR
ECS can then deploy the new version.
The simplified flow becomes:
Developer
│
▼
GitHub
│
▼
Build Docker Image
│
▼
Amazon ECR
│
▼
ECS Service
│
▼
New Tasks
│
▼
Production
This is the foundation for modern container-based CI/CD pipelines.
12. What About the Database?
Containers are designed to be replaceable.
That means we generally shouldn't treat the container itself as the permanent place for important application data.
Instead:
ECS / Fargate
│
├── Application
│
▼
RDS
│
▼
Persistent Data
If a container disappears and ECS starts a new one, the application data remains in RDS.
The same principle applies to files.
Instead of storing uploaded files inside the container:
Container ❌
we can use:
S3 ✅
So:
Application
│
├── Database → RDS
├── Files → S3
├── Cache → ElastiCache
└── Jobs → SQS
This separation is extremely important when designing scalable applications.
13. Security Still Matters
Moving from EC2 to containers doesn't mean security disappears.
We still need to think about:
IAM
ECS tasks can use IAM roles to access AWS services without hardcoding AWS credentials into the application.
Security Groups
We can control network communication between:
ALB
↓
ECS
↓
RDS
Secrets Manager
Sensitive values such as database credentials and API secrets can be stored securely instead of being hardcoded into the image.
VPC
Our ECS tasks can run inside private subnets while the ALB remains publicly reachable.
So the architecture can still follow a secure pattern:
Internet
│
▼
ALB
│
▼
Private ECS Tasks
│
├── RDS
├── S3
└── Other AWS Services
14. What Happens When Traffic Increases?
Let's say our application normally runs three tasks:
ECS Service
Task 1
Task 2
Task 3
Suddenly, traffic increases.
We can configure ECS Service Auto Scaling to increase the number of running tasks.
Normal Traffic
Task 1
Task 2
Task 3
Then:
High Traffic
Task 1
Task 2
Task 3
Task 4
Task 5
Task 6
The ALB distributes traffic across the available healthy tasks.
This gives us a scalable container architecture without manually logging into servers and starting applications ourselves.
15. ECS vs EC2 — What's the Difference?
This was one of the comparisons that helped me understand the purpose of ECS.
| EC2 | ECS + Fargate |
|---|---|
| Manage virtual servers | Manage containers |
| OS management required | AWS manages underlying infrastructure with Fargate |
| Install runtime manually | Runtime packaged in container |
| Server-focused | Container-focused |
| More infrastructure control | Less infrastructure management |
| Scaling servers | Scaling container tasks |
Neither is automatically “better.”
The right choice depends on the application's requirements, team experience, control requirements, and operational needs.
16. ECS vs Lambda
Another useful comparison is ECS vs Lambda.
Lambda is excellent for event-driven, short-lived workloads where we don't want to manage servers or containers directly.
ECS is useful when we want more control over a containerized application and its runtime.
A simplified comparison:
| ECS | Lambda |
|---|---|
| Container-based | Function-based |
| Long-running services are common | Event-driven execution is common |
| More runtime control | Less infrastructure management |
| Good for containerized APIs/workers | Good for many event-driven workloads |
Again, neither service replaces the other.
The architecture should determine the choice.
17. The Complete Flow
Now we can connect everything together.
USER
│
▼
Route 53
│
▼
CloudFront
│
▼
ALB
│
▼
ECS Service
│
┌────────────┼────────────┐
▼ ▼ ▼
Fargate Fargate Fargate
Task Task Task
│ │ │
└────────────┼────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
RDS S3 ElastiCache
Database File Storage Cache
│
│
▼
SQS
│
▼
Worker Tasks
And the deployment side looks like:
Developer
│
▼
GitHub
│
▼
CI/CD Pipeline
│
▼
Docker Build
│
▼
Amazon ECR
│
▼
Amazon ECS
│
▼
Fargate
│
▼
Production
Now we have two important flows:
Application flow
User → ALB → ECS → AWS Services
Deployment flow
Code → Docker → ECR → ECS → Fargate
That distinction made the whole architecture much easier for me to understand.
The Mental Model I Keep Now
I don't think of ECS, ECR, and Fargate as three unrelated AWS services anymore.
I think of them as a chain:
Docker
│
│ Packages
▼
ECR
│
│ Stores
▼
ECS
│
│ Manages
▼
Fargate
│
│ Runs
▼
Container
In simple words:
Docker → Package my application
ECR → Store my image
ECS → Manage my containers
Fargate → Run those containers without managing the underlying servers
That mental model is much easier to remember than four separate definitions.
What Changed My Perspective?
Before learning containers, my mental model was mostly:
Code
↓
EC2
↓
Run application
Now it looks more like:
Code
↓
Docker
↓
Container Image
↓
ECR
↓
ECS
↓
Fargate
↓
Production
And when we connect that with the architecture from my previous AWS article:
Users
↓
Route 53
↓
CloudFront
↓
ALB
↓
ECS / Fargate
↓
RDS / S3 / SQS / ElastiCache
AWS starts looking less like a collection of services and more like a complete platform for running applications.
Quick Cheat Sheet
| Technology / Service | Main Job |
|---|---|
| Docker | Packages applications into containers |
| Dockerfile | Defines how an image is built |
| Docker Image | Packaged application blueprint |
| Container | Running instance of an image |
| ECR | Stores container images |
| ECS | Orchestrates containers |
| Task Definition | Defines how containers should run |
| ECS Service | Maintains desired running tasks |
| Fargate | Runs containers without managing servers |
| ALB | Distributes traffic |
| RDS | Stores relational data |
| S3 | Stores files and objects |
| SQS | Handles asynchronous jobs |
| ElastiCache | Caches frequently accessed data |
| IAM | Controls permissions |
| VPC | Provides network isolation |
Final Thoughts
The biggest shift in my AWS learning wasn't memorizing more services.
It was learning to ask:
“What problem is this service solving, and where does it fit in the architecture?”
Once I started thinking that way, AWS stopped feeling like a huge list of unrelated services.
Instead, it started looking like a set of building blocks.
And when those building blocks are combined properly, we can build applications that are scalable, secure, highly available, and easier to operate.
That, for me, is where learning AWS started becoming much more interesting.

Top comments (0)