Skip to content
Learni
View all tutorials
Cloud AWS

How to Deploy a Node.js App on AWS Fargate in 2026

Lire en français

Introduction

AWS Fargate is a serverless compute engine for Amazon ECS (Elastic Container Service) that lets you run Docker containers without provisioning or managing underlying machines. Unlike EC2, where you handle instances, Fargate manages orchestration, scaling, and high availability.

Why use it in 2026? Costs are predictable (pay-per-second), native AWS integrations (VPC, IAM, CloudWatch) simplify microservices deployments, and it supports up to 16 vCPUs per task. This beginner tutorial guides you through deploying a simple Node.js app: an HTTP server that responds with 'Hello Fargate!'.

At the end, you'll have a scalable ECS service on Fargate, monitored and accessible via public IP. Estimated time: 30 minutes. Cost: <$1 for testing. Prepare your free tier AWS account.

Prerequisites

  • AWS account (with IAM user for CLI, us-east-1 region recommended)
  • AWS CLI v2 installed and configured (aws configure)
  • Docker Desktop (v24+)
  • Node.js 20+ and npm
  • Git cloned locally
  • IAM permissions: AmazonEC2ContainerRegistryFullAccess, AmazonECS_FullAccess, IAMFullAccess (for beginner testing)

Create the Node.js Application

index.js
const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello Fargate ! Déployé en 2026 🚀');
});

const port = process.env.PORT || 8080;
server.listen(port, '0.0.0.0', () => {
  console.log(`Serveur sur port ${port}`);
});

This code creates a minimal HTTP server using native Node.js (no Express for simplicity). It listens on the PORT environment variable (injected by Fargate) or 8080, and responds with a fixed message. No external dependencies for a quick build; just copy-paste into a project folder.

Prepare package.json

Create a basic package.json for the container. Run npm init -y then add "start": "node index.js". No other dependencies needed.

Write the Dockerfile

Dockerfile
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY index.js .

USER node

EXPOSE 8080

CMD ["npm", "start"]

Optimized production Dockerfile: lightweight alpine base (50MB), production-only install, non-root user for Fargate security. Implicit multi-stage via RUN npm ci. EXPOSE informs ECS of the port without forcing a bind.

Build and Push to ECR

deploy-ecr.sh
#!/bin/bash
REPO_NAME=fargate-demo
AWS_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
REGION=us-east-1
REPO_URI=${AWS_ACCOUNT}.dkr.ecr.${REGION}.amazonaws.com/${REPO_NAME}

# Create ECR repo
aws ecr create-repository --repository-name ${REPO_NAME} --region ${REGION}

# Login to ECR
docker build -t ${REPO_NAME} .
docker tag ${REPO_NAME}:latest ${REPO_URI}:latest
aws ecr get-login-password --region ${REGION} | docker login --username AWS --password-stdin ${REPO_URI}

docker push ${REPO_URI}:latest
echo "Image pushed: ${REPO_URI}:latest"

Complete, idempotent bash script: creates ECR repo, builds/tags/pushes the image. Uses dynamic variables (AWS account auto-detected). Make executable with chmod +x deploy-ecr.sh and run ./deploy-ecr.sh. Verify with aws ecr describe-images.

Create the ECS Cluster

Fargate requires an empty ECS cluster. No manual capacity providers needed in beginner mode.

Define the Task Definition

