>_
EngineeringNotes
← Back to DevOps
Module 10

AWS ECR & ECS

Elastic Container Registry & Elastic Container Service.

01

What is ECR

ECR stands for Elastic Container Registry. It is a fully managed Docker container registry service provided by Amazon Web Services (AWS). ECR allows you to store, manage, and deploy Docker container images, making it easier for developers to work with containerized applications.

Key features of ECR include:

1. Private repositoriesYou can create private repositories to store your container images securely.
2. Integration with ECS and EKSECR is fully integrated with Amazon Elastic Container Service (ECS) and Amazon Elastic Kubernetes Service (EKS), making it seamless to deploy containers from ECR to these services.
3. ScalabilityAs a managed service, ECR scales automatically to accommodate the growth of your container image repository.
02

ECR vs Docker Hub

🐳 Docker Hub

  • Public by Default: Like GitHub for images. Great for open source.
  • Rate Limits: Free tier has strict pull limits (you might hit them easily).
  • Generic: Not tied to any cloud provider.

☁️ AWS ECR

  • Private by Default: Designed for proprietary company code.
  • Integrated IAM: Control access using AWS Roles (no need for `docker login` passwords if configured correctly).
  • Faster in AWS: Pulling images to EC2/ECS is ultra-fast within the AWS network.

Verdict: Use Docker Hub for public images (like Node, Python, Nginx). Use ECR for your own application code.

03

What is ECS

ECS stands for Elastic Container Service. It is a fully managed container orchestration service provided by AWS. It allows you to easily run, manage, and scale Docker containers on AWS infrastructure without having to manually install and operate your own container orchestration software.

1. Orchestration 🎼

ECS automates deployment and scaling. You say "Runs 5 copies of my app," and ECS ensures 5 copies are always running.

2. Clusters 🖥️

A logical group of resources (EC2 instances or Fargate) where your tasks run.

3. Task Definitions 📄

The "Blueprint". It's a JSON file telling ECS which Docker Image to use, how much CPU/RAM it needs, and which ports to open.

4. Services 🔄

Ensures your Tasks run continuously. If a container crashes, the Service replaces it automatically.

5. Integration 🧩

Works seamlessly with Load Balancers (ELB), CloudWatch (Logs), and IAM (Security).

6. Fargate ⚡

A serverless compute engine. You don't manage servers; you just say "I need 2GB RAM" and AWS runs it.

7. Scaling 📈

Automatic scaling based on demand. If CPU usage goes up, ECS adds more containers automatically.

ECS vs. Managing EC2 Manually

FeatureManual EC2 + DockerAWS ECS
ScalingHard. You must script "Auto Scaling Groups" and user data manually.Automatic. Scale based on CPU/RAM usage.
UpdatesManual. SSH in, `docker pull`, `docker stop`, `docker run`. Downtime possible.Rolling Updates. Zero downtime deployments out of the box.
ManagementYou manage OS updates, Docker daemon, networking.Managed. Focus on your app, not the OS (especially with Fargate).

The Takeaway: If you don't want to deal with managing servers, patching OS, and manual scaling scripts, use ECS.

04

Concepts Simplified

What is "Orchestration"? 🎻

Imagine you have 100 containers. Starting them manually is a nightmare.

Without Orchestration

You are the conductor. You have to manually tell every musician (container) when to start, stop, or play louder. If one faints, silence.

With Orchestration (ECS)

ECS is the conductor. You give it the sheet music (Task Definition). It ensures everyone plays correctly. If someone faints, ECS instantly replaces them.

Fargate vs. EC2 Mode 🏨

Where do your containers live?

EC2 Mode (Renting a House)

  • You rent the virtual machines.
  • You must update the OS and security patches.
  • Cheaper, but more work.

Fargate Mode (Hotel Room)

  • You just check in (run container).
  • AWS manages the building (OS, security).
  • Slightly more expensive, but zero maintenance.
05

Hands-on: Deploying to ECS

Step 1: Containerize a Node.js App

Before we can use ECR or ECS, we need an application packed into a Docker Image.

If you haven't created a Node.js app yet, follow our dedicated guide to build one and verify it runs locally.

