Deploy a Containerized App with CDK

Deploy a Docker container to ECS Fargate behind a load balancer using CDK.

Introduction

30 Second Summary

Every time you open an app on your phone or visit a website, a server somewhere is running your request. If that server crashes, someone has to fix it manually. Modern cloud platforms solve this by running your app inside containers that can restart, scale, and heal themselves.

In this project, you will deploy a containerized web application to AWS. You'll define your entire infrastructure in TypeScript using the AWS CDK, then watch it come alive as a publicly accessible service.

What You'll Build

A live web application running in a Docker container on AWS ECS Fargate, accessible via a public URL and defined entirely as code using the AWS CDK.

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

  • A Docker container running your Node.js web server on AWS ECS Fargate, with no servers to manage.
  • An Application Load Balancer routing internet traffic to your container and health-checking it automatically.
  • A CDK TypeScript project that provisions your entire stack as repeatable Infrastructure as Code.
  • Secret Mission: Add a CI/CD pipeline that automatically builds and deploys new container images when you push code.

Are there any prerequisites?

You'll need an AWS account with admin access, basic familiarity with the command line, and a general understanding of what Docker containers are. No prior AWS CDK or ECS experience is required.

Before We Start

Before We Start...

Set Up Your Tools

Before you can deploy a containerized app to AWS, you need a few tools on your machine.

In this step, you will install Node.js (which powers the AWS CDK), the AWS CLI (to talk to your AWS account), Docker (to build container images), and the CDK CLI itself.

In this step, get ready to:
  • Install Node.js and npm.
  • Install and configure the AWS CLI.
  • Install Docker Desktop and the AWS CDK CLI.
Install Node.js and npm

Node.js is the JavaScript runtime that powers the AWS CDK. Installing Node.js also installs npm, the package manager you will use to install the CDK CLI.

  • Open your browser and navigate to nodejs.org.
  • Download the LTS (Long Term Support) version for macOS.
  • Run the downloaded installer and follow the on-screen prompts to complete installation.

Once the installer finishes, open your terminal to verify that Node.js and npm are available.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Check your Node.js version by running:
node --version
  • Check your npm version by running:
npm --version

✔️ I see version numbers for both

Node.js and npm are installed and ready. You can move on to the next substep.

ⓧ Command not found

The installer may not have completed successfully, or your terminal needs to be restarted.

  • Close your terminal completely and reopen it.
  • Run node --version again.
  • If it still fails, re-download and run the installer from nodejs.org.
Install AWS CLI and configure credentials

The AWS CLI is the command-line tool that lets you interact with AWS services directly from your terminal. You need it configured so that CDK can provision resources in your account.

  • Download the AWS CLI v2 macOS installer by running:
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
  • Run the installer by running:
sudo installer -pkg AWSCLIV2.pkg -target /

What do these commands do?

The curl command downloads the official AWS CLI v2 package file from Amazon. The installer command is the macOS system utility that installs .pkg files.

The sudo prefix grants the necessary permissions to write to system directories. Your terminal will prompt you for your macOS password. Type it and press Enter (the characters will not appear as you type).

  • Verify that the AWS CLI installed correctly by running:
aws --version

✔️ I see aws-cli/2.x.x

The AWS CLI v2 is installed. Continue to the configuration step below.

ⓧ I see aws-cli/1.x.x

You have version 1 installed, which does not support all the features CDK needs.

ⓧ Command not found

The installer may not have created the correct symlink.

  • Close and reopen your terminal, then try aws --version again.
  • If it still fails, re-download and install following the official AWS CLI install guide.

Now configure the CLI with your AWS credentials. You will need your Access Key ID and Secret Access Key from the AWS Console.

Where do I find my Access Key ID and Secret Access Key?

In the AWS Console, click your username in the top-right corner, then select Security credentials. Scroll down to Access keys and click Create access key.

Copy the Access Key ID and Secret Access Key that appear. Store them safely because the Secret Access Key will not be shown again.

  • Start the configuration wizard by running:
aws configure

