← Back home

How to Deploy a Docker Container on AWS ECS Fargate (Manual Setup with ECR)

Every time I build something and want a client to try it, I need it online fast. Not perfectly architected — just accessible, stable enough for a demo, and easy to update when feedback comes in.

For a long time that meant spinning up a VM, installing everything by hand, and hoping it didn’t break when I pushed a change. Context switching between building the app and managing the server adds up. It’s slow, and it’s the kind of friction that makes you put off sharing early work.

Containers fixed most of that. Package the app once, ship it anywhere. And AWS ECS with Fargate made it easy to run those containers without managing servers at all.

This is Part 1 of a 3-part series:

📺 Prefer to watch? Full walkthrough on YouTube


How It All Fits Together

Before touching the console, here’s the architecture at a glance:

Your App (Docker Image)

   Amazon ECR          ← private image registry

ECS Task Definition    ← blueprint: image URL, CPU, memory, ports

   ECS Service         ← keeps your task running

  ECS Cluster          ← the managed space everything runs in

  Public IP / URL      ← how clients access your app

ECS Cluster — an organised space where your services live. Think of it as the environment, not the server.

Task Definition — the blueprint for your container. Comparable to a Kubernetes pod spec. It tells AWS what image to run, how much CPU and memory to allocate, and what ports to expose.

ECR (Elastic Container Registry) — where your Docker images are stored. Private by default, and it integrates natively with ECS so no extra auth setup is needed.

Fargate — the serverless compute option for ECS. You define what to run; AWS figures out where to run it. No EC2 instances to manage.


Prerequisites


Step 1: Create the ECS Cluster

Go to the ECS section in the AWS console and click Create Cluster.

That’s it. The cluster is just the container — it doesn’t do anything until you add services to it.


Step 2: Create the ECR Repository

Before building the task definition, get the image repository ready. That way you’ll have the image URL on hand when you need it.

Go to ECR → Create Repository. Give it a name that matches your app.

Once created, copy the repository URI — you’ll use it as the Docker image tag.

# Format:
# <account_id>.dkr.ecr.<region>.amazonaws.com/<repo-name>

# Example:
123456789.dkr.ecr.ap-southeast-1.amazonaws.com/gif-app

Step 3: Build and Push Your Docker Image

For this demo the app is a simple Python web app that displays random GIFs on every page refresh. Nothing fancy — the point is the deployment pattern, not the app.

# Authenticate Docker with ECR
aws ecr get-login-password --region ap-southeast-1 | \
  docker login --username AWS --password-stdin \
  123456789.dkr.ecr.ap-southeast-1.amazonaws.com

# Build the image using the ECR repo URI as the tag
docker build -t 123456789.dkr.ecr.ap-southeast-1.amazonaws.com/gif-app:latest .

# Run it locally to verify it works
docker run -p 5000:5000 123456789.dkr.ecr.ap-southeast-1.amazonaws.com/gif-app:latest

# Push to ECR
docker push 123456789.dkr.ecr.ap-southeast-1.amazonaws.com/gif-app:latest

If you get an authorization token expired error on push, just re-run the aws ecr get-login-password command and try again.


Step 4: Create the Task Definition

Go to ECS → Task Definitions → Create new task definition.

Key fields to fill in:

FieldValue
Launch typeAWS Fargate
CPU0.25 vCPU (enough for a PoC)
Memory0.5 GB
Image URIyour ECR repo URI + :latest
Port mapping5000 (or whatever port your app listens on)

Task definitions are versioned — every change creates a new revision. This makes it easy to roll back or pick a specific version when deploying a service.

Leave the rest as defaults for now.


Step 5: Create the ECS Service

Now go back to your cluster and create a service.

The service is what keeps your task running. If the container crashes, the service restarts it. If you want multiple copies, the service manages that too.

Under Networking, the security group is the important part here. For a PoC, open inbound traffic on port 5000 (or whichever port your app uses). You’d tighten this down for production.

Also make sure Public IP is enabled — this is how you’ll access the app.

Create the service and wait for the task to reach a RUNNING state.


Step 6: Access Your App

Go to your cluster → Tasks → click the running task → find the Public IP.

Open http://<public-ip>:5000 in your browser.

The IP changes every time you create a new task or deploy an update. For a quick client demo this is fine. For anything more permanent, you’d put a load balancer in front — that’s covered in a later post.


Step 7: Push an Update

This is the part that used to be painful. Here’s how it works now.

Make your code change, rebuild, and push:

docker build -t 123456789.dkr.ecr.ap-southeast-1.amazonaws.com/gif-app:latest .
docker push 123456789.dkr.ecr.ap-southeast-1.amazonaws.com/gif-app:latest

Check ECR — you should now see 2 images in the repository (latest will be overwritten, but the previous image is retained with its digest).

Back in ECS, go to your service and click Force new deployment. ECS will pull the latest image and replace the running task. Get the new IP and verify the change is live.


What You’ve Built

At this point you have:

It’s not automated yet — every update still requires a manual docker push and a forced redeployment. That’s what Part 2 fixes with GitHub Actions.


What’s Next

Part 2 wires this up to GitHub Actions so that every push to main automatically builds, pushes to ECR, and triggers an ECS redeployment. No console clicks required.

Part 3 replaces the manual console setup with Pulumi so the whole infrastructure is reproducible in code.

Questions or issues? Drop them in the comments.