Deploy Docker Containers to ECS Fargate

Push your Docker image to ECR and run it on ECS Fargate with managed Postgres.

Introduction

30 Second Summary

Running docker compose up on your laptop feels like deployment. Your app starts, your database connects, traffic flows. But no one else in the world can reach it. For your app to serve real users, it needs to live somewhere public, stable, and managed.

In this project, you will deploy your production Docker container to AWS using the same tools and architecture that production engineering teams use every day.

What You'll Build

Your Node.js API running live on AWS, accessible via a stable URL you can share with anyone. You'll curl your /health endpoint from your terminal and watch it respond from the cloud instead of localhost.

By the end of this project, you'll have:

  • Your container running on ECS Fargate with a public URL you can curl from anywhere, proving your app is live without you managing a single server.
  • A managed PostgreSQL database (RDS) handling your data while AWS takes care of backups, patching, and failover.
  • An Application Load Balancer routing traffic to your container, replacing what Nginx did in your local setup as a fully managed service.
  • Secret Mission: Make your app Redis-fault-tolerant and redeploy through the full pipeline, proving you can ship code changes to production.

Are there any prerequisites?

This project assumes you already have Docker installed and an existing AWS account. You should be comfortable building Docker images and using Docker Compose.

You'll also need an existing Node.js app with a production Dockerfile. If you completed the previous project in this series, you're ready to go.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Running docker compose up on your laptop is great for development, but it's not production. In this project, you'll take your existing Node.js app and deploy it the way real teams do: push to a private registry, run on a serverless container platform, and connect to a managed database.

Set Up AWS CLI and ECR

Your Docker container is ready to deploy, but your terminal has no way to communicate with AWS yet. Without the AWS CLI installed and configured, you cannot push images or create any cloud resources.

In this step, you will install the AWS CLI, authenticate it with your AWS account, and create a private container registry to hold your Docker image.

In this step, get ready to:
  • Install and verify AWS CLI v2.
  • Configure your AWS credentials for terminal access.
  • Create an ECR repository to store your Docker image.
Install AWS CLI v2

The AWS CLI is the command-line tool that lets your terminal create, manage, and interact with AWS services. Every command you run in this project flows through it.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Check if the AWS CLI is already installed by running:
aws --version

Look at the output and pick the tab that matches what you see.

✔️ I see aws-cli/2.x

Your AWS CLI v2 is already installed. Move on to the next substep where you will configure your credentials.

ⓧ I see aws-cli/1.x

You have AWS CLI version 1 installed, which is not compatible with this project. AWS recommends uninstalling v1 before installing v2, as both use the same aws command name.

  • If you installed v1 via pip, uninstall it by running:
pip uninstall awscli
  • Then install AWS CLI v2 by running:
curl -fsSL https://awscli.amazonaws.com/v2/install.sh | bash
  • Close and reopen your terminal, then verify the installation by running:
aws --version

You should now see output starting with aws-cli/2. followed by a version number.

ⓧ Command not found

AWS CLI is not installed yet. Let's install v2 now.

  • Install AWS CLI v2 by running the recommended install script:
curl -fsSL https://awscli.amazonaws.com/v2/install.sh | bash
  • Close and reopen your terminal, then verify the installation by running:
aws --version

You should see output starting with aws-cli/2. followed by a version number.

Still seeing command not found?

The installer may have placed the aws binary in a location not on your PATH. Try opening a completely new terminal window. If the issue persists, check the installer output for the path where it placed the binary.

Help me troubleshoot my AWS CLI installation

Configure your AWS credentials

The CLI is installed, but it does not know which AWS account to talk to yet. You need an Access Key ID and Secret Access Key to authenticate.

If you already have an IAM access key pair, keep them handy. If not, expand the tab below to create one.

✔️ I already have an IAM access key

You are ready to configure the CLI. Have your Access Key ID and Secret Access Key available to paste in the next section.

ⓧ I need to create one

You will create an IAM user with admin permissions and generate access keys.

  • Open the AWS Console in your browser and sign in to your account.
  • Search for IAM in the top search bar and select the IAM service.
  • Click Users in the left navigation pane.
  • Click Create user.
  • Enter a username (e.g. myapp-deployer) and click Next.
  • Under Permissions options, select Attach policies directly.
  • Search for AdministratorAccess, check the box next to it, and click Next.
  • Click Create user.