The CLI will prompt you for four values. Enter them as follows:

  • At the AWS Access Key ID [None]: prompt, paste your Access Key ID.
  • At the AWS Secret Access Key [None]: prompt, paste your Secret Access Key.
  • At the Default region name [None]: prompt, type us-east-1.
  • At the Default output format [None]: prompt, type json.
  • Verify that your credentials work by running:
aws sts get-caller-identity

You should see a JSON response containing your account ID, user ARN, and user ID. This confirms the CLI can authenticate with AWS.

Seeing an error about invalid credentials?

  • Double-check that you pasted the Access Key ID and Secret Access Key without extra spaces.
  • Make sure your IAM user has admin access or sufficient permissions.
  • Run aws configure again to re-enter your credentials.

Help me fix my AWS CLI credentials

Install Docker Desktop and AWS CDK

CDK needs Docker running locally to build your container image during deployment. The AWS CDK CLI is the command-line tool that synthesizes and deploys your infrastructure.

  • Open your browser and navigate to docker.com/products/docker-desktop.
  • Download Docker Desktop for Mac.
  • Open the downloaded .dmg file and drag Docker into your Applications folder.
  • Launch Docker Desktop from your Applications folder.
  • It will ask for permissions. Allow them.
  • Wait for the Docker icon in your menu bar to show a steady state (not animating). This means Docker is running.
  • Verify Docker is running by running:
docker --version

✔️ I see a version number

Docker is installed and running. Continue to install the CDK CLI below.

ⓧ Command not found or connection error

Docker Desktop may not be fully started yet.

  • Check the Docker icon in your menu bar. If it is still animating, wait for it to finish starting.
  • If Docker Desktop is not running, open it from your Applications folder and wait for it to start.
  • Try docker --version again after Docker shows as running.
  • Install the CDK CLI globally by running:
npm install -g aws-cdk
  • Verify the installation by running:
cdk --version

You should see a version number like 2.x.x printed in your terminal.

cdk command not found?

  • If you installed Node.js recently, close and reopen your terminal so the PATH updates take effect.
  • Try running npx cdk --version as an alternative.
  • If you see a permissions error, re-run the install with sudo npm install -g aws-cdk.

Help me fix CDK installation

What is the AWS CDK?

The AWS CDK (Cloud Development Kit) lets you define cloud infrastructure using TypeScript instead of clicking through the AWS Console. CDK translates your code into CloudFormation templates, which AWS then uses to provision the actual resources.

All four tools are installed and ready. Next up, you will scaffold your CDK project and define the infrastructure for your containerized app.

Initialize Your CDK Project

Your tools are installed and AWS is configured. Now it's time to create the project that will define your entire cloud infrastructure as TypeScript code.

The AWS CDK uses a project scaffold similar to any Node.js application. You'll generate this scaffold with a single command, then prepare your AWS account to receive CDK deployments.

In this step, get ready to:
  • Create a CDK TypeScript project from the official template.
  • Bootstrap your AWS account for CDK deployments.
  • Explore the generated project structure.
Create the project scaffold

CDK projects live in their own directory. You'll create a fresh folder and then run the CDK initializer inside it.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir ecs-fargate-app
cd ecs-fargate-app
  • Initialize a CDK TypeScript app inside this folder by running:
cdk init app --language typescript

What does this command do?

This command generates a complete CDK project from a built-in template.

  • app is the template type, which creates a single-stack CDK application.
  • --language typescript tells CDK to scaffold with TypeScript, giving you type checking and autocomplete for every AWS resource property.
  • Wait for the initialization to finish. The command installs npm dependencies automatically, so it may take a minute.
  • Confirm the project files exist by running:
ls

You should see folders like bin/, lib/, node_modules/, and test/, plus files like cdk.json, package.json, and tsconfig.json.

Seeing 'directory is not empty' error?

  • cdk init only works inside an empty directory. Make sure you created a fresh ecs-fargate-app folder and moved into it before running the command.
  • Run pwd to confirm your terminal is in the right location.
  • Why is cdk init saying the directory is not empty?
Bootstrap your AWS environment

Before CDK can deploy resources to your AWS account, it needs a place to store deployment assets like CloudFormation templates and Docker images. This one-time setup is called bootstrapping.

  • Prepare your AWS account for CDK deployments by running:
