Gerson

Gerson

Passionate developer specializing in web development, cloud architecture, and system design.

TypeScriptReactNext.jsPythonFastAPISQLNode.jsAWS

Deploying Next.js Apps to AWS with Docker

A complete guide to containerizing your Next.js application with Docker and deploying it to AWS. Covers multi-stage builds, ECS, and CI/CD with GitHub Actions.

Gerson
Docker containers and cloud deployment

Docker provides a reliable and portable way to deploy Next.js applications. By containerizing your app, you eliminate environment inconsistencies and enable seamless deployments to any cloud provider.

In this guide, we'll containerize a Next.js app and deploy it to AWS using Docker, covering best practices for production-ready deployments.

Why Docker for Next.js?

Docker packages your app with all dependencies, providing:

  • Portability - Deploy anywhere Docker runs (AWS, GCP, Azure, etc.)
  • Consistency - Same environment in development and production
  • Isolation - Dependencies don't conflict with host system
  • Scalability - Easy horizontal scaling with container orchestration

Creating an Optimized Dockerfile

Use multi-stage builds to create lean production images:

Dockerfile

# Stage 1: Dependencies
FROM node:22-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile

# Stage 2: Build
FROM node:22-alpine AS builder
WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NEXT_TELEMETRY_DISABLED 1
RUN corepack enable pnpm && pnpm build

# Stage 3: Production
FROM node:22-alpine AS runner
WORKDIR /app

ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1

# Create non-root user for security
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# Copy only necessary files
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

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

Key Optimization: Use node:22-alpine to reduce image size. The Alpine base image is ~5MB compared to ~900MB for the full Node image.

Enable standalone output in your Next.js config:

next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
};

module.exports = nextConfig;
Cloud infrastructure visualization
Docker containers enable consistent deployments across any cloud infrastructure

Docker Compose for Local Development

docker-compose.yml

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
    depends_on:
      - db

  db:
    image: postgres:17
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

volumes:
  postgres_data:

Deploying to AWS ECS

AWS Elastic Container Service (ECS) is a fully managed container orchestration service. Here's how to deploy:

1. Push to Amazon ECR

First, create an ECR repository and push your image:

Terminal

# Login to ECR
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin \
  123456789.dkr.ecr.us-east-1.amazonaws.com

# Build and tag
docker build -t my-nextjs-app .
docker tag my-nextjs-app:latest \
  123456789.dkr.ecr.us-east-1.amazonaws.com/my-nextjs-app:latest

# Push
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/my-nextjs-app:latest

2. Create ECS Task Definition

task-definition.json

{
  "family": "my-nextjs-app",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "executionRoleArn": "arn:aws:iam::123456789:role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {
      "name": "my-nextjs-app",
      "image": "123456789.dkr.ecr.us-east-1.amazonaws.com/my-nextjs-app:latest",
      "portMappings": [
        {
          "containerPort": 3000,
          "protocol": "tcp"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/my-nextjs-app",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ]
}

GitHub Actions CI/CD

Automate deployments with GitHub Actions:

.github/workflows/deploy.yml

name: Deploy to AWS ECS

on:
  push:
    branches: [main]

env:
  AWS_REGION: us-east-1
  ECR_REPOSITORY: my-nextjs-app
  ECS_SERVICE: my-nextjs-service
  ECS_CLUSTER: my-cluster

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build, tag, and push image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

      - name: Deploy to ECS
        run: |
          aws ecs update-service \
            --cluster $ECS_CLUSTER \
            --service $ECS_SERVICE \
            --force-new-deployment

Security: Never commit AWS credentials. Use GitHub Secrets and IAM roles with minimal required permissions.

Common Issues and Solutions

Port Not Accessible

Ensure Next.js listens on all interfaces:

package.json

{
  "scripts": {
    "start": "next start -H 0.0.0.0"
  }
}

Missing .next Directory

Always run npm run build before npm start in your Dockerfile. The standalone output requires a complete build.

Alternative: AWS App Runner

For simpler deployments, consider AWS App Runner which handles infrastructure automatically:

Terminal

aws apprunner create-service \
  --service-name my-nextjs-app \
  --source-configuration \
    'ImageRepository={ImageIdentifier=123456789.dkr.ecr.us-east-1.amazonaws.com/my-nextjs-app:latest,ImageRepositoryType=ECR}'

Conclusion

Docker and AWS provide a powerful, scalable platform for Next.js deployments. With multi-stage builds, you get small, secure images. With ECS or App Runner, you get managed container orchestration.

Start with App Runner for simplicity, then graduate to ECS when you need more control over networking, scaling, and costs.