Now generate access keys for this user.

  • Click the username you just created to open its details.
  • Select the Security credentials tab.
  • Scroll down to Access keys and click Create access key.
  • Select Command Line Interface (CLI) as the use case, check the confirmation box, and click Next.
  • Click Create access key.
  • Copy both the Access key ID and Secret access key to a safe place immediately. The secret key will not be shown again.

Copy your secret key now

AWS only displays the Secret Access Key once at creation time. If you lose it, you will need to delete the key and create a new one.

  • Return to your terminal and run the configuration command:
aws configure

What will this command ask?

The aws configure command prompts you for four values, one at a time. Enter each value when prompted:

  • AWS Access Key ID: Paste your Access Key ID.
  • AWS Secret Access Key: Paste your Secret Access Key.
  • Default region name: Type us-east-1.
  • Default output format: Type json.

Before you run the next command, what do you expect to see if your credentials are configured correctly?

  • Verify your credentials are working by running:
aws sts get-caller-identity

You should see a JSON response containing your AWS account ID, user ARN, and user ID. This confirms your terminal can now talk to AWS.

Seeing 'Unable to locate credentials'?

This means aws configure did not save your keys correctly. Run aws configure again and make sure you paste the full Access Key ID and Secret Access Key without extra spaces.

Help me troubleshoot my AWS CLI credentials

Create an ECR repository

Amazon ECR (Elastic Container Registry) is a private container registry that integrates directly with ECS. Your Docker image needs to live here so that AWS services can pull and run it.

  • Create a repository for your app by running:
aws ecr create-repository --repository-name myapp-api

What does this command do?

This creates a private Docker image repository named myapp-api in your default region (us-east-1). Think of it like a private Docker Hub that only your AWS account can access.

In the JSON output, find the repositoryUri field. It looks like this:

123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp-api
  • Copy your repositoryUri value and save it somewhere accessible. You will use it in the next step to push your Docker image.
  • Confirm the repository was created by running:
aws ecr describe-repositories --repository-names myapp-api

You should see your myapp-api repository listed with its URI and creation timestamp.

Seeing 'access denied' or 'not authorized'?

Your IAM user may not have the required permissions. Go back to the IAM console and confirm the AdministratorAccess policy is attached to your user.

Help me fix my ECR permissions error

Your AWS CLI is configured, your credentials are verified, and your private container registry is waiting for an image. Next up, you will build your Docker image and push it to ECR.

Push Your Image to ECR

Your AWS CLI is configured and your ECR repository is waiting. The next challenge is getting your Docker image from your laptop into that registry so AWS services can actually deploy it.

ECR is a private registry, which means Docker needs explicit permission to push to it. In this step, you'll build your image, authenticate Docker to ECR, and push the image up.

In this step, get ready to:
  • Build your production Docker image locally.
  • Authenticate Docker to ECR and tag your image for upload.
  • Push the image to ECR and verify it arrived.
Build your production Docker image

Before you can push anything to ECR, you need a built image. Your project already has a production Dockerfile, so you just need to run the build command.

  • Build your production image by running this command from the directory that contains your Dockerfile:
docker build -t myapp-api .

What does this command do?

  • -t myapp-api names the image myapp-api so you can reference it by name later.
  • . tells Docker to use the current directory as the build context, picking up your existing Dockerfile.
  • Confirm the image was built by running:
docker images myapp-api

You should see your myapp-api image listed with the latest tag and its size in MB.

Build failing?

  • Make sure you're running the command from the same directory as your Dockerfile. If you're in the wrong folder, Docker can't find the build instructions.
  • Check that Docker Desktop is running. If it's not started, Docker commands won't work.

Why is my Docker build failing?

Authenticate Docker to ECR and tag your image

ECR is a private registry. Docker needs a temporary authentication token before it can push images there.

  • Authenticate your local Docker client to ECR by running this command:
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin [[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]].dkr.ecr.us-east-1.amazonaws.com

What does this command do?

  • aws ecr get-login-password fetches a temporary authentication token from AWS using your configured credentials.
  • The pipe (|) passes that token directly to docker login, which authenticates your Docker client to your private ECR registry.
  • This token is valid for 12 hours. After that, you'd need to run this command again.

You should see Login Succeeded in your terminal.