task-definition.json
{
  "family": "fargate-demo",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "executionRoleArn": "arn:aws:iam::YOUR_ACCOUNT:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::YOUR_ACCOUNT:role/ecsTaskRole",
  "containerDefinitions": [
    {
      "name": "demo-app",
      "image": "YOUR_ACCOUNT.dkr.ecr.us-east-1.amazonaws.com/fargate-demo:latest",
      "portMappings": [{ "containerPort": 8080, "protocol": "tcp" }],
      "essential": true,
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/fargate-demo",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ]
}

Valid JSON for aws ecs register-task-definition. Replace YOUR_ACCOUNT with your AWS ID (from the previous script). awsvpc is required for Fargate (native VPC). CPU 256=0.25 vCPU, memory 512MB minimum. Logs go to auto-created CloudWatch.

Create Cluster, Task, and Service

deploy-ecs.sh
#!/bin/bash
CLUSTER_NAME=fargate-cluster
SERVICE_NAME=fargate-service
TASK_FAMILY=fargate-demo
REGION=us-east-1
TASK_REV=$(aws ecs describe-task-definition --task-definition ${TASK_FAMILY} --query 'taskDefinition.revision' --output text)

# Create cluster
aws ecs create-cluster --cluster-name ${CLUSTER_NAME} --capacity-providers FARGATE --default-capacity-provider-strategy capacityProvider=FARGATE --region ${REGION}

# Create log group
aws logs create-log-group --log-group-name /ecs/fargate-demo --region ${REGION}

# Register task def (if not already)
aws ecs register-task-definition --cli-input-json file://task-definition.json

# Create service (no LB for simplicity; add --load-balancers later)
aws ecs create-service --cluster ${CLUSTER_NAME} --service-name ${SERVICE_NAME} --task-definition ${TASK_FAMILY}:${TASK_REV} --desired-count 1 --launch-type FARGATE --platform-version LATEST --network-configuration "awsvpcConfiguration={subnets=[subnet-01234567],securityGroups=[sg-01234567],assignPublicIp=ENABLED}" --region ${REGION}

echo "Service created. Check: aws ecs describe-services --cluster ${CLUSTER_NAME} --services ${SERVICE_NAME}"

All-in-one script: Fargate cluster, logs, task registration, service with public networking (replace subnet/SG IDs from your default VPC). Desired-count=1 for testing. Access via generated public IP. Scale with --desired-count.

Verify the Deployment

Run aws ecs list-tasks --cluster fargate-cluster --output table for the task ARN. Then aws ecs describe-tasks --cluster fargate-cluster --tasks arn... for logs/IP. Visit IP:8080 → 'Hello Fargate!'.

CloudWatch Logs: /ecs/fargate-demo auto-created.

Clean Up Resources

cleanup.sh
#!/bin/bash
CLUSTER_NAME=fargate-cluster
SERVICE_NAME=fargate-service
REPO_NAME=fargate-demo
REGION=us-east-1

aws ecs update-service --cluster ${CLUSTER_NAME} --service ${SERVICE_NAME} --desired-count 0
aws ecs delete-service --cluster ${CLUSTER_NAME} --service ${SERVICE_NAME}
aws ecs delete-cluster --cluster ${CLUSTER_NAME}
aws ecr delete-repository --repository-name ${REPO_NAME} --force
aws logs delete-log-group --log-group-name /ecs/fargate-demo

echo "Resources cleaned up."

Cleanup script to avoid costs: stops service, deletes everything. Run after testing. Check AWS billing console.

Best Practices

  • Use minimal IAM roles: TaskRole for the app, ExecutionRole for ECR/logs/pull secrets.
  • Private networking: awsvpc with private subnets + NAT for production; add ALB for HTTPS.
  • Auto scaling: Set up ECS Service Auto Scaling on CPU/Memory.
  • Secrets/Env vars: Inject via task def secrets or Systems Manager.
  • Monitoring: Enable CloudWatch Container Insights on the cluster.

Common Errors to Avoid

  • Invalid Subnet/SG: Fargate requires awsvpc; ensure default VPC has public subnets with IGW.
  • Image pull failed: Check ECR policy; add ecsTaskExecutionRole with ECR pull perms.
  • Port not exposed: ContainerPort must match app (8080) and healthCheckPath if using ALB.
  • CPU/Memory mismatch: Stick to Fargate combos (256/512, 512/1024, etc.) or validation error.

Next Steps

Master Fargate for production:

  • AWS Fargate Docs
  • Add ALB/NLB: ECS Load Balancing tutorial.
  • CI/CD: GitHub Actions to ECR.

Check out our Learni Dev AWS trainings for advanced ECS/EKS.