cdk bootstrap

What does this command do?

cdk bootstrap creates a CloudFormation stack called CDKToolkit in your AWS account. This stack provisions three things CDK needs to deploy your code.

  • An S3 bucket for storing CloudFormation templates and other assets.
  • An ECR repository for storing Docker images.
  • IAM roles that give CDK permission to manage resources on your behalf.

You only need to run this once per account and region combination.

  • Wait for the command to complete. It takes about a minute to provision all the bootstrap resources.

You should see output showing resources being created, ending with a confirmation message that your environment has been bootstrapped.

Bootstrap failing?

  • Make sure your AWS credentials are configured correctly. Run aws sts get-caller-identity to verify your credentials are working.
  • Your IAM user needs administrator permissions to create the bootstrap resources.
  • Check that your default region is set by running aws configure get region. You should see us-east-1.
  • Why is cdk bootstrap failing with a permissions error?
Understand the CDK project structure

The scaffold generated several files. Three of them form the core of every CDK project.

  • bin/ecs-fargate-app.ts is the app entry point. It creates a CDK App and instantiates your stack.
  • lib/ecs-fargate-app-stack.ts is where you define your infrastructure. This is the file you'll edit most in the next steps.
  • cdk.json tells CDK how to run the app. Its "app" field points to the entry file: npx ts-node --prefer-ts-exts bin/ecs-fargate-app.ts.

How does CDK relate to CloudFormation?

Your TypeScript code is not what AWS reads directly. CDK translates your TypeScript into a CloudFormation template (a JSON document), and CloudFormation is what actually provisions your resources.

Think of CDK as a compiler. You write high-level TypeScript, and CDK compiles it down into the low-level CloudFormation that AWS understands.

✔️ Awesome, I've got everything!

Great. Make sure you can see the three core files in your project before moving on.

ⓧ I'd like to double check the full code

Here's what your generated files should look like. Compare them with what cdk init created.

bin/ecs-fargate-app.ts:

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { EcsFargateAppStack } from '../lib/ecs-fargate-app-stack';

const app = new cdk.App();
new EcsFargateAppStack(app, 'EcsFargateAppStack', {});

lib/ecs-fargate-app-stack.ts:

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class EcsFargateAppStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
  }
}

The key line in cdk.json:

{
  "app": "npx ts-node --prefer-ts-exts bin/ecs-fargate-app.ts"
}

Your CDK project is scaffolded and your AWS account is bootstrapped. Next up, you'll build the Docker container image that CDK will deploy to ECS Fargate.

Build Your Container Image

Your CDK project is scaffolded and your AWS environment is bootstrapped. Now you need the actual application that will run inside a container on ECS Fargate.

In this step, you'll write a simple Node.js web server, package it into a Docker image, and test it locally. This confirms everything works before you hand it off to CDK for deployment.

In this step, get ready to:
  • Create the Node.js application code with a health check endpoint.
  • Write a Dockerfile to package the application.
  • Build and test the container locally.
Create the application code

Your container needs an application to run. You'll create a lightweight Express web server that responds to HTTP requests and includes a dedicated health check endpoint that AWS will use to verify your container is alive.

  • Create a docker/ directory inside your ecs-fargate-app folder by running this command:
mkdir docker
  • Confirm the directory exists by running:
ls

You should see docker listed alongside the other CDK project folders like bin/ and lib/.

  • Create a file called package.json inside the docker/ folder, and paste in the following code:
{
  "name": "ecs-fargate-app",
  "version": "1.0.0",
  "description": "Simple web server for ECS Fargate deployment",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "^4.21.0"
  }
}

What does this file do?

This package.json tells Node.js what your app is called and what packages it needs.

The express dependency is a web framework that makes it easy to create HTTP endpoints. When Docker builds your image, it reads this file to install the right packages.

  • Save docker/package.json.
  • You won't see a visible change yet. The next file (app.js) uses this dependency.
  • Create a file called app.js inside the docker/ folder, and paste in the following code:
const express = require('express');
const app = express();
const port = 80;