Seeing an access denied error?

  • Double-check that the account ID in your command matches the one you see when you run aws sts get-caller-identity.
  • Make sure your IAM user has AdministratorAccess or at least ECR permissions. If you created the user in Step 1, it should already have this.

Help me fix my ECR authentication error.

Now that Docker is authenticated, you need to tag your image with the full ECR repository URI. Docker uses this tag to know where to push.

  • Tag your local image with the ECR repository URI by running:
docker tag myapp-api:latest [[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]].dkr.ecr.us-east-1.amazonaws.com/myapp-api:latest

What does tagging do?

This creates a second name (tag) pointing to the same image you already built. The fully-qualified ECR URI tells Docker exactly which registry and repository to push to. The image data doesn't get duplicated locally.

Why ECR instead of Docker Hub?

Docker Hub would technically work, but ECR integrates directly with ECS. There's no cross-network latency when pulling images, and ECS can authenticate to ECR automatically without any extra credential configuration.

Push the image to ECR and verify it arrived
  • Push your tagged image to ECR by running:
docker push [[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]].dkr.ecr.us-east-1.amazonaws.com/myapp-api:latest

What should I see?

Docker pushes each layer of your image individually. You'll see progress bars for each layer as they upload. Layers that already exist in the registry show as Layer already exists. The push is complete when you see the final digest and size printed.

Before you verify, what do you expect to see when you query ECR for your image? Think about what metadata a registry stores for each image.

  • Verify the image is in ECR by running:
aws ecr describe-images --repository-name myapp-api

You should see a JSON response with an imageDetails array containing your image. Look for latest in the imageTags list and a sha256 digest confirming the image content.

Push failing or image not showing?

  • If you see a "no basic auth credentials" error, your login token may have expired. Re-run the authentication command from the previous substep.
  • If the push hangs or times out, check your internet connection. Large images may take a few minutes on slower connections.
  • Make sure the repository name in your tag exactly matches myapp-api. A typo creates a reference to a non-existent repository.

Help me fix my ECR push error.

Your image is now sitting in a private registry that ECS can pull from directly. Next up, you'll deploy that image to a running container on Fargate and watch your API come alive in the cloud.

Deploy to ECS Fargate

Your image is sitting safely in ECR, but it is not running anywhere yet. In this step, you will create an ECS cluster, define how your container should run, and launch it on Fargate.

The container will come alive and respond to requests. But there is no database connected yet, so the health endpoint will report "database": "disconnected". That gap is intentional. You will feel the problem before solving it in the next step.

In this step, get ready to:
  • Create an ECS cluster for Fargate workloads.
  • Register a task definition that tells ECS how to run your container.
  • Launch a service and confirm your container is reachable via its public IP.
Create an ECS cluster

An ECS cluster is a logical grouping for your tasks and services. With Fargate, you do not provision any servers. AWS runs your containers on demand.

  • Open the Amazon ECS console at https://console.aws.amazon.com/ecs/v2.
  • In the left navigation pane, choose Clusters.
  • Choose Create cluster.
  • Under Cluster configuration, for Cluster name, enter myapp-cluster.
  • Under Infrastructure, make sure AWS Fargate (serverless) is selected. Deselect any other options.
  • Choose Create.

After a few seconds, you should see your myapp-cluster cluster appear with status Active.

What is an ECS cluster?

A cluster is a logical boundary that groups your running containers (tasks) and the services managing them. It does not represent a physical server. With Fargate, AWS provisions the compute behind the scenes every time a task starts.

Register a task definition

A task definition is the blueprint that tells ECS which image to run, how much CPU and memory to allocate, which ports to expose, and what environment variables to set. You will create one using the JSON editor in the console.

  • In the left navigation pane, choose Task definitions.
  • Choose Create new task definition, then Create new task definition with JSON.
  • Clear the default content in the JSON editor and paste the following task definition:
{
  "family": "myapp-api",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "containerDefinitions": [
    {
      "name": "myapp-api",
      "image": "[[AWS_ACCOUNT_ID=\"your 12-digit AWS account ID\"]].dkr.ecr.us-east-1.amazonaws.com/myapp-api:latest",
      "essential": true,
      "portMappings": [
        {
          "containerPort": 3000,
          "hostPort": 3000,
          "protocol": "tcp"
        }
      ],
      "environment": [
        { "name": "PGHOST", "value": "placeholder" },
        { "name": "PGPORT", "value": "5432" },
        { "name": "PGDATABASE", "value": "myapp" },
        { "name": "PGUSER", "value": "postgres" },
        { "name": "PGPASSWORD", "value": "placeholder" }
      ]
    }
  ]
}
  • Replace the image value's account ID portion with your own 12-digit AWS account ID (the same one from your ECR repository URI).
  • Choose Create.

