前言

在现代Web开发中,Next.js凭借其服务端渲染(SSR)、静态站点生成(SSG)和API路由等特性成为React开发的首选框架。而Docker容器化技术则为应用部署提供了环境一致性、隔离性和可移植性的解决方案。本文将详细介绍如何通过Docker多阶段构建优化Next.js应用镜像,并结合Nginx反向代理SSL证书配置PM2进程管理,实现生产级别的服务器部署。

部署架构将采用"Next.js应用容器 + Nginx反向代理容器"的双容器模式,通过Docker Compose编排服务,最终实现HTTPS访问、静态资源缓存和服务高可用。这种架构已在实际项目中验证(如easynomad.cn),可支持日均10万+请求的稳定运行。

一、环境准备:本地开发与服务器环境配置

1.1 本地开发环境要求

开始前需确保本地环境已安装以下工具:

  • Docker Engine (20.10+): 用于构建和运行容器,可通过Docker官方文档安装
  • Docker Compose (v2+): 用于多容器编排,通常随Docker Desktop一起安装
  • Node.js (18.x+): 本地开发Next.js项目,建议使用LTS版本
  • Git: 版本控制与代码传输

验证安装:

docker --version       # 应显示Docker版本信息
docker compose version # 应显示Compose版本信息
node -v                # 应显示v18.x.x或更高版本

1.2 服务器环境准备(以CentOS 7为例)

生产服务器需完成以下基础配置:

1.2.1 系统依赖安装

# 更新系统包
sudo yum update -y

# 安装Docker依赖
sudo yum install -y yum-utils device-mapper-persistent-data lvm2

# 添加Docker官方仓库
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# 安装Docker引擎
sudo yum install docker-ce docker-ce-cli containerd.io -y

# 启动Docker服务并设置开机自启
sudo systemctl start docker
sudo systemctl enable docker

# 配置当前用户权限(避免每次使用sudo)
sudo usermod -aG docker $USER
newgrp docker  # 立即应用权限

1.2.2 Docker镜像加速配置

为提高国内服务器镜像拉取速度,配置镜像加速器:

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://mirror.ccs.tencentyun.com",
    "https://registry.docker-cn.com",
    "https://docker.mirrors.ustc.edu.cn"
  ]
}
EOF

# 重启Docker服务使配置生效
sudo systemctl daemon-reload
sudo systemctl restart docker

1.2.3 辅助工具安装

# 安装Git(用于拉取代码)
sudo yum install git -y

# 安装acme.sh(用于申请SSL证书)
curl https://get.acme.sh | sh -s email=your-email@example.com
source ~/.bashrc  # 刷新环境变量

1.3 Ubuntu 22.04/Debian 12

1.3.1 基础依赖安装
# 更新系统包
sudo apt update && sudo apt upgrade -y

# 安装基础工具
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common git
1.3.2 Docker安装
# 卸载旧版本
sudo apt remove docker docker-engine docker.io containerd runc

# 添加Docker官方GPG密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# 添加Docker仓库
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装Docker引擎
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

# 配置非root用户权限
sudo usermod -aG docker $USER
newgrp docker  # 立即生效用户组配置

# 配置镜像加速器
sudo tee /etc/docker/daemon.json <<EOF
{
  "registry-mirrors": [
    "https://mirror.ccs.tencentyun.com",
    "https://docker.mirrors.ustc.edu.cn"
  ]
}
EOF

# 重启Docker服务
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl enable docker
1.3.3 Git配置
# 安装Git
sudo apt install -y git-all

# 配置用户信息
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"

# 生成SSH密钥(用于GitHub/GitLab访问)
ssh-keygen -t ed25519 -C "your.email@example.com"
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
cat ~/.ssh/id_ed25519.pub  # 复制公钥到代码仓库
1.3.4 UFW防火墙配置
# 安装并启用UFW
sudo apt install -y ufw
sudo ufw enable

# 开放必要端口
sudo ufw allow ssh/tcp     # SSH
sudo ufw allow 80/tcp      # HTTP
sudo ufw allow 443/tcp     # HTTPS
sudo ufw allow 3000/tcp    # Next.js应用

# 设置默认策略
sudo ufw default deny incoming
sudo ufw default allow outgoing

# 查看规则
sudo ufw status numbered

二、项目配置:Next.js应用优化与环境变量管理

2.1 Next.js项目结构与关键配置

2.1.1 推荐目录结构