Go to Node.js Server Guide →Containerize with Docker →

Checklist before proceeding:

  • You have a `Dockerfile` in your project root.
  • You can run `docker build -t my-app .` successfully.
  • You can run the container locally.

Step 2: Create ECR Repository

Now we create a secure place to store our Docker images on AWS.

1. Go to ECR Console

Search for "Elastic Container Registry" in the AWS search bar and click "Get Started" or "Create repository".

2. Configure the Repo

  • Visibility: Select Private.
  • Name: Enter a name (e.g., my-node-app).

⚠️ Important: Tag Immutability

You will see an option for Tag immutability. What does it mean?

Mutable (Default):

You can overwrite tags. If you push `v1` today, and push a NEW `v1` tomorrow, the old one is gone. Good for testing.

Immutable:

Once `v1` exists, it cannot be changed. Prevents accidental overwrites. Good for Production security.

For this tutorial, leave it Disabled (Mutable).

3. Encryption

Keep KMS encryption enabled (default). AWS encrypts your images at rest so even if someone steals the hard drive, they can't read your code.

4. Create Repository

Click Create repository. You will see your new repo in the list.

Step 3: Push Image to ECR

To push our image from our local system to ECR, we need to authenticate.

🛡️ Security Best Practice: Use IAM User

NEVER use your AWS Root User for daily tasks. The Root User has unlimited access, and if compromised, your entire account is at risk.

Instead, create an IAM User with limited permissions:

  1. Go to IAM Console → Users → Create User.
  2. Attach policies like AmazonEC2ContainerRegistryFullAccess (or more restrictive) and click next then create user.
  3. Go to Users → this user → Security Credentials → Create Access Key.
  4. Selct Use Case as Command Line Interface (CLI).
  5. Run the command: aws configure and enter the access key id and secret access key.

🛠️ Prerequisite: AWS CLI

To run the commands below, you must have the AWS CLI installed and configured on your machine.

Download AWS CLI ↗
Check version:aws --version

🔐 How ECR Authentication Works

Unlike Docker Hub where you use your persistent username and password (e.g., docker login), AWS usage is strictly secure.

AWS provides a Temporary Password (valid for 12 hours) generated via the AWS CLI. You use this token to log your local Docker client into ECR.

Pushing the Image

Select your repo in the console and click View push commands. You will run these 4 commands in your terminal:

# 1. Retrieve password & login Docker to AWS
aws ecr get-login-password ... | docker login ...
# 2. Build your image
docker build -t my-node-app .
# 3. Tag image (Mapping local tag to ECR URL)
docker tag my-node-app:latest ...
# 4. Push to ECR
docker push ...

Step 4: ECS Architecture 🏗️

Now that our image is safely in ECR, how do we run it? The best way to understand ECS is to map it to what you already know about EC2 and Auto Scaling.

🧩 Connecting the Dots: EC2 vs. ECS

In Manual EC2 World   🐢
AMI (OS Image)
In ECS World   🚀
Docker Image (ECR)
Setup Config
Launch Template (User Data, KeyPair)
Blueprint
Task Definition (CPU, RAM, Env Vars)
The Running Creature
EC2 Instance (VM)
The Running Unit
Task (Container)
The Manager
Auto Scaling Group (ASG)
The Orchestrator
ECS Service (Ensures X copies run)

� The Full Architecture Flow

When a user visits your website, here is exactly how the request travels through the components we are about to build:

🌍 Internet / Users
Application Load Balancer (ALB)
Target Group (Port 3000)
⬇️ Forwards to Dynamic IPs ⬇️
ECS Cluster
*Runs on EC2 or Fargate
ECS Service

"Make sure 2 Tasks are always running. If one dies, restart it. Register new ones to the Target Group."

Healthy
🐳
Task 1
10.0.1.45
Healthy
🐳
Task 2
10.0.2.19

🏢 One Cluster, Many Services

A single ECS Cluster is like a Managed Apartment Complex. You don't build a new building for every tenant. You just add more apartments (Services) to the existing complex.