What does this task definition do?

The cpu of "256" means 0.25 vCPU and memory of "512" means 0.5 GB. These are the smallest Fargate sizes and keep costs minimal for learning.

The environment variables have placeholder values because you do not have a database yet. Your app will start but report the database as disconnected.

The networkMode is awsvpc, which is required for Fargate. Each task gets its own network interface with a private (and optionally public) IP.

Why are the database values placeholders?

You have not created an RDS instance yet. These placeholder values let the container start. Your app's /health endpoint gracefully reports the database status without crashing.

Create a service and access your running container

A service keeps a specified number of tasks running at all times. If a task crashes, the service replaces it. You will create a service with one desired task, give it a public IP, and confirm it responds.

  • In the left navigation pane, choose Clusters, then select myapp-cluster.
  • Choose the Services tab, then choose Create.
  • Under Environment, choose Launch type and select FARGATE.
  • Under Deployment configuration, for Task definition family, choose myapp-api.
  • For Service name, enter myapp-api-service.
  • For Desired tasks, enter 1.
  • Expand the Networking section.
  • For Security group, choose Create a new security group.
  • Enter a security group name like myapp-ecs-sg and a description.
  • Add an inbound rule: Type Custom TCP, Port range 3000, Source Anywhere (0.0.0.0/0).
  • For Public IP, make sure it is turned on (this gives your task a public IP so you can reach it).
  • Choose Create.

Why a public IP on the task?

Without a load balancer (you will add one in the next step), the only way to reach your Fargate task from the internet is to assign it a public IP directly. This is temporary. In Step 5, you will put an Application Load Balancer in front and remove the public IP.

ECS will now launch your task. It typically takes 30-60 seconds for the task to reach RUNNING state.

  • From the myapp-cluster page, choose the Tasks tab.
  • Wait until the task shows Last status as RUNNING. Refresh the page if needed.
  • Choose the task ID to open its details.
  • In the Configuration section, find the Public IP and copy it.

Before you run the next command, take a moment to predict: do you think the health check will report the database as connected or disconnected?

  • Test your running container by running this in your terminal:
curl http://[[ECS_PUBLIC_IP="your task's public IP"]]:3000/health

You should see a response like this:

{"status":"ok","database":"disconnected","cache":"disconnected"}

Your container is alive and responding from AWS. The "database": "disconnected" is expected. You set placeholder values for the database environment variables, so your app cannot connect to anything. That is the gap you will close in the next step by creating a managed PostgreSQL instance on RDS.

Task stuck in PROVISIONING or STOPPED?

  • If the task stops immediately, choose the task ID and check the Logs tab for error output. A common cause is the ECR image URI being incorrect (typo in account ID or region).
  • If you see a permissions error, make sure the ECS task execution role (ecsTaskExecutionRole) exists in your account. ECS auto-creates it on first use, but if IAM permissions are restricted it may not.
  • If the task is RUNNING but curl times out, confirm your security group allows inbound TCP on port 3000 from 0.0.0.0/0 and that the task has a public IP assigned.

Help me troubleshoot my ECS Fargate task deployment

Your container is running in the cloud on ECS Fargate. Next up, you will create a managed PostgreSQL database and connect it to your running container so the health check turns green.

Connect to Managed Postgres (RDS)

Your container is live on ECS Fargate and reachable at its public IP. But curling /health reveals the gap: "database":"disconnected". The task definition still has placeholder values for the Postgres connection.

Instead of running Postgres inside a container (like your docker-compose does), you'll create a managed instance using Amazon RDS. AWS handles the database server, backups, patching, and failover. You just get a connection string and wire it into your task.

In this step, get ready to:
  • Create a managed PostgreSQL database instance on RDS.
  • Lock down network access so only your container can reach the database.
  • Update your ECS task definition with real database credentials and redeploy.
Create an RDS PostgreSQL instance

