Docker và CI/CD Pipeline cho ứng dụng Next.js
Hướng dẫn đầy đủ containerise ứng dụng Next.js với Docker multi-stage build và thiết lập CI/CD pipeline tự động với GitHub Actions.
Containerisation và CI/CD là hai trụ cột của DevOps hiện đại. Kết hợp Docker với GitHub Actions giúp tự động hoá hoàn toàn từ code commit đến production deployment, giảm thiểu lỗi human error và rút ngắn release cycle.
1. Dockerfile tối ưu cho Next.js
Multi-stage build giúp giảm image size từ ~1GB xuống còn ~150MB.
# Dockerfile
# Stage 1: Dependencies
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
# Stage 2: Builder
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Tắt telemetry
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
# Stage 3: Runner (production image)
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
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 --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
Bật standalone output trong next.config.ts:
const nextConfig = {
output: 'standalone',
};
export default nextConfig;
2. Docker Compose cho môi trường local
# docker-compose.yml
services:
app:
build:
context: .
dockerfile: Dockerfile
target: runner
ports:
- "3000:3000"
environment:
- DATABASE_URL=${DATABASE_URL}
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 10s
timeout: 5s
retries: 5
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- app
volumes:
postgres_data:
3. GitHub Actions CI/CD Pipeline
# .github/workflows/deploy.yml
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Type check
run: npm run typecheck
- name: Lint
run: npm run lint
- name: Build
run: npm run build
env:
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
build-and-push:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build-and-push
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to VPS via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /opt/app
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f
echo "Deploy completed at $(date)"
4. Secrets Management
Không bao giờ hardcode secrets vào Dockerfile hay docker-compose. Dùng GitHub Secrets cho CI/CD và .env files cho local (đã có trong .gitignore).
# Thêm secrets vào GitHub repo
gh secret set DATABASE_URL --body "postgresql://..."
gh secret set VPS_SSH_KEY < ~/.ssh/id_rsa
gh secret set NEXTAUTH_SECRET --body "$(openssl rand -base64 32)"
5. Health Check và Rollback
# Thêm health check vào docker-compose.yml
services:
app:
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
// app/api/health/route.ts
export async function GET() {
return Response.json({
status: 'ok',
timestamp: new Date().toISOString(),
version: process.env.npm_package_version,
});
}
Script deploy với auto-rollback:
#!/bin/bash
# deploy.sh
set -e
PREVIOUS_IMAGE=$(docker inspect app --format='{{.Image}}' 2>/dev/null || echo "")
docker compose pull
docker compose up -d --remove-orphans
# Kiểm tra health sau 30 giây
sleep 30
if ! curl -sf http://localhost:3000/api/health; then
echo "Health check failed, rolling back..."
docker compose down
docker tag $PREVIOUS_IMAGE ghcr.io/org/app:latest
docker compose up -d
exit 1
fi
echo "Deploy successful"
Kết luận
Pipeline CI/CD với Docker và GitHub Actions giúp team tự tin deploy nhiều lần mỗi ngày. Với cấu hình trên, mỗi pull request tự động chạy tests, build Docker image và deploy lên production trong vòng 3-5 phút. Ventra Rocket áp dụng pipeline này cho tất cả dự án production của khách hàng.
Bài viết liên quan
Hướng dẫn Deploy Node.js lên Kubernetes
Từng bước deploy Node.js lên Kubernetes — Deployments, Services, HPA, health checks, và zero-downtime rollouts.
Kiến trúc Microservices với Docker và Message Queue
Design patterns microservices — phân rã service, giao tiếp async với RabbitMQ, circuit breaker, distributed tracing và observability.
CI/CD với GitHub Actions: Pipeline Build, Test và Deploy
Tự động hóa quy trình phát triển phần mềm với GitHub Actions — pipeline, testing, Docker build, deployment và quản lý secrets.