your-next-project/
├── app/                # App Router目录(Next.js 13+)
├── public/             # 静态资源
├── prisma/             # 数据库配置(如使用Prisma ORM)
├── .env.production     # 生产环境变量(不提交到Git)
├── .dockerignore       # Docker构建排除文件
├── docker-compose.yml  # Docker服务编排配置
├── Dockerfile          # 应用镜像构建文件
├── next.config.js      # Next.js核心配置
└── package.json        # 项目依赖

2.1.2 next.config.js优化配置

为实现容器化部署,需在next.config.js中添加独立输出模式配置,该模式会生成包含所有依赖的独立应用包,大幅减小生产镜像体积:

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',  // 关键配置:生成独立部署包
  poweredByHeader: false, // 移除X-Powered-By头
  compress: true,        // 启用Gzip压缩
  images: {
    domains: ['assets.example.com'], // 允许加载的图片域名
    minimumCacheTTL: 60 * 60 * 24,   // 静态图片缓存1天
  },
  // 可选:添加构建分析工具(需安装@next/bundle-analyzer)
  // experimental: {
  //   optimizeCss: true, // 优化CSS
  // }
}

module.exports = nextConfig

2.2 环境变量管理

2.2.1 环境变量文件配置

创建.env.production文件存储生产环境变量,切勿提交到代码仓库

# .env.production
NODE_ENV=production
NEXTAUTH_SECRET=your-secure-random-secret  # 用于NextAuth.js加密
NEXTAUTH_URL=https://your-domain.com       # 应用访问URL
DATABASE_URL=file:/app/data/prod.db        # SQLite数据库路径(如使用)
API_URL=https://api.your-domain.com        # 后端API地址

2.2.2 环境变量使用规范

  • 客户端环境变量:必须以NEXT_PUBLIC_为前缀,如NEXT_PUBLIC_API_URL,构建时会被注入客户端代码
  • 服务端环境变量:无需前缀,仅在服务端代码中可用
  • 敏感信息处理:数据库密码、API密钥等敏感信息通过Docker Compose或服务器环境变量注入,不写入代码

三、容器化构建:Dockerfile与Docker Compose配置

3.1 多阶段Dockerfile编写(生产优化版)

采用多阶段构建策略,将构建环境与运行环境分离,最终镜像仅包含运行时必要文件。以下是针对Next.js 14+优化的Dockerfile:

# 阶段1:基础镜像(复用依赖层缓存)
FROM node:18-alpine AS base
# 安装Alpine系统依赖(如需Sharp图像处理库)
RUN apk add --no-cache libc6-compat

# 阶段2:安装依赖
FROM base AS deps
WORKDIR /app
# 复制依赖配置文件
COPY package.json pnpm-lock.yaml* ./
# 启用Corepack并安装pnpm(高效包管理器)
RUN corepack enable pnpm && pnpm i --frozen-lockfile

# 阶段3:构建应用
FROM base AS builder
WORKDIR /app
# 复制依赖
COPY --from=deps /app/node_modules ./node_modules
# 复制项目文件
COPY . .
# 构建应用(使用环境变量)
RUN pnpm run build

# 阶段4:生产运行时
FROM base AS runner
WORKDIR /app
# 创建非root用户并设置权限
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# 设置生产环境变量
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
# 复制静态资源和构建产物
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
# 切换到非root用户运行
USER nextjs
# 暴露端口
EXPOSE 3000
# 启动命令
CMD ["node", "server.js"]

关键优化点

  • 使用node:18-alpine基础镜像,比完整版小90%以上
  • 分离依赖安装与代码构建,充分利用Docker缓存
  • 仅复制运行时必要文件(通过output: 'standalone'实现)
  • 使用非root用户运行容器,提升安全性
  • 安装libc6-compat解决Alpine系统兼容性问题

3.2 .dockerignore文件配置

创建.dockerignore文件排除不必要文件,加速构建并减小镜像体积:

# 依赖目录
node_modules
.pnp
.pnp.js

# 构建产物
.next/
out/

# 环境变量文件
.env*
!.env.production  # 仅保留生产环境变量模板(可选)

# 日志和缓存
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
.cache/

# 版本控制
.git
.gitignore

# Docker相关
Dockerfile
docker-compose.yml
.dockerignore

# 编辑器配置
.idea
.vscode
*.swp
*.swo

3.3 Docker Compose配置(多服务编排)

创建docker-compose.yml文件定义应用服务和Nginx服务:

version: '3.8'