Your app needs a real database to connect to. You'll create one using RDS, which gives you a fully managed PostgreSQL server without worrying about maintenance.

  • In the AWS Console search bar, type RDS and select the RDS service.
  • In the left navigation pane, click Databases.
  • Click Create database.
  • Select Standard create.
  • Under Engine options, choose PostgreSQL.
  • Under Templates, select Free tier.
  • In the Settings section, set DB instance identifier to myapp-db.
  • Set Master username to postgres.
  • Clear the Auto generate a password checkbox.
  • Enter a password you will remember in both Master password and Confirm password. Write it down somewhere safe.
  • In the Instance configuration section, confirm db.t3.micro is selected.
  • In the Connectivity section, confirm the VPC matches the one your ECS cluster uses (the default VPC if you haven't changed it).
  • Expand Additional configuration and enter myappdb for Initial database name.
  • Leave all other settings at their defaults.
  • Click Create database.

Why managed over containerized?

Your docker-compose file runs Postgres in a container right alongside your app. That works locally, but in production you want automated backups, patching, replication, and failover without managing any of it yourself.

RDS gives you all of that as a service. You pay only for the instance hours. The Free tier template uses db.t3.micro, which is eligible for the AWS Free Tier if your account qualifies.

The instance takes 5-10 minutes to become available. Wait until the Status column in the Databases list changes from Creating to Available.

Use the wait productively

While RDS provisions, you can read ahead to the security group step. The next substep modifies the RDS instance's security group, which only appears once the instance exists.

Configure the RDS security group

By default, the RDS instance's security group allows inbound traffic from your local IP. You need to change this so only your ECS task can reach the database on port 5432.

  • In the RDS Databases list, click on myapp-db to open its details.
  • Select the Connectivity & security tab.
  • Under Security, click the active VPC security group link. This opens the security group in the EC2 console.
  • Select the Inbound rules tab, then click Edit inbound rules.
  • Delete any existing rules that reference your local IP.
  • Click Add rule.
  • Set Type to PostgreSQL (this auto-fills port 5432).
  • For Source, choose Custom and start typing the name of your ECS task's security group. Select it from the dropdown.
  • Click Save rules.

What does this security chain do?

You have just created a rule that says: only resources using the ECS task's security group (your container) can connect to this database on port 5432. No other traffic can reach it. This is the standard 3-tier security model where each layer only talks to the next.

Confirm the inbound rules list now shows a single rule: PostgreSQL / port 5432 / source is your ECS security group.

Can't find your ECS security group in the dropdown?

Make sure the RDS instance is in the same VPC as your ECS cluster. If they are in different VPCs, security group references across VPCs are not supported. Delete and recreate the RDS instance in the correct VPC.

help me troubleshoot my RDS security group configuration for ECS access

Update the task definition and redeploy

Now that RDS is running and the security group is configured, you need to tell your ECS task where the database actually lives. This means creating a new revision of the task definition with real connection values.

  • Return to the RDS console and open your myapp-db instance.
  • On the Connectivity & security tab, find the Endpoint value. Copy this. It looks like myapp-db.xxxxxxxxxxxx.us-east-1.rds.amazonaws.com.

Now create a new task definition revision with your real database values.

  • In the AWS Console, navigate to ECS and click Task definitions in the left pane.
  • Click your task definition family name.
  • Select the latest revision, then click Create new revision, then Create new revision again from the dropdown.
  • Scroll down to your container definition and click on the container name to expand it.
  • Expand the Environment variables section.
  • Update PGHOST to the RDS endpoint you just copied.
  • Update PGPORT to 5432.
  • Update PGDATABASE to myappdb.
  • Update PGUSER to postgres.
  • Update PGPASSWORD to the password you set when creating the RDS instance.
  • Click Create to save the new revision.

What is a task definition revision?

A revision is a new version of your task definition with updated values. ECS keeps all revisions so you can roll back if needed. The new revision automatically gets the next number (for example, revision 2).

Now update your ECS service to use the new revision.

  • Navigate to Clusters in the ECS console and click myapp-cluster.
  • In the Services section, select the checkbox next to your service.
  • Click Update.
  • Select Force new deployment.
  • For Task definition, choose your task definition family and select the latest revision (the one you just created).
  • Click Update.

ECS will now perform a rolling deployment. It starts a new task with the updated definition and stops the old one after the new task is healthy. Wait for the new task to reach RUNNING state in the Tasks tab of your service.

The public IP will change

Because a new task replaces the old one, it gets a new public IP. Click on the running task in the ECS console and find the new public IP in the Configuration section under Network.

Before you run the health check, what do you expect the database field to show now that the task has real RDS credentials?

  • Test your deployment by curling the health endpoint with the new IP:
curl http://[[ECS_PUBLIC_IP="your task's new public IP"]]:3000/health

You should see:

{"status":"ok","database":"connected","cache":"disconnected"}

The database field has flipped from disconnected to connected. Your container is now talking to a managed database.

  • Test the full CRUD flow by creating an item:
curl -X POST http://[[ECS_PUBLIC_IP="your task's new public IP"]]:3000/items \
  -H "Content-Type: application/json" \
  -d '{"name": "test item"}'
  • Retrieve the items list to confirm it persisted:
curl http://[[ECS_PUBLIC_IP="your task's new public IP"]]:3000/items

You should see your test item returned in the response. The data lives in RDS now, not in a disposable container volume.

Still seeing database disconnected?

Check that the RDS security group allows inbound on port 5432 from your ECS task's security group. If you used a CIDR range instead of a security group reference, it will not match Fargate tasks.

Verify the PGHOST value in your new task definition revision matches the RDS endpoint exactly. Extra spaces or a trailing slash will cause a connection failure.

Confirm the RDS instance status is Available and not still Creating.

help me debug why my ECS task cannot connect to my RDS database

Your container is now backed by a production-grade managed database. Next up, you'll put an Application Load Balancer in front of it so you get a stable URL that never changes.

Add an Application Load Balancer

Your container is running and connected to its managed database. There's one problem: you're accessing it through a raw public IP that changes every time the task restarts.

In your docker-compose setup, Nginx handled this as a reverse proxy. On AWS, an Application Load Balancer does the same job as a fully managed service with a stable DNS name and built-in health checks.

In this step, get ready to:
  • Create an internet-facing Application Load Balancer with its own security group.
  • Set up a target group and listener to route traffic to your container on port 3000.
  • Lock down your ECS security groups so only the ALB can reach your task.
Create the load balancer security group and target group

Before creating the ALB itself, you need two prerequisites: a security group that controls who can reach the ALB, and a target group that tells the ALB where to forward traffic.

  • In the AWS Console, navigate to the EC2 service.
  • In the left sidebar under Network & Security, click Security Groups.
  • Click Create security group.
  • Enter myapp-alb-sg as the Security group name.
  • For VPC, select the same VPC your ECS tasks are running in.
  • Under Inbound rules, click Add rule.
  • Set Type to HTTP, which auto-fills port 80.
  • Set Source to Anywhere-IPv4 (0.0.0.0/0).
  • Click Create security group.

Why a separate security group for the ALB?

Each layer in your architecture gets its own security group. The ALB accepts traffic from the internet on port 80. Your ECS tasks only accept traffic from the ALB on port 3000. Your RDS instance only accepts traffic from the ECS tasks on port 5432.

This layered model means compromising one layer does not automatically expose the next.

Now create the target group that tells the ALB where to send traffic.

  • In the EC2 left sidebar under Load Balancing, click Target Groups.
  • Click Create target group.
  • For Choose a target type, select IP addresses.
  • Enter myapp-tg as the Target group name.
  • Set Port to 3000.
  • Select the same VPC as your ECS tasks.
  • In the Health checks section, set Health check path to /health.
  • Click Next.

Why IP addresses as the target type?

Fargate tasks use awsvpc networking, which gives each task its own elastic network interface. These aren't traditional EC2 instances, so you register them by IP address rather than instance ID.

  • On the Register targets page, skip adding targets manually. ECS will handle registration automatically.
  • Click Create target group.

Don't see your VPC in the dropdown?

Make sure you're in the us-east-1 region (check the top-right corner of the Console). Your ECS cluster, security groups, and target groups must all be in the same region and VPC.

Help me find my VPC

Create the Application Load Balancer

With the security group and target group ready, you can now create the ALB and wire everything together.

  • In the EC2 left sidebar under Load Balancing, click Load Balancers.
  • Click Create load balancer.
  • Under Application Load Balancer, click Create.
  • For Load balancer name, enter myapp-alb.
  • For Scheme, select Internet-facing.
  • For IP address type, keep IPv4.

In the Network mapping section:

  • For VPC, select the same VPC as your ECS tasks.
  • Under Availability Zones and subnets, select at least two Availability Zones and pick one subnet in each.

In the Security groups section:

  • Remove the default security group.
  • Select the myapp-alb-sg security group you just created.

In the Listeners and routing section:

  • Keep the default listener on HTTP port 80.
  • For Default action, select the myapp-tg target group from the dropdown.
  • Click Create load balancer.

The ALB takes 2-3 minutes to provision. Wait until the State changes from provisioning to active.

  • Once active, copy the DNS name from the ALB details page. It looks like myapp-alb-123456789.us-east-1.elb.amazonaws.com.

Why ALB instead of Nginx?

In your docker-compose, Nginx sat in front of your app as a reverse proxy. The ALB does the same thing: routes traffic, performs health checks, and can terminate HTTPS.

The difference is that AWS manages it entirely. No container to maintain, no config files to update, and it scales automatically with your traffic.

Route all traffic through the load balancer

The ALB exists but nothing is sending traffic to it yet. You need to recreate your ECS service with the load balancer attached, then lock down the security groups so only the ALB can reach your container.

Why recreate the service?

AWS does not allow modifying a service's load balancer configuration through the Console after creation. The simplest path is to delete the existing service and create a new one with the load balancer attached. ECS handles the transition cleanly.

  • Switch to the ECS console and click on your myapp-cluster cluster.
  • In the Services tab, select your existing service and click Delete.
  • Confirm the deletion and wait for the task to stop.

Now create a new service with the ALB attached.

  • Click Create in the Services tab.
  • Select your latest task definition and revision.
  • Enter a service name (e.g. myapp-service).
  • Set Desired tasks to 1.
  • Expand the Load balancing section.
  • For Load balancer type, select Application Load Balancer.
  • Select Use an existing load balancer and choose myapp-alb.
  • Select Use an existing target group and choose myapp-tg.

In the Networking section:

  • Select the same VPC and subnets as before.
  • Select your existing ECS security group.
  • For Public IP, turn it off (set to DISABLED). The ALB is now the only entry point.
  • Click Create.

Wait for the new task to reach RUNNING status. This takes 1-2 minutes.

What changed from your previous service?

Two critical differences. First, assignPublicIp is now DISABLED. Your task no longer has a public IP that changes on every restart.

Second, the service is registered with the target group. ECS automatically registers and deregisters tasks as they start and stop.

Now lock down the ECS task security group so only the ALB can reach it.

  • Navigate back to EC2, then Security Groups in the left sidebar.
  • Find and select your ECS task security group (the one allowing inbound on port 3000).
  • Click Edit inbound rules.
  • Delete the existing rule that allows port 3000 from 0.0.0.0/0 (Anywhere).
  • Click Add rule.
  • Set Type to Custom TCP and Port range to 3000.
  • For Source, search for and select your myapp-alb-sg security group.
  • Click Save rules.

What is the security group chain?

You now have a three-tier security model. The ALB security group allows the internet in on port 80. The ECS security group allows only the ALB in on port 3000. The RDS security group allows only ECS in on port 5432.

Each layer trusts only the one directly in front of it.

Before you run this, think: will /health still report the database as connected? The task definition still has the same RDS environment variables, so the answer should be yes.

  • Test your ALB by running this in your terminal:
curl http://[[ALB_DNS="your ALB DNS name, e.g. myapp-alb-123456789.us-east-1.elb.amazonaws.com"]]/health

You should see:

{"status":"ok","database":"connected","cache":"disconnected"}

The URL is now stable. Even if your underlying task restarts, the ALB DNS name stays the same and routes traffic to whatever healthy task is running.

Getting a 503 Service Temporarily Unavailable?

This usually means the target group has no healthy targets yet. Check that your ECS task is in RUNNING state and that the target group's health check on /health is passing.

In the EC2 console, click Target Groups, select myapp-tg, then open the Targets tab to see the health status.

If the target shows unhealthy, confirm the ECS security group allows inbound on port 3000 from the ALB security group (not from 0.0.0.0/0).

Help me debug my ALB 503 error

Your container is now running behind a managed load balancer with a stable URL. Next up, you'll prove you can ship code changes through this entire pipeline by making your app Redis-fault-tolerant and redeploying.

Secret mission

Ship a Code Fix Through the Pipeline

Your app works on AWS, but POST /items crashes when Redis is unavailable. Can you fix the code, rebuild the image, and ship the fix through the full deploy pipeline? This proves you can iterate in production, not just deploy once.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. All resources in this project are billed per-second, so costs only accrue while they are active.

Cost warning

If you leave your ECS Fargate task, RDS instance, and Application Load Balancer running, you will be charged approximately $1.20 per day.

The ALB alone costs about $0.02/hour even with zero traffic. Delete everything if you are done learning.

Resources you used:

  • ECS Fargate task (running your container, ~$0.01/hour).
  • RDS PostgreSQL instance (db.t3.micro, ~$0.02/hour).
  • Application Load Balancer (~$0.02/hour).
  • ALB target group.
  • ECR repository (myapp-api, negligible storage cost).
  • ECS cluster (myapp-cluster).
  • Three security groups (ALB, ECS task, RDS).
  • Local Docker images and project files.

Keep everything running

No action needed. Choose this if you are still actively building or experimenting with your deployment.

  • Your container stays live at the ALB DNS URL.
  • You will be billed approximately $1.20/day while resources remain active.
  • Your local Docker images and project files remain on your machine.
  • AWS CLI v2 remains installed on your machine.

Pause - I'll come back to this later

Stop the container and database to halt per-second billing. Your configuration stays intact so you can resume later.

  • Set the ECS service desired count to 0. Open the ECS console, select myapp-cluster, go to the Services tab, select your service, click Update, and set Desired tasks to 0.
  • Stop the RDS instance. In the RDS console, select your DB instance, click Actions, then Stop temporarily. It auto-restarts after 7 days unless you stop it again.
  • Delete the ALB to stop its hourly charge. In the EC2 console, go to Load Balancers, select your ALB, choose Actions, then Delete load balancer. You can recreate it when you return.
  • Your ECR images, task definitions, and security groups remain at no cost.

Delete - I don't want to use this again

Remove all project resources to stop all charges. Follow this order to avoid dependency errors.

Why does order matter?

ECS services depend on the ALB target group, and the ALB and tasks use security groups. Deleting in reverse creation order (service, ALB, RDS, ECR, cluster, security groups) avoids dependency errors that would block deletion.

  • Delete the ECS service. Open the ECS console, select myapp-cluster, go to the Services tab, select your service, choose Delete, check Force delete service, and confirm.
  • Delete the Application Load Balancer. In the EC2 console, go to Load Balancers in the left navigation, select your ALB, choose Actions, then Delete load balancer. Type confirm and click Delete.
  • Delete the target group. In the EC2 console, go to Target Groups, select your target group, choose Actions, then Delete.
  • Delete the RDS instance. In the RDS console, select your DB instance, choose Actions, then Delete. Uncheck Create final snapshot, check the acknowledgement box, type delete me, and confirm. This takes a few minutes.
  • Delete the ECR repository by running this command in your terminal:
aws ecr delete-repository --repository-name myapp-api --force
  • Delete the ECS cluster by running:
aws ecs delete-cluster --cluster myapp-cluster
  • Delete the three security groups (ALB, ECS task, RDS). In the VPC console, go to Security Groups in the left navigation, select each project security group one at a time, choose Actions, then Delete security groups. Delete the RDS and ECS security groups first, then the ALB security group last.
  • Remove your local Docker images by running:
docker rmi myapp-api:latest myapp-api:v2
  • If you created an IAM user specifically for this project, delete it in the IAM console. Go to Users, select the user, and click Delete.

That's a Wrap!

That's a Wrap!

Nice work. You just deployed a production Docker container to AWS ECS Fargate, wired it to a managed PostgreSQL database, and put an Application Load Balancer in front of it.

You've learned how to:

  • Push a production Docker image to a private container registry (ECR) and deploy it to serverless containers on ECS Fargate without managing any servers.
  • Create a managed PostgreSQL database (RDS) and securely connect it to your running container using a security group chain that locks down access at each tier.
  • Replace Nginx with an Application Load Balancer for stable, managed traffic routing with built-in health checks.
  • Ship a code fix through the full production pipeline: modify, rebuild, push to ECR, and redeploy with a rolling update (Secret Mission).

Ready to quiz yourself?