app.get('/', (req, res) => {
  res.json({
    message: 'Hello from ECS Fargate!',
    timestamp: new Date().toISOString(),
    container: process.env.HOSTNAME || 'local'
  });
});

app.get('/health', (req, res) => {
  res.status(200).json({ status: 'healthy' });
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

What does this code do?

  • The / route returns a JSON response with a greeting, the current timestamp, and the container's hostname. This helps you confirm which container is serving traffic once deployed.
  • The /health route returns a simple 200 OK response. AWS uses this endpoint to check whether your container is healthy and ready to receive traffic.
  • The server listens on port 80, which is the standard HTTP port that your load balancer will forward traffic to.
  • Save docker/app.js.
  • You won't see a visible change yet. Once the Dockerfile is written, you'll build and test everything together.

Not sure where to create these files?

Both package.json and app.js go inside the docker/ folder you just created. If you're using VS Code, right-click the docker folder in the Explorer sidebar and select New File.

Help me find where to create files in my project

✔️ Awesome, I've got everything!

Great. Make sure both files are saved inside the docker/ folder before moving on.

ⓧ I'd like to double check the full code

Here is docker/package.json:

{
  "name": "ecs-fargate-app",
  "version": "1.0.0",
  "description": "Simple web server for ECS Fargate deployment",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "^4.21.0"
  }
}

Here is docker/app.js:

const express = require('express');
const app = express();
const port = 80;

app.get('/', (req, res) => {
  res.json({
    message: 'Hello from ECS Fargate!',
    timestamp: new Date().toISOString(),
    container: process.env.HOSTNAME || 'local'
  });
});

app.get('/health', (req, res) => {
  res.status(200).json({ status: 'healthy' });
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});
Write the Dockerfile

A Dockerfile is a recipe that tells Docker how to build your image. It specifies the base operating system, copies your code in, installs dependencies, and defines how to start your app.

  • Create a file called Dockerfile inside the docker/ folder, and paste in the following code:
FROM node:20-alpine

WORKDIR /app

COPY package.json ./
RUN npm install --production

COPY app.js ./

EXPOSE 80

CMD ["node", "app.js"]

What does this Dockerfile do?

  • FROM node:20-alpine starts from a lightweight Node.js 20 image based on Alpine Linux. This keeps your container small and fast.
  • COPY package.json and RUN npm install --production install only the dependencies your app needs (no dev tools).
  • COPY app.js adds your application code after dependencies are installed. This ordering takes advantage of Docker's layer caching.
  • EXPOSE 80 documents which port the container listens on.
  • CMD ["node", "app.js"] tells Docker what command to run when the container starts.
  • Save docker/Dockerfile.
  • Confirm all three files exist by running:
ls docker/

You should see Dockerfile, app.js, and package.json listed.

Missing a file?

Make sure all three files are directly inside the docker/ folder, not nested in a subfolder. The Dockerfile has no file extension. If you accidentally named it Dockerfile.txt, rename it to just Dockerfile.

Help me check my docker/ folder structure

✔️ Awesome, I've got everything!

Great. Make sure docker/Dockerfile is saved before moving on.

ⓧ I'd like to double check the full code

Here is docker/Dockerfile:

FROM node:20-alpine

WORKDIR /app

COPY package.json ./
RUN npm install --production

COPY app.js ./

EXPOSE 80

CMD ["node", "app.js"]
Test the container locally

Before deploying to AWS, you want to confirm the container works on your machine. This catches issues early and saves you from debugging in the cloud.

  • Build your Docker image by running this command:
docker build -t ecs-fargate-app ./docker

What does this command do?

  • docker build tells Docker to create an image from a Dockerfile.
  • -t ecs-fargate-app tags the image with a name so you can reference it later.
  • ./docker points to the directory containing your Dockerfile and application code.

The build downloads the base image, installs your dependencies, and packages everything into a single image. You should see output ending with a line that confirms the image was built successfully.

Seeing a "Cannot connect to the Docker daemon" error?

This means Docker Desktop is not running. Open Docker Desktop and wait for the whale icon in your menu bar to show a steady state (not animating). Then try the build command again.

Help me fix my Docker build error

  • Run your container locally by running this command:
docker run -p 8080:80 ecs-fargate-app

What does this command do?

docker run starts a container from your image. The -p 8080:80 flag maps port 8080 on your machine to port 80 inside the container. This means you can visit http://localhost:8080 to reach your server.

You should see Server running on port 80 printed in your terminal. The terminal will stay occupied because the container is running in the foreground.

  • Open your browser and visit http://localhost:8080.
  • You should see a JSON response with a message, timestamp, and container field.
  • Visit http://localhost:8080/health in your browser.
  • You should see {"status":"healthy"} returned.

Not seeing a response in your browser?

Make sure you're visiting http://localhost:8080 (port 8080, not 80). Port 80 is inside the container. Port 8080 is the mapped port on your machine.

If you see connection refused, check that the docker run command is still running in your terminal and shows Server running on port 80.

Help me troubleshoot my Docker container

  • Return to the terminal where your container is running.
  • Press Ctrl+C to stop the container.

The container stops and you get your terminal prompt back.

Your application works locally inside a container. Next up, you'll define your AWS infrastructure with CDK so this exact container can run on ECS Fargate behind a load balancer.

Define Your Infrastructure with CDK

Your Docker container runs perfectly on your machine. Now the challenge is telling AWS exactly how to run it in the cloud, with networking, load balancing, and health checks all wired up.

In this step, you'll define your entire AWS infrastructure in a single TypeScript file using the CDK. About 20 lines of code will describe everything from container hosting to traffic routing.

In this step, get ready to:
  • Write the CDK stack definition for your ECS Fargate service.
  • Configure container, memory, CPU, and health check settings.
  • Preview the CloudFormation template that CDK generates.
Write the ECS Fargate stack

The ApplicationLoadBalancedFargateService is a high-level CDK construct that provisions an entire ECS service with a load balancer in a single definition. It creates a VPC, subnets, security groups, an ALB, and the Fargate task all from one construct.

  • Open lib/ecs-fargate-app-stack.ts in your editor.
  • Replace the entire file contents with the following code:
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';
import * as path from 'path';

export class EcsFargateAppStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Define the Fargate service with an Application Load Balancer
    const fargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'FargateService', {
      taskImageOptions: {
        // Build the Docker image from the local docker/ directory and push to ECR
        image: ecs.ContainerImage.fromAsset(path.join(__dirname, '..', 'docker')),
        containerPort: 80,
      },
      cpu: 256,            // 0.25 vCPU
      memoryLimitMiB: 512, // Smallest Fargate configuration
      desiredCount: 1,     // Run one container instance
      publicLoadBalancer: true,
    });

    // Configure the ALB health check to use your /health endpoint
    fargateService.targetGroup.configureHealthCheck({
      path: '/health',
    });

    // Output the load balancer URL so you can access your app
    new cdk.CfnOutput(this, 'LoadBalancerDNS', {
      value: fargateService.loadBalancer.loadBalancerDnsName,
      description: 'Load Balancer DNS Name',
    });
  }
}