ECS Cluster (Production)
Frontend Service
Replica: 3
⚛️⚛️⚛️
Backend API
Replica: 2
🚂🚂
Worker Service
Replica: 5
🔨🔨...

💰 The Million Dollar Question: Why NOT just use EC2 + ASG?

You are right. You could create a Launch Template, an ASG, and a Load Balancer for every single service. But here is why that becomes a nightmare at scale:

1
Bin Packing (Cost Savings)

EC2/ASG: If your app uses 1.5 CPUs, you have to pay for a 2-CPU instance. 0.5 CPU is wasted x 100 instances = 💸.
ECS: It fits small containers into the empty "cracks" of your servers. It utilizes 90%+ of your hardware.

2
Rolling Updates is HARD on EC2

EC2/ASG: To update, you must terminate the instance, boot a new OS, run User Data, install Node... (Takes 5+ mins).
ECS: The container stops, a new one starts. No OS boot. (Takes 30 seconds).

3
Management Overhead

Imagine managing 50 different Launch Templates and 50 ASGs for 50 microservices.
With ECS, you manage 1 Cluster. You just toss Task Definitions at it.

Ready to build this?

We will build this exact architecture step-by-step.

1. Create Cluster
⬇️
2. Define Task
⬇️
3. Create Service + ELB

Step 5: Create the Cluster 🖥️

The Cluster is the "Physical Home" for our tasks. Even if we use Fargate (Serverless), we need a Cluster to group our services logically.

1. Go to ECS Console

Search for "Elastic Container Service" in AWS.
On the left sidebar, click Clusters.

2. Click Create Cluster

You will see a big orange button Create Cluster.

Cluster Name:my-app-cluster
Infrastructure:
AWS Fargate (Serverless)

We will verify this is selected. This manages the underlying servers for us.

3. Create

Leave everything else as default. Click Create.
It might take a few seconds, and then you will see your active cluster.

Step 6: Create Task Definition 📄

A Task Definition is like a blueprint for your application. It tells AWS which Docker Image to use, how much CPU/Memory it needs, and what environment variables to inject.

1. Go to Task Definitions

On the left sidebar, click Task definitions.
Click the orange button Create new Task Definition.

Build: Create new task definition

2. Configure Task Definition

Task definition family:my-node-task
Infrastructure requirements:
AWS Fargate (Serverless)
Task size:
  • CPU: .5 vCPU
  • Memory: 1 GB

(Smallest size usually fits within free tier/low cost)

Task execution role:Create new role (ecsTaskExecutionRole)

This grants permissions for ECS to pull images from ECR and send logs to CloudWatch.

3. Container Details & Ports

Name:app-container
Image URI:123456789.dkr.ecr.us-east-1.amazonaws.com/my-node-app:latest

(Paste the URI from your ECR repository)

Container Port:3000

(Must match the port your Node app listens on)

🔐 4. Environment Variables

This is where you inject configuration (like DB URLs or API Keys) into your container at runtime.

Add environment variable
Key
DATABASE_URL
Value
mongodb+srv://...
API_ENDPOINT
https://api.example.com
⚠️

Security Note: For highly sensitive secrets (like production DB passwords), consider using ValueFrom and referencing AWS Secrets Manager or Systems Manager Parameter Store instead of plain text.

5. Create

Click Create at the bottom.
You should see a green success message: Successfully created task definition my-node-task:1

Step 7: Create Service & Load Balancer 🔄

Now we bring it all together. The Service ensures our task keeps running, and the Load Balancer gives us a public URL to access it.

1. Go back to your Cluster

Go to Clusters → Click on my-app-cluster.
In the "Services" tab below, click Create.

2. Environment Configuration

Compute Options:Launch type
Launch Type:FARGATE

Deployment Configuration

Familymy-node-task
Service namemy-node-service
Desired tasks1

3. Load Balancing (Crucial Step)

Under the Load Balancing section, select Application Load Balancer.

Load balancer name:Create new: my-app-lb
Target Group:Create new target group

(Important: Change the protocol to HTTP is fine, but ensure the port mapping is correct)

✅ Container to load balance:
Ensure app-container:3000 is selected.
Listener Port: 80 (HTTP)