services:
  # Next.js应用服务
  nextjs:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped  # 异常退出时自动重启
    environment:
      - NODE_ENV=production
      - NEXTAUTH_URL=https://your-domain.com
      # 其他环境变量可在此添加或通过.env文件挂载
    volumes:
      - nextjs_data:/app/data  # 持久化数据(如SQLite数据库)
    networks:
      - app_network
    healthcheck:  # 健康检查
      test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # Nginx反向代理服务
  nginx:
    image: nginx:1.27.3-alpine
    ports:
      - "80:80"   # HTTP端口
      - "443:443" # HTTPS端口
    volumes:
      - ./nginx/conf:/etc/nginx/conf.d  # Nginx配置
      - ./nginx/ssl:/etc/nginx/ssl      # SSL证书
      - ./nginx/logs:/var/log/nginx     # 日志文件
    depends_on:
      - nextjs  # 依赖nextjs服务启动
    restart: unless-stopped
    networks:
      - app_network

# 持久化数据卷
volumes:
  nextjs_data:

# 内部网络(服务间通信)
networks:
  app_network:
    driver: bridge

四、Nginx反向代理与HTTPS配置

4.1 Nginx配置文件编写

创建Nginx配置目录及文件:mkdir -p nginx/conf,然后创建nginx/conf/default.conf

# nginx/conf/default.conf
events {}

http {
  # 日志格式配置
  log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for"';

  access_log /var/log/nginx/access.log main;
  error_log /var/log/nginx/error.log warn;

  # 静态资源缓存配置
  proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=nextjs_cache:10m max_size=10g
                   inactive=60m use_temp_path=off;

  # 上游服务(Next.js容器)
  upstream nextjs_upstream {
    server nextjs:3000;  # Docker Compose服务名:端口
  }

  # HTTP服务器(重定向到HTTPS)
  server {
    listen 80;
    listen [::]:80;
    server_name your-domain.com www.your-domain.com;

    # 所有HTTP请求重定向到HTTPS
    return 301 https://$host$request_uri;
  }

  # HTTPS服务器
  server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name your-domain.com www.your-domain.com;

    # SSL证书配置
    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # 静态资源缓存配置
    location /_next/static/ {
      proxy_pass http://nextjs_upstream/_next/static/;
      proxy_set_header Host $host;
      proxy_cache nextjs_cache;
      proxy_cache_valid 200 304 365d;  # 缓存静态资源365天
      proxy_cache_use_stale error timeout invalid_header updating;
      expires 365d;
      add_header Cache-Control "public, max-age=31536000, immutable";
    }

    # 图片优化服务
    location /_next/image/ {
      proxy_pass http://nextjs_upstream/_next/image/;
      proxy_set_header Host $host;
      proxy_cache nextjs_cache;
      proxy_cache_valid 200 304 7d;  # 缓存图片7天
    }

    # API路由和页面请求
    location / {
      proxy_pass http://nextjs_upstream;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection 'upgrade';
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_cache_bypass $http_upgrade;
    }
  }
}

4.2 SSL证书申请与配置(使用acme.sh)

通过acme.sh申请Let’s Encrypt免费SSL证书:

# 申请证书(需提前解析域名到服务器IP)
acme.sh --issue -d your-domain.com -d www.your-domain.com --standalone

# 创建证书目录
mkdir -p ./nginx/ssl

# 安装证书到Nginx目录
acme.sh --install-cert -d your-domain.com \
  --key-file ./nginx/ssl/privkey.pem \
  --fullchain-file ./nginx/ssl/fullchain.pem \
  --reloadcmd "docker compose restart nginx"  # 证书更新时重启Nginx

# 设置自动续期(acme.sh默认已配置)
acme.sh --cron

五、服务器部署流程

5.1 服务器代码准备

# 1. 登录服务器(替换为你的服务器IP)
ssh user@your-server-ip

# 2. 创建项目目录
mkdir -p /var/www/nextjs-app && cd /var/www/nextjs-app

# 3. 克隆代码仓库(或通过SCP传输项目文件)
git clone https://github.com/your-username/your-nextjs-project.git .

# 4. 创建环境变量文件(根据模板)
cp .env.example .env.production
# 编辑.env.production设置实际环境变量
nano .env.production

5.2 构建与启动容器

# 1. 构建镜像并启动服务(首次运行或配置变更时)
docker compose up -d --build

# 2. 查看服务状态
docker compose ps

# 3. 查看应用日志
docker compose logs -f nextjs

# 4. 查看Nginx日志
docker compose logs -f nginx

5.3 数据库初始化(如使用SQLite+Prisma)

如果应用使用SQLite数据库,需初始化数据卷和迁移:

# 1. 创建数据目录并设置权限
docker compose exec nextjs mkdir -p /app/data
docker compose exec nextjs chown -R node:node /app/data