What does this code do?

  • The three new imports bring in the aws-ecs module (for container image definitions), aws-ecs-patterns (for the high-level Fargate construct), and path (to reference the local docker/ directory).
  • ContainerImage.fromAsset() tells CDK to build your Docker image locally and push it to ECR automatically during deployment.
  • The CfnOutput at the bottom prints the load balancer URL after deployment so you can access your app.
  • Save lib/ecs-fargate-app-stack.ts.

You won't see a visible change yet. The next substep uses cdk synth to verify your code compiles into a valid CloudFormation template.

Seeing a red underline in your editor?

  • Make sure your import paths match exactly: aws-cdk-lib/aws-ecs and aws-cdk-lib/aws-ecs-patterns. A typo in the module path causes an unresolved import error.
  • Confirm that aws-cdk-lib is listed in your package.json dependencies. CDK init should have added it automatically.
  • help me fix import errors in my CDK stack file

✔️ Awesome, I've got everything!

Great. Double check you've saved the file before moving on.

ⓧ I'd like to double check the full code

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';
import * as path from 'path';

export class EcsFargateAppStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const fargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'FargateService', {
      taskImageOptions: {
        image: ecs.ContainerImage.fromAsset(path.join(__dirname, '..', 'docker')),
        containerPort: 80,
      },
      cpu: 256,
      memoryLimitMiB: 512,
      desiredCount: 1,
      publicLoadBalancer: true,
    });

    fargateService.targetGroup.configureHealthCheck({
      path: '/health',
    });

    new cdk.CfnOutput(this, 'LoadBalancerDNS', {
      value: fargateService.loadBalancer.loadBalancerDnsName,
      description: 'Load Balancer DNS Name',
    });
  }
}
Understand the service properties