4. Networking & Security

Subnets: Select all available subnets.

Security Group (for Service): Create new. Name it `my-service-sg`.
Inbound rule: Allow Custom TCP 3000 from Anywhere (0.0.0.0/0) (or better, from LB SG).

5. Create

Click Create. Wait for the service status to turn Active.

⚠️ Final Step: Open Port 80

By default, the Load Balancer security group creates an outbound rule but might miss the inbound rule for the internet.

  1. Go to EC2 Console Security Groups.
  2. Find the SG created for your Load Balancer (usually has `lb` or `ELB` in name).
  3. Click Edit inbound rules.
  4. Add Rule: HTTP (80) from Source 0.0.0.0/0 (Anywhere).
  5. Save rules.

🎉 Verification

Go to EC2 → Load Balancers. Copy the DNS name (e.g., my-app-lb-123.us-east-1.elb.amazonaws.com).
Paste it in your browser. You should see your app!

Step 8: Enabling HTTPS (Optional) 🔒

To make your site secure (green lock icon), we need an SSL Certificate. AWS makes this free and easy with ACM (AWS Certificate Manager).

1. Request a Certificate (ACM)

Search for Certificate Manager in AWS.
Click Request a certificate Request a public certificate.

Domain names:example.com
*.example.com (Recommended wildcard)
Validation:DNS validation (Recommended)

After requesting, click on the Certificate ID. You will see a CNAME record. Add this to your domain provider (Route 53 or GoDaddy) to prove ownership. Wait for status: Issued.

2. Add HTTPS Listener to Load Balancer

Go to EC2 → Load Balancers → Select your LB → Listeners tab.

  • Click Add listener.
  • Protocol: HTTPS (Port 443).
  • Action: Forward to → Select your Target Group.
  • Secure Listener Settings: Select your ACM Certificate from the dropdown.
  • Click Add.

3. Force HTTP to HTTPS (Redirect)

We want users who type `http://` to be automatically moved to `https://`.

  • Edit the existing HTTP:80 listener.
  • Change Action from "Forward to" to Redirect to URL.
  • Select HTTPS (Port 443).
  • Save.

4. Point Domain to LB (Route 53)

This removes the ugly `.elb.amazonaws.com` URL.

  • Go to Route 53 → Hosted Zones.
  • Create Record (A Record).
  • Toggle Alias to Yes.
  • Choose Endpoint: Alias to Application and Classic Load Balancer.
  • Select Region & your Load Balancer.
  • Create.
09

Alternative: Manual HTTPS (Certbot)

If you cannot use AWS DNS validation (e.g., your domain is not on Route 53 and you want a free Let's Encrypt certificate), you can manually generate one using Certbot on a temporary EC2 instance.

1. Create a Temporary EC2 Instance

Launch a small t2.micro instance (Ubuntu or Amazon Linux). Ensure it has a public IP.

2. Point DNS to EC2

Go to your DNS provider (GoDaddy, Namecheap, etc.) and create an A Record pointing your domain (e.g., api.example.com) to the Public IP of this EC2 instance.

🚀 3. Generate Certificate with Certbot

SSH into your EC2 instance and run Certbot to generate the standard .pem files. We have a dedicated module for this process.

Go to Certificate Management Guide →

4. Import to AWS ACM

Once you have the files (fullchain.pem, privkey.pem) from Certbot:

  1. Go to AWS Certificate Manager (ACM).
  2. Click Import a certificate.
  3. Certificate body: Paste content of cert.pem (or first part of fullchain.pem).
  4. Certificate private key: Paste content of privkey.pem.
  5. Certificate chain: Paste content of chain.pem (or rest of fullchain.pem).
  6. Click Import.

5. Attach to Load Balancer

Now use this imported certificate exactly like in Step 8:
Go to Load Balancer Listeners → Edit HTTPS Listener → Select your new Imported certificate.

⚠️ Important: Cleanup

Once the certificate is imported, you don't need the EC2 instance anymore.
1. Terminate the EC2 instance.
2. Update DNS again: Point your domain (CNAME/Alias) to the Load Balancer DNS Name (removing the A record to the EC2 IP).