Fargate

Deploy containerized web services on AWS ECS Fargate with an Application Load Balancer for scalable, managed container hosting. No EC2 instances, no cluster management — automatic health checks and rolling updates.

Supported Frameworks

AWS Resources

ResourcePurpose
ECS ClusterContainer orchestration
Fargate TaskServerless container runtime
Application Load BalancerPublic HTTP/HTTPS endpoint, health checks
VPCNetwork isolation (created automatically if not provided)
ECR RepositoryContainer image registry (via CodePipeline)
CloudWatch LogsContainer logs, retained for 1 week
ACM CertificateSSL for custom domain (optional)
Route53DNS A record (optional)

Container Service Architecture

Users
Route 53
Application Load Balancer
Fargate

Route 53 handles DNS resolution for custom domains. Application Load Balancer distributes incoming traffic across multiple container instances with health checks and SSL termination. Fargate runs containerized applications without managing servers, providing automatic scaling and high availability.

Quick Start

Installation

Terminal window
bun add -D @thunder-so/thunder

Configuration

stack/prod.ts
import { Cdk, Fargate, type FargateProps } from "@thunder-so/thunder";
const config: FargateProps = {
env: {
account: 'YOUR_ACCOUNT_ID',
region: 'us-east-1',
},
application: 'myapp',
service: 'web',
environment: 'prod',
rootDir: '.',
serviceProps: {
dockerFile: 'Dockerfile',
architecture: Cdk.aws_ecs.CpuArchitecture.ARM64,
desiredCount: 1,
cpu: 512,
memorySize: 1024,
port: 3000,
healthCheckPath: '/',
},
};
new Fargate(
new Cdk.App(),
`${config.application}-${config.service}-${config.environment}-stack`,
config
);

Deploy

Terminal window
npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default

CDK builds your Docker image, pushes it to ECR, and deploys the service:

Outputs:
myapp-web-prod-stack.LoadBalancerDNS = myapp-web-prod-1234567890.us-east-1.elb.amazonaws.com

Custom Domain

Connect your service to a custom domain. The certificate must be issued in the same region as your Fargate service.

stack/prod.ts
const config: FargateProps = {
// ...
domain: 'app.example.com',
hostedZoneId: 'Z1D633PJN98FT9',
regionalCertificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/abc-123',
};

When a domain is configured: HTTPS listener is added on port 443, HTTP on port 80 redirects to HTTPS, and a Route53 A record is created.

Service Configuration

Environment Variables and Secrets

stack/prod.ts
const config: FargateProps = {
// ...
serviceProps: {
// ...
variables: [
{ NODE_ENV: 'production' },
{ PUBLIC_API_URL: 'https://api.example.com' },
],
secrets: [
{
key: 'DATABASE_URL',
resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123',
},
],
},
};

Secrets are injected as environment variables at container startup. The task role is automatically granted read access.

CPU and Memory

Fargate uses fixed CPU/memory combinations. ARM64 (Cdk.aws_ecs.CpuArchitecture.ARM64) is cheaper and often faster for Node.js workloads.

CPU (units)vCPUValid Memory (MB)
2560.25512, 1024, 2048
5120.51024–4096
102412048–8192
204824096–16384
409648192–30720

Nixpacks Integration

Use Nixpacks to automatically generate a container image without writing a Dockerfile. When no dockerFile is set in serviceProps, Thunder runs Nixpacks during cdk synth to detect your runtime and generate an optimized build.

Nixpacks auto-detects Node.js, Python, Go, Ruby, Rust, PHP, Java, Deno, and more.

stack/prod.ts
const config: FargateProps = {
// ...
serviceProps: {
// no dockerFile — Nixpacks takes over
port: 3000,
cpu: 512,
memorySize: 1024,
},
buildProps: {
runtime_version: '22', // Node.js version
installcmd: 'bun install',
buildcmd: 'bun run build',
startcmd: 'bun start',
},
};

For advanced control, add a nixpacks.toml to your project root:

nixpacks.toml
[phases.install]
cmds = ["bun install --frozen-lockfile"]
[phases.build]
cmds = ["bun run build"]
[start]
cmd = "node dist/server.js"

Custom Dockerfile

For full control over the build environment, provide your own Dockerfile. The entire rootDir is used as the Docker build context.

Dockerfile
FROM public.ecr.aws/docker/library/node:22-alpine AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN curl -fsSL https://bun.sh/install | bash && export PATH="$HOME/.bun/bin:$PATH"
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
FROM public.ecr.aws/docker/library/node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/server.js"]

Estimated Cost

ScenarioMonthly (us-east-1, no free tier)
1 task (0.25 vCPU / 512 MB)~$33
2 tasks (0.5 vCPU / 1 GB each)~$47

The ALB (~$22/month) is the dominant fixed cost. See Fargate pricing and ALB pricing.

CI/CD Pipeline

GitHub
CodePipeline
CodeBuild
ECR
Fargate

GitHub triggers deployments on code changes. CodePipeline orchestrates the workflow while CodeBuild builds Docker images and pushes to ECR. Fargate pulls the latest images and deploys containers with zero-downtime rolling updates.

Nixpacks Pipeline

stack/prod.ts
const config: FargateProps = {
// ...
accessTokenSecretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:github-token-XXXXXX',
sourceProps: {
owner: 'your-username',
repo: 'your-repo',
branchOrRef: 'main',
},
buildProps: {
installcmd: 'bun install',
buildcmd: 'bun run build',
startcmd: 'bun start',
},
};

Custom Dockerfile Pipeline

stack/prod.ts
const config: FargateProps = {
// ...
accessTokenSecretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:github-token-XXXXXX',
sourceProps: {
owner: 'your-username',
repo: 'your-repo',
branchOrRef: 'main',
},
serviceProps: {
dockerFile: 'Dockerfile',
port: 3000,
},
};

Stack Outputs

OutputDescription
LoadBalancerDNSALB DNS name
Route53DomainCustom domain URL (only if domain is configured)

Destroy

Terminal window
npx cdk destroy --app "bunx tsx stack/prod.ts" --profile default