AWS CDK: Serverless Container with Fargate

January 6th, 2025 1150 Words

With AWS, there are many ways to get something done; finding the perfect solution is one of the challenges. A simple task, like deploying a container, can be done in many ways; this guide will show you how to deploy an application in a Docker container using AWS Fargate and AWS Cloud Development Kit.

On a high level, this will cover:

  • Running a Docker container locally
  • Publishing the Docker container to an ECR repository
  • Deploying the container with ECS Fargate
  • Have custom DNS with Route 53

Everything is configured using the AWS Cloud Development Kit (CDK).

The Docker Container

To get started, we need to have a Dockerfile and a simple use cases: a static website. Of course, this can be anything else, but for this example, we will use a static website. You may argue, this shall not a container at all, instead just files in S3. This is true, but the point is, we want to have a container with the most simple application.

Start with creating a local file in ./src/index.html and a Dockerfile like this:

# Create source files

$ > mkdir -p src
$ > echo "Welcome." > src/index.html

# Create Dockerfile

$ > touch Dockerfile
FROM nginx:latest

COPY ./src/index.html /usr/share/nginx/html/index.html

Next, you can build and run the container locally:

$ > docker build -t app .
$ > docker run -p 8080:80 app

Open your browser and navigate to http://localhost:8080, you should see the content of your ./src/index.html file.

Docker with nginx serving static file

Boom, we have a simple application running locally. Now, how to get this to AWS?

Needed AWS Infrastructure

What is needed to deploy the container to AWS next? Well, to have a truly production-ready setup, we need quite a few things:

This may seem like a lot, but it is actually quite simple to configure all of this using the AWS Cloud Development Kit (CDK). Additionally, this infrastructure scales and will be highly available out-of-the-box. This is production-ready from the ground up.

AWS Infrastructure with CDK

With an existing CDK project, we can simply add the needed components; first, start with the baseline infrastructure to store the container: an Amazon ECR Repository.

import { App, aws_ecr, CfnOutput, Stack } from "aws-cdk-lib";

const app = new App();
const stack = new Stack(app, "serverless-container");

const repository = new aws_ecr.Repository(stack, "Repository", {
  repositoryName: "serverless-container",
});

new CfnOutput(stack, "RepositoryUri", {
  value: repository.repositoryUri,
});

Next, you can deploy the stack:

$ > npx cdk deploy

 ✅ serverless-container

✨  Deployment time: 19.08s

Outputs:
RepositoryUri = 120032447123.dkr.ecr.us-east-1.amazonaws.com/serverless-container

With the repository deployed, you can push your container to it next:

$ > docker build -t app . --platform=linux/amd64

$ > aws ecr get-login-password --region us-east-1 \
  | docker login --username AWS --password-stdin \
    120032447123.dkr.ecr.us-east-1.amazonaws.com

$ > docker tag app 120032447123.dkr.ecr.us-east-1.amazonaws.com/serverless-container:latest
$ > docker push 120032447123.dkr.ecr.us-east-1.amazonaws.com/serverless-container:latest

Notice the --platform=linux/amd64 flag; this is needed to build the container for the correct architecture running on AWS Fargate.

Network Infrastructure

For running the container, we need some basic network components and AWS resources: VPC, Route 53 HostedZone, and ECS Cluster:

const vpc = new aws_ec2.Vpc(stack, "VPC", {
  vpcName: "serverless-container",
  maxAzs: 3,
});

const zone = new aws_route53.PublicHostedZone(stack, "HostedZone", {
  zoneName: "serverless-container.aws.sbstjn.com",
});

const cluster = new aws_ecs.Cluster(stack, "Cluster", {
  clusterName: "serverless-container",
  containerInsights: true,
  vpc,
});

new CfnOutput(stack, "ZoneNameservers", {
  value: Fn.join(" ", zone.hostedZoneNameServers!),
});
$ > npx cdk deploy

 ✅ serverless-container

✨  Deployment time: 19.08s

Outputs:
RepositoryUri = 120032447123.dkr.ecr.us-east-1.amazonaws.com/serverless-container
ZoneNameservers = ns-123.awsdns-01.net ns-2345.awsdns-02.org ns-3456.awsdns-03.co.uk ns-456.awsdns-29.com

The nameservers are needed to configure the DNS records in your domain registrar or DNS provider. Create a new NS record with the nameservers from the output; otherwise, AWS Certificate Manager will not be able to create a certificate for the domain next.

Deploy Container with Fargate

Thanks to AWS ECS Patterns in CDK, we can deploy the Fargate service with a single construct and do not need to worry about the underlying components:

const service = new aws_ecs_patterns.ApplicationLoadBalancedFargateService(
  stack,
  "Service",
  {
    cluster,
    serviceName: "serverless-container",
    memoryLimitMiB: 2048,
    cpu: 512,
    desiredCount: 3,
    taskImageOptions: {
      image: aws_ecs.ContainerImage.fromEcrRepository(repository, "latest"),
      containerPort: 80,
    },
    taskSubnets: {
      subnets: vpc.privateSubnets,
    },
    loadBalancerName: cluster.clusterName,
    healthCheck: {
      command: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"],
      interval: Duration.seconds(30),
      timeout: Duration.seconds(10),
      retries: 3,
    },
  }
);

The ECS Patterns construct will create a new load balancer and service, and configure the necessary settings. An output with the DNS name of the load balancer is created as well:

$ > npx cdk deploy

 ✅ serverless-container

✨  Deployment time: 19.08s

Outputs:
RepositoryUri = 120032447123.dkr.ecr.us-east-1.amazonaws.com/serverless-container
ZoneNameservers = ns-123.awsdns-01.net ns-2345.awsdns-02.org ns-3456.awsdns-03.co.uk ns-456.awsdns-29.com
ServiceServiceURL250C0FB6 = http://serverless-container-1580688403.us-east-1.elb.amazonaws.com