The construct you just wrote packs a lot of configuration into a small space. Each property controls a different part of how your container runs in the cloud.

What does each property control?

  • cpu: 256 allocates 0.25 vCPU to your container. Combined with memoryLimitMiB: 512, this is the smallest (and cheapest) Fargate configuration available.
  • desiredCount: 1 tells ECS to keep exactly one copy of your container running. In production you would increase this for redundancy.
  • publicLoadBalancer: true makes the ALB internet-facing so you can reach your app from a browser.
  • The health check on /health tells the ALB to periodically hit that endpoint. If the container stops responding, the ALB stops sending traffic to it and ECS replaces it.

Why use an L3 construct?

CDK organizes constructs into levels. L1 constructs map 1:1 to CloudFormation resources. L2 adds sensible defaults. L3 constructs like ApplicationLoadBalancedFargateService wire together multiple resources (VPC, subnets, security groups, ALB, ECS cluster, task definition, IAM roles) into a single high-level pattern.

Your ~20 lines of TypeScript will generate hundreds of lines of CloudFormation. That is the power of Infrastructure as Code at a high level of abstraction.

Preview the generated CloudFormation

Before deploying anything, CDK lets you synthesize your code into a raw CloudFormation template. This is your chance to verify everything compiles correctly.

  • Generate the CloudFormation template by running this command:
cdk synth

What does cdk synth do?

This command compiles your TypeScript into a CloudFormation YAML template. For a single-stack app, the full template prints directly to your terminal. CDK also saves the output in the cdk.out/ directory as a cloud assembly.

Scroll through the output and notice how your ~20 lines of TypeScript expanded into hundreds of lines defining a VPC, subnets, NAT gateways, security groups, an ALB, target groups, an ECS cluster, a task definition, IAM roles, and more.

  • You should see a large YAML template printed to your terminal. Look for resource types like AWS::ECS::Cluster and AWS::ElasticLoadBalancingV2::LoadBalancer in the output.
  • Preview what resources CDK will create by running:
cdk diff

You should see a list of resources prefixed with [+] symbols. Each line represents an AWS resource that will be created when you deploy.

Seeing a compilation error?

  • If you see a TypeScript error, check that your file matches the code above exactly. A missing comma after publicLoadBalancer: true or a mismatched bracket is the most common cause.
  • If the error mentions Docker, make sure Docker Desktop is running. CDK needs Docker available to process the fromAsset call during synthesis.
  • help me fix CDK synth errors in my Fargate stack

Your infrastructure is fully defined and validated. Next up, you'll deploy the entire stack to AWS and see your containerized app running live behind a load balancer.

Deploy and Verify

You've defined your entire infrastructure in TypeScript and previewed it with cdk synth. Now comes the moment of truth.

In this step, you'll deploy the stack to AWS, verify your containerized app is accessible through the load balancer, and explore the resources CDK provisioned in the console.

In this step, get ready to:
  • Deploy the EcsFargateAppStack to your AWS account.
  • Verify the application responds via the ALB URL.
  • Explore the ECS and ALB resources in the AWS Console.
Deploy the stack to AWS

CDK builds your Docker image locally and pushes it to ECR before provisioning the infrastructure through CloudFormation. Docker Desktop must be running for this to work.

  • Confirm Docker Desktop is running (check for the whale icon in your menu bar).
  • Deploy your stack by running this command:
cdk deploy

What happens during deployment?

CDK will display a list of IAM and security group changes your stack introduces. Because this is a brand-new stack creating roles and security groups, you will see a security approval prompt.

