Plaintext Engineering
0%

🚀 Deploy Next.js with Docker, Traefik, SSL, and CI/CD

Aug 2, 2025 • 4 min read

This tutorial walks you through deploying a production‑ready Next.js application using Docker and Docker Compose on a fresh cloud server. You’ll set up a reverse proxy with Traefik, enable automatic SSL via Let’s Encrypt, and wire up a basic Jenkins CI/CD pipeline for tagged deployments.

What You Will Achieve

Server Comparisons: EC2 vs Droplet vs GCP

ProviderEase of UseCost (approx. monthly)Notes
Amazon EC2Moderate (AWS Console)$8–$15 (t3.micro)Rich features, more complex UI
DigitalOcean DropletVery easy (simple UI)$5–$15Developer‑friendly, predictable pricing
Google Cloud (GCP)Moderate$7–$15Strong integrations, sometimes complex

For this guide, we’ll use a DigitalOcean Droplet for simplicity and predictable pricing.

Step 1: Set up the server and install Docker + Compose

SSH into your server (Ubuntu 22.04 example):

ssh root@your_droplet_ip

Update and install prerequisites:

apt update && apt upgrade -y
apt install -y curl apt-transport-https ca-certificates software-properties-common

Install Docker:

curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
usermod -aG docker ${USER}
newgrp docker
docker --version

Install Docker Compose v2:

mkdir -p ~/.docker/cli-plugins/
curl -SL https://github.com/docker/compose/releases/download/v2.18.1/docker-compose-linux-x86_64 \
  -o ~/.docker/cli-plugins/docker-compose
chmod +x ~/.docker/cli-plugins/docker-compose
docker compose version

Step 2: Create an optimized Dockerfile for Next.js

Save this Dockerfile in your Next.js project root:

# Stage 1: Build
FROM node:20-alpine AS builder

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

COPY . .
RUN npm run build

# Stage 2: Production image
FROM node:20-alpine

WORKDIR /app
ENV NODE_ENV=production

COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/node_modules ./node_modules

EXPOSE 3000
CMD ["npm", "start"]

Key points:

Step 3: Configure Docker Compose with Traefik + TLS

Create docker-compose.yml in the project root:

version: "3.8"

services:
  nextjs:
    build: .
    container_name: nextjs_app
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nextjs.rule=Host(`example.com`)"
      - "traefik.http.routers.nextjs.entrypoints=websecure"
      - "traefik.http.routers.nextjs.tls.certresolver=myresolver"
      - "traefik.http.services.nextjs.loadbalancer.server.port=3000"
    networks:
      - web

  traefik:
    image: traefik:v2.11
    container_name: traefik_proxy
    restart: always
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.httpchallenge=true"
      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.myresolver.acme.email=your-email@example.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"   # Traefik dashboard (optional)
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "traefik_letsencrypt:/letsencrypt"
    networks:
      - web

networks:
  web:
    external: true

volumes:
  traefik_letsencrypt:

Notes:

docker network create web

Step 4: Domain setup and SSL

Step 5: Deployment strategies

Option A: Build & push a Docker image

docker build -t your-registry/nextjs-app:latest .
docker push your-registry/nextjs-app:latest

# On the server
docker pull your-registry/nextjs-app:latest
docker compose up -d

Option B: Build on the server

# Push code to the server (git/rsync/etc), then
docker compose up -d --build

Step 6: Keep it running and view logs

systemctl enable docker
docker logs -f nextjs_app

Step 7: Jenkins CI/CD pipeline (basic example)

pipeline {
  agent any

  environment {
    REGISTRY = "your-registry/nextjs-app"
  }

  stages {
    stage('Checkout') {
      steps { checkout scm }
    }

    stage('Build and Push') {
      steps {
        script {
          docker.build(env.REGISTRY + ":${env.GIT_TAG}").push()
        }
      }
    }

    stage('Deploy') {
      steps {
        sshagent(['server-ssh']) {
          sh '''
            ssh root@your_droplet_ip \
              "docker pull ${env.REGISTRY}:${env.GIT_TAG} && docker-compose up -d"
          '''
        }
      }
    }
  }

  triggers { pollSCM('H/5 * * * *') }

  post {
    success { echo "Deployment successful for tag ${env.GIT_TAG}" }
    failure { echo "Deployment failed" }
  }
}

Summary: key commands

TaskCommand/snippet
Create Docker networkdocker network create web
Build Docker imagedocker build -t your-registry/nextjs-app:latest .
Push Docker imagedocker push your-registry/nextjs-app:latest
Deploy with Composedocker compose up -d or docker compose up -d --build
Enable Docker on startupsystemctl enable docker
View container logsdocker logs -f nextjs_app

This end‑to‑end approach gives you a reproducible, secure deployment with HTTPS, easy rollouts via Compose, and a simple CI/CD pipeline to automate builds and releases.

Sources

Related articles