Aller au contenu principal

Complete Docker Setup

This example shows how to containerize an application with an optimized multi-stage Dockerfile and docker-compose.

Command used

/ops:ops-docker "Dockerize a Next.js application with PostgreSQL"

Generated structure

.
├── Dockerfile # Multi-stage build
├── docker-compose.yml # Dev environment
├── docker-compose.prod.yml # Production
├── .dockerignore # Excluded files
└── scripts/
└── docker-entrypoint.sh

Multi-Stage Dockerfile

Dockerfile

# ============================================
# Stage 1: Dependencies
# ============================================
FROM node:20-alpine AS deps

# System dependencies for node-gyp
RUN apk add --no-cache libc6-compat

WORKDIR /app

# Copy dependency files
COPY package.json package-lock.json* ./

# Install dependencies
RUN npm ci --only=production

# ============================================
# Stage 2: Builder
# ============================================
FROM node:20-alpine AS builder

WORKDIR /app

# Copy dependencies from previous stage
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Build environment variables
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL

# Disable Next.js telemetry
ENV NEXT_TELEMETRY_DISABLED=1

# Build the application
RUN npm run build

# ============================================
# Stage 3: Runner (Production)
# ============================================
FROM node:20-alpine AS runner

WORKDIR /app

# Create a non-root user
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs

# Copy required files
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json

# Copy the Next.js build with the right permissions
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# Runtime environment variables
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

# Switch user
USER nextjs

# Expose the port
EXPOSE 3000

# Healthcheck
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1

# Start command
CMD ["node", "server.js"]

.dockerignore

# Dependencies
node_modules
.pnp
.pnp.js

# Build
.next
out
build
dist

# Testing
coverage

# Git
.git
.gitignore

# IDE
.idea
.vscode
*.swp
*.swo

# Logs
*.log
npm-debug.log*

# Environment
.env
.env.*
!.env.example

# Docker
Dockerfile*
docker-compose*
.docker

# Documentation
README.md
docs

# Tests
__tests__
*.test.ts
*.spec.ts
jest.config.*

# Misc
.DS_Store
*.tgz

Docker Compose

docker-compose.yml (Development)

version: '3.8'

services:
# Next.js application
app:
build:
context: .
dockerfile: Dockerfile
target: deps # Use the deps stage for dev
image: myapp:dev
container_name: myapp-dev
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://postgres:postgres@db:5432/myapp_dev
- REDIS_URL=redis://redis:6379
volumes:
- .:/app
- /app/node_modules
- /app/.next
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
command: npm run dev
networks:
- myapp-network

# PostgreSQL
db:
image: postgres:16-alpine
container_name: myapp-db
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: myapp_dev
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
networks:
- myapp-network

# Redis (cache and sessions)
redis:
image: redis:7-alpine
container_name: myapp-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
networks:
- myapp-network

# Adminer (DB interface)
adminer:
image: adminer:latest
container_name: myapp-adminer
ports:
- "8080:8080"
environment:
ADMINER_DEFAULT_SERVER: db
depends_on:
- db
networks:
- myapp-network
profiles:
- debug

volumes:
postgres_data:
redis_data:

networks:
myapp-network:
driver: bridge

docker-compose.prod.yml (Production)

version: '3.8'

services:
app:
image: ${REGISTRY}/myapp:${VERSION:-latest}
container_name: myapp-prod
restart: unless-stopped
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
- NEXTAUTH_URL=${NEXTAUTH_URL}
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
deploy:
replicas: 2
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
- myapp-network

# Nginx reverse proxy (optional)
nginx:
image: nginx:alpine
container_name: myapp-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- myapp-network

networks:
myapp-network:
driver: bridge

Useful scripts

scripts/docker-entrypoint.sh

#!/bin/sh
set -e

echo "🔄 Running database migrations..."
npx prisma migrate deploy

echo "🌱 Seeding database (if needed)..."
npx prisma db seed || true

echo "🚀 Starting application..."
exec "$@"

Makefile

.PHONY: build dev prod down logs clean

# Build the production image
build:
docker build -t myapp:latest .

# Start the development environment
dev:
docker-compose up -d
docker-compose logs -f app

# Start with debug tools (Adminer)
dev-debug:
docker-compose --profile debug up -d
docker-compose logs -f app

# Start production
prod:
docker-compose -f docker-compose.prod.yml up -d

# Stop all containers
down:
docker-compose down
docker-compose -f docker-compose.prod.yml down 2>/dev/null || true

# View logs
logs:
docker-compose logs -f

# Clean up (volumes included)
clean:
docker-compose down -v
docker system prune -f

# Rebuild without cache
rebuild:
docker-compose build --no-cache
docker-compose up -d

# Run migrations
migrate:
docker-compose exec app npx prisma migrate dev

# Shell into the container
shell:
docker-compose exec app sh

# Tests in the container
test:
docker-compose exec app npm test

Next.js Configuration

next.config.js (for standalone output)

/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone', // Required for the optimized Dockerfile

// Production optimizations
poweredByHeader: false,
compress: true,

// Images
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**.example.com',
},
],
},

// Environment
env: {
NEXT_PUBLIC_APP_VERSION: process.env.npm_package_version,
},
};

module.exports = nextConfig;

Health Check Endpoint

// pages/api/health.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { prisma } from '@/lib/prisma';

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
// Check the DB connection
await prisma.$queryRaw`SELECT 1`;

res.status(200).json({
status: 'healthy',
timestamp: new Date().toISOString(),
version: process.env.NEXT_PUBLIC_APP_VERSION,
});
} catch (error) {
res.status(503).json({
status: 'unhealthy',
error: 'Database connection failed',
});
}
}

Key points

AspectImplementation
Multi-stage3 stages: deps → builder → runner
Image size~150MB (vs 1GB+ without optimization)
SecurityNon-root user, minimal files
Healthcheck/api/health endpoint
Dev/ProdSeparate docker-compose files
  • /ops:ops-ci - CI pipeline with Docker build
  • /ops:ops-k8s - Kubernetes deployment
  • /qa:qa-security - Image vulnerability scan

Security scan

Scan your image with Trivy:

trivy image myapp:latest