Type y and press Enter to confirm. CDK then builds your Docker image, pushes it to ECR, and hands the CloudFormation template to AWS.

  • Wait for the deployment to complete. This takes 5-10 minutes as CloudFormation provisions approximately 30 resources (VPC, subnets, NAT gateway, ALB, ECS cluster, Fargate service, security groups, and IAM roles).
  • When the deployment finishes, look for the Outputs section in your terminal. Note the LoadBalancerDNS value. This is your application's public URL.

What is the LoadBalancerDNS output?

This is the public DNS name of the Application Load Balancer that CDK created. It routes internet traffic to your Fargate container.

The URL looks something like EcsFa-Farga-XXXXX-YYYYY.us-east-1.elb.amazonaws.com. You defined this output in your CDK stack using CfnOutput.

Deployment failed or timed out?

  • Make sure Docker Desktop is running. CDK builds the Docker image locally before pushing it to ECR.
  • Check that your AWS credentials are valid by running aws sts get-caller-identity. If this fails, re-run aws configure.
  • If you see a permissions error, ensure your IAM user has administrator access or the necessary CDK bootstrap role permissions.

Help me troubleshoot my CDK deployment failure. I ran cdk deploy and it did not complete successfully.

Verify your running service

Your container is now running on AWS behind a load balancer. Time to confirm it responds to HTTP requests.

  • Copy the LoadBalancerDNS URL from your terminal output.
  • Open a new browser tab and paste the URL. You should see a JSON response containing the message Hello from ECS Fargate! along with a timestamp and container hostname.
  • Append /health to the URL in your browser address bar and press Enter. You should see {"status":"healthy"} confirming the health check endpoint works.

Why a health check endpoint?

The ALB pings /health on your container every few seconds. If it stops returning 200, the ALB marks the task as unhealthy and ECS replaces it. This is how production services self-heal without human intervention.

Browser shows an error or no response?

  • Wait 1-2 minutes after deployment completes. The ALB needs time to register the target and pass initial health checks.
  • Make sure you are using http:// (not https://) in the URL. The ALB is configured for HTTP only.
  • If the page still doesn't load, return to your terminal and confirm cdk deploy finished without errors.

My ALB URL is not returning a response after deploying my ECS Fargate app with CDK. Help me debug this.

Observe the ECS architecture in the AWS Console

Seeing your app respond in the browser confirms everything works. Now explore the individual resources CDK created so you can connect the architecture to what you've built.

  • Open the Amazon ECS console at https://console.aws.amazon.com/ecs/v2.
  • In the navigation pane, click Clusters. You should see a cluster created by your stack.
  • Click the cluster name to open it.
  • Select the Services tab. You should see one service running with 1 desired task and 1 running task.
  • Click the service name, then select the Tasks tab. You should see one task in RUNNING status.

How do these pieces connect?

  • The Cluster is the logical group that holds your services and tasks.
  • The Service keeps your desired number of tasks alive. If a task crashes, the Service launches a replacement.
  • The Task is the running instance of your container, created from a Task Definition (the recipe that specifies the image, CPU, memory, and port).
  • Now open the EC2 console at https://console.aws.amazon.com/ec2/.
  • In the navigation pane, scroll down and click Load Balancers (under the Load Balancing section).
  • Click your ALB name to view its details.
  • Select the Listeners tab to see the HTTP listener on port 80.
  • Click the linked target group to see your Fargate task registered as a healthy target.

How does traffic reach your container?

The full request path is: Internet > ALB DNS name > Listener (port 80) > Target Group > Health Check passes > routes to your ECS Task on port 80.

The ALB continuously checks /health on each registered target. Only healthy targets receive traffic.

  • Finally, open the CloudFormation console at https://console.aws.amazon.com/cloudformation/.
  • Click the EcsFargateAppStack stack name. Under the Resources tab, you can see every resource CDK provisioned from your ~20 lines of TypeScript.

Why does CloudFormation show so many resources?

Your CDK stack used an L3 construct (ApplicationLoadBalancedFargateService) that abstracts away boilerplate. Under the hood, CDK generated a VPC, subnets, route tables, a NAT gateway, an internet gateway, security groups, an ALB, a listener, a target group, an ECS cluster, a task definition, a service, IAM roles, and an ECR repository.

