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
| Resource | Purpose |
|---|---|
| ECS Cluster | Container orchestration |
| Fargate Task | Serverless container runtime |
| Application Load Balancer | Public HTTP/HTTPS endpoint, health checks |
| VPC | Network isolation (created automatically if not provided) |
| ECR Repository | Container image registry (via CodePipeline) |
| CloudWatch Logs | Container logs, retained for 1 week |
| ACM Certificate | SSL for custom domain (optional) |
| Route53 | DNS A record (optional) |
Container Service Architecture
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
bun add -D @thunder-so/thunderConfiguration
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
npx cdk deploy --app "bunx tsx stack/prod.ts" --profile defaultCDK 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.comCustom Domain
Connect your service to a custom domain. The certificate must be issued in the same region as your Fargate service.
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
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) | vCPU | Valid Memory (MB) |
|---|---|---|
| 256 | 0.25 | 512, 1024, 2048 |
| 512 | 0.5 | 1024–4096 |
| 1024 | 1 | 2048–8192 |
| 2048 | 2 | 4096–16384 |
| 4096 | 4 | 8192–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.
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:
[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.
FROM public.ecr.aws/docker/library/node:22-alpine AS builderWORKDIR /appCOPY package.json bun.lockb ./RUN curl -fsSL https://bun.sh/install | bash && export PATH="$HOME/.bun/bin:$PATH"RUN bun install --frozen-lockfileCOPY . .RUN bun run build
FROM public.ecr.aws/docker/library/node:22-alpine AS runnerWORKDIR /appENV NODE_ENV=productionENV PORT=3000COPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesCOPY --from=builder /app/package.json ./EXPOSE 3000CMD ["node", "dist/server.js"]Estimated Cost
| Scenario | Monthly (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 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
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
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
| Output | Description |
|---|---|
LoadBalancerDNS | ALB DNS name |
Route53Domain | Custom domain URL (only if domain is configured) |
Destroy
npx cdk destroy --app "bunx tsx stack/prod.ts" --profile default