Using you web browser, you should now be able to access your application running on AWS Fargate.

Fargate with nginx serving static file

Per default, the service will be available with a random DNS name on AWS. This is not what we want, so we need to create a new DNS record in Route 53, create a certificate in AWS Certificate Manager, and configure the load balancer to use the certificate.

Custom DNS and Certificate

First, let’s create a new certificate in AWS Certificate Manager. This requires a valid configuration of the NS records for your HostedZone!

const certificate = new aws_certificatemanager.Certificate(
  stack,
  "Certificate",
  {
    domainName: "serverless-container.aws.sbstjn.com",
    validation: aws_certificatemanager.CertificateValidation.fromDns(zone),
  }
);

Using DNS validation, the certificate will be created and validated automatically. Internally, AWS will create the needed DNS record in your HostedZone.

Finally, move the certificate on top of the ECS service construct and pass it to the service. When deploying the stack, you will notice the change in Security Group configuration:

$ > npx cdk deploy

[]

┌───┬─────────────────────────────────────┬─────┬──────────┬─────────────────┐
│   │ Group                               │ Dir │ Protocol │ Peer            │
├───┼─────────────────────────────────────┼─────┼──────────┼─────────────────┤
│ - │ ${Service/LB/SecurityGroup.GroupId} │ In  │ TCP 80   │ Everyone (IPv4)├───┼─────────────────────────────────────┼─────┼──────────┼─────────────────┤
│ + │ ${Service/LB/SecurityGroup.GroupId} │ In  │ TCP 443  │ Everyone (IPv4)└───┴─────────────────────────────────────┴─────┴──────────┴─────────────────┘

[]

Outputs:
RepositoryUri = 120032447123.dkr.ecr.us-east-1.amazonaws.com/serverless-container
ZoneNameservers = ns-123.awsdns-01.net ns-2345.awsdns-02.org ns-3456.awsdns-03.co.uk ns-456.awsdns-29.com
ServiceServiceURL250C0FB6 = https://serverless-container-1580688403.us-east-1.elb.amazonaws.com

Additionally, the service URL output now is using HTTPS. When accessing the service, your web browser should complain about the certificate not matching the domain, but this is expected - for now.

Fargate with nginx serving static file failure

To fix this, all we need is a custom DNS record.

Configure custom DNS

Using Route 53, you need to create a new alias record next:

new aws_route53.RecordSet(stack, "RecordSet", {
  zone,
  recordName: "serverless-container.aws.sbstjn.com",
  recordType: aws_route53.RecordType.A,
  target: aws_route53.RecordTarget.fromAlias(
    new aws_route53_targets.LoadBalancerTarget(service.loadBalancer)
  ),
});

After your next deployment, you can access the service using the custom DNS name! And the correct SSL certificate is used.

Fargate with nginx serving static file

Awesome! 🎉

Further Reading

To have a more robus service, add DNS Failover with Route 53 and read about how to deploy new containers automatically when you push them to the container registry.


  • Deploy Serverless Containers to Scaleway with OpenTofu

    December 5 th, 2025 721 Words

    After building multiarch Docker containers for Rust applications, you want to deploy them somewhere. Scaleway’s serverless container platform offers a straightforward way to run containers without managing infrastructure, and OpenTofu provides the infrastructure-as-code tooling. This guide walks through deploying Docker containers to Scaleway using the OpenTofu provider, from setting up the registry to running your container.

  • Multiarch Docker Containers with Rust

    December 5 th, 2025 395 Words

    This guide shows how to build multiarch Docker containers for Rust applications using cargo zigbuild for cross-compilation and Docker build commands for architecture-specific or multiarch container images. Based on the example for Building a Rust API with Rocket and JWT Authentication, this guide provides the next steps.

  • Building a Rust API with Rocket and JWT Authentication

    December 5 th, 2025 1868 Words

    When building backend APIs, JWT authentication is a common requirement. In Rust, you’ve got several web frameworks to choose from, and Rocket is one that makes request handling feel natural with its request guard system. Combining Rocket with JWTiny for JWT validation and JWKServe as a local identity provider gives you a complete setup for development and testing without external dependencies.

  • JWTiny: Minimal JWT Validation for Rust

    December 4 th, 2025 1340 Words

    I was learning Rust with an example project that needed JWT validation. The popular jsonwebtoken crate depends on serde, but I wanted miniserde instead. That constraint led me to build my own validator — handling signature verification, claims validation, and remote key fetching, designed for reuse across requests. JWTiny is the result.

  • JWKServe: A Fake JWT Authentication Service for Local Development

    December 4 th, 2025 990 Words

    When writing backend services that validate JWT access tokens, you run into a frustrating problem: you need a real identity provider just to test your authentication logic. With Cognito, Auth0, or other OpenID Connect providers, spinning up an authentication service for local development or CI pipelines adds unnecessary complexity. You need valid signatures and correct claims, not the provider itself. That’s where JWKServe comes in.

  • Static Website Hosting in Europe with Free Services

    June 10 th, 2025 303 Words

    The AWS European Sovereign Cloud is maybe the most interesting developments of the current cloud computing era; having AWS create a dedicated branch for european workloads is the next big move. But, how do you run a static website without using US vendors at all?

  • AWS CDK: Serverless WebService Blueprints

    January 19 th, 2025 113 Words

    The past days have been full of content about serverless workloads with AWS AppSync, Amazon Cognito, and AWS Fargate. This guide wraps up all scenarios and is your starting point if you want to build modern serverless applications with AWS using the Cloud Development Kit (CDK).