# 2. 应用数据库迁移
docker compose run --rm nextjs npx prisma migrate deploy

# 3. 可选:初始化种子数据
docker compose run --rm nextjs npx prisma db seed

5.4 部署验证

访问以下地址验证部署结果:

  • https://your-domain.com: 应用主页
  • https://your-domain.com/api/health: 健康检查接口(需自行实现)
  • https://your-domain.com/_next/static/: 静态资源访问

六、进程管理与监控

6.1 PM2进程管理集成(可选)

对于需要更精细进程管理的场景,可在Docker中集成PM2。修改Dockerfile的最后阶段:

# 阶段4:生产运行时(带PM2)
FROM base AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# 安装PM2
RUN npm install pm2 -g

# 创建PM2配置文件
COPY <<EOF /app/ecosystem.config.js
module.exports = {
  apps: [{
    name: 'nextjs',
    script: 'server.js',
    instances: 'max',  # 使用所有CPU核心
    exec_mode: 'cluster',  # 集群模式
    env: {
      NODE_ENV: 'production',
      PORT: 3000,
      HOSTNAME: '0.0.0.0'
    },
    max_memory_restart: '512M',  # 内存超过512M时重启
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
  }]
};
EOF

# 复制构建产物(同前)
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
CMD ["pm2-runtime", "ecosystem.config.js"]  # 使用PM2启动

6.2 容器与应用监控

# 查看容器资源使用情况
docker stats

# 查看应用详细日志(带PM2时)
docker compose exec nextjs pm2 logs

# 查看Nginx访问日志
tail -f /var/www/nextjs-app/nginx/logs/access.log

# 设置日志轮转(防止日志文件过大)
sudo nano /etc/logrotate.d/nextjs-nginx

日志轮转配置示例:

/var/www/nextjs-app/nginx/logs/*.log {
  daily
  missingok
  rotate 14
  compress
  delaycompress
  notifempty
  create 0640 root root
}

七、常见问题与最佳实践

7.1 常见问题解决方案

7.1.1 容器启动失败

# 1. 查看详细错误日志
docker compose logs --tail=100 nextjs

# 2. 检查环境变量配置
docker compose exec nextjs env | grep -i database_url

# 3. 检查数据卷权限
docker compose exec nextjs ls -la /app/data

7.1.2 静态资源无法加载

  • 检查Nginx配置中/_next/static/路径的缓存配置
  • 确认next.config.jsoutput: 'standalone'已正确设置
  • 检查容器内文件权限:docker compose exec nextjs ls -la ./.next/static

7.1.3 SSL证书申请失败

  • 确保80端口未被占用:sudo lsof -i :80
  • 检查域名解析:nslookup your-domain.com
  • 临时关闭防火墙:sudo systemctl stop firewalld(测试后重新启用)

7.2 生产环境最佳实践

  1. 镜像优化

    • 使用Alpine基础镜像减小体积
    • 合理排序Dockerfile指令,利用缓存层
    • 通过.dockerignore排除不必要文件
  2. 安全加固

    • 使用非root用户运行容器
    • 限制容器CPU/内存资源:在docker-compose.yml中添加deploy: resources配置
    • 定期更新基础镜像和依赖包
    • 启用Docker Content Trust验证镜像签名
  3. 性能优化

    • 配置Nginx静态资源缓存和Gzip压缩
    • 启用Next.js图像优化(next/image
    • 对API路由添加Redis缓存(如使用)
    • 合理设置Node.js内存限制:NODE_OPTIONS=--max-old-space-size=4096
  4. 部署自动化

    • 使用GitHub Actions或GitLab CI/CD自动构建镜像
    • 实现蓝绿部署或滚动更新(通过Docker Compose或Kubernetes)
    • 配置部署钩子自动拉取代码并重启服务

八、总结

通过本文介绍的步骤,你已掌握将Next.js项目通过Docker容器化并部署到生产服务器的完整流程。关键要点包括:

  • 环境一致性:Docker确保开发与生产环境一致,减少"在我电脑上能运行"问题
  • 镜像优化:多阶段构建和output: 'standalone'配置大幅减小镜像体积
  • 生产可靠性:Nginx反向代理提供HTTPS支持和静态资源缓存,PM2实现进程守护
  • 可扩展性:Docker Compose便于添加数据库、Redis等服务,为未来扩展奠定基础

这种部署架构已在实际项目中验证,可满足中小规模应用的生产需求。随着业务增长,可平滑迁移至Kubernetes等更强大的容器编排平台。

祝你部署顺利!如有问题,可参考Next.js官方文档的部署指南或Docker的最佳实践文档

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