This is the power of Infrastructure as Code. You declare what you want. CDK and CloudFormation handle the details.

Secret mission

Add a CI/CD Pipeline

Your ECS Fargate app is deployed, but every code change requires a manual cdk deploy. In this secret mission, you'll add an automated CI/CD pipeline using CDK Pipelines that rebuilds and deploys your container whenever you push code changes.

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.

This project provisions AWS resources that incur hourly charges while running. If you leave everything up, expect to pay approximately $2-3 per day.

Cost warning

Your deployed stack includes resources that charge by the hour. The biggest cost drivers are the NAT Gateway (~$0.045/hr per gateway) and Application Load Balancer (~$0.02/hr).

If you are not actively using this project, choose Pause or Delete below to avoid unexpected charges.

Resources you used:

  • EcsFargateAppStack CloudFormation stack (ALB, NAT Gateway, Fargate service, ECR repository, VPC, subnets, security groups, IAM roles).
  • CDK Bootstrap resources (CDKToolkit CloudFormation stack with S3 bucket and ECR repository).
  • CI/CD Pipeline resources, if you completed the Secret Mission (CodePipeline, CodeBuild project, additional IAM roles).
  • Local project files (ecs-fargate-app directory on your Desktop).

Keep everything running

No action needed. Choose this if you are still actively exploring the deployed service or experimenting with changes.

  • Your Fargate service continues running and serving traffic through the ALB URL.
  • Ongoing cost is approximately $2-3 per day while resources remain active.
  • Node.js, Docker Desktop, AWS CLI, and the AWS CDK CLI remain installed on your machine for future projects.

Pause - I'll come back to this later

Destroy the AWS resources to stop all charges, but keep your local project files so you can redeploy later with a single command.

  • Make sure you are inside the ecs-fargate-app directory in your terminal.
  • Destroy the deployed stack by running:
cdk destroy
  • Confirm the deletion when prompted by entering y.
  • If you completed the Secret Mission and have additional pipeline stacks, run:
cdk destroy --all
  • Your local ecs-fargate-app folder stays on your Desktop, ready for redeployment with cdk deploy whenever you return.
  • The CDK Bootstrap stack (CDKToolkit) stays in your account for future CDK projects. It costs nothing while idle.

Delete - I don't want to use this again

Remove all AWS resources and local project files to start completely fresh.

  • Make sure you are inside the ecs-fargate-app directory in your terminal.
  • Destroy all CDK stacks (including any Secret Mission pipeline stacks) by running:
cdk destroy --all
  • Confirm the deletion when prompted by entering y.
  • Wait for CloudFormation to finish deleting all resources. This may take a few minutes.
  • Verify in the AWS Console that no resources remain. Check the following services in us-east-1:
  • CloudFormation: no stacks named EcsFargateAppStack remain.
  • ECS: no clusters remain.
  • EC2 > Load Balancers: no ALBs remain.
  • VPC: no extra VPCs beyond the default VPC.
  • Remove the CDK Bootstrap stack if you do not plan to use CDK again. In the AWS Console, navigate to CloudFormation, select the CDKToolkit stack, and click Delete.
  • Remove your local project files by navigating to your Desktop and running:
cd ~/Desktop
rm -rf ecs-fargate-app
  • Optionally, uninstall the AWS CDK CLI by running:
npm uninstall -g aws-cdk

That's a Wrap!

That's a Wrap!

Nice work! You just deployed a real, internet-accessible service to AWS using modern Infrastructure as Code practices.

You've learned how to:

  • Define an entire AWS infrastructure stack (VPC, ECS cluster, ALB, ECR, IAM roles) in ~20 lines of CDK TypeScript.
  • Build a Docker container image with a health check endpoint and deploy it to ECS Fargate behind an Application Load Balancer.
  • Trace the full request path from ALB URL to running container, and explore how CDK generates hundreds of lines of CloudFormation from your concise TypeScript definitions.
  • Secret Mission: Set up a CI/CD pipeline with CodePipeline that automatically builds and deploys new container images on git push.

Ready to quiz yourself?