开发工具2026-03-19 12 分钟

前端项目 Docker 部署指南:从 Dockerfile 到 CI/CD

手把手教你用 Docker 部署前端项目,涵盖多阶段构建、Nginx 配置、环境变量注入、CI/CD 流水线搭建等完整流程。

以前部署前端项目就是 npm run build 然后把 dist 文件夹扔到服务器上,简单粗暴。直到有一次,测试环境和生产环境的 Node 版本不一样,构建产物居然不同,debug 了一整天才发现问题。自从用了 Docker,「我本地是好的」这句话再也不用说了——因为本地和线上跑的是完全一样的环境。

很多前端同学觉得 Docker 是后端的事,跟自己没关系。但现在越来越多的公司要求前端也要会写 Dockerfile,至少得看得懂 CI/CD 流水线。特别是做 SSR 项目(Nuxt、Next.js),不会 Docker 真的寸步难行。与其被动学习,不如主动掌握,反正也不难。

1. 为什么前端需要 Docker?

Docker 对前端项目的价值不仅仅是「容器化」这么简单,它解决了几个核心痛点:

环境一致性

Node 版本、npm/pnpm 版本、系统依赖全部锁定在 Dockerfile 中,消除「我本地是好的」问题。

构建可复现

同一个 Dockerfile + 同一个 commit,无论在哪台机器上构建,产物都完全一致。

部署简单

一个 docker pull + docker run 就能在任何安装了 Docker 的服务器上启动应用。

快速回滚

每个版本都是一个镜像,回滚只需要切换到上一个镜像版本,秒级完成。

2. 多阶段构建:从 1.2GB 到 25MB

前端 Docker 镜像最常见的问题就是太大。如果你把 node_modules 也打包进去,镜像轻松超过 1GB。多阶段构建(multi-stage build)是解决这个问题的关键。

SPA 项目的标准 Dockerfile

# ===== 阶段 1:构建 =====
FROM node:20-alpine AS builder

WORKDIR /app

# 先复制依赖文件,利用 Docker 层缓存
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile

# 再复制源码并构建
COPY . .
RUN pnpm build

# ===== 阶段 2:运行 =====
FROM nginx:alpine

# 只复制构建产物,不带 node_modules
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

node:20 (1.1GB) + node_modules (200MB) nginx:alpine (25MB) + dist (几 MB)

镜像体积从 1.3GB 降到不到 30MB

关键技巧:先复制 package.json 和 lock 文件,再 pnpm install,最后才复制源码。这样当只有源码变化时,Docker 会利用层缓存跳过依赖安装步骤,大幅加速构建。

3. Nginx 配置:SPA 路由与性能优化

前端 SPA 应用在 Docker 中通常用 Nginx 来提供服务。一个经过优化的 Nginx 配置至关重要:

server {
    listen 80;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;

    # SPA 路由:所有路径回退到 index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 静态资源长期缓存(带 hash 的文件)
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Gzip 压缩
    gzip on;
    gzip_types text/plain text/css application/json
               application/javascript text/xml application/xml
               application/xml+rss text/javascript image/svg+xml;
    gzip_min_length 1000;

    # 安全头
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
}

几个要点:try_files 是 SPA 路由的关键,没有它刷新页面就会 404;带 hash 的静态资源可以设置长期缓存(因为内容变了 hash 也会变);Gzip 可以将文本资源的传输体积减少 60-80%。

4. 环境变量:构建时 vs 运行时

前端项目的环境变量处理是 Docker 化中最容易出错的地方。核心问题是:前端代码在构建时就已经把环境变量「烧」进了 JS 文件中,运行时无法更改。

构建时注入(常见方式)

# Dockerfile 中使用 ARG
ARG VITE_API_URL
ENV VITE_API_URL=$VITE_API_URL

RUN pnpm build

# 构建时传入
docker build \
  --build-arg VITE_API_URL=https://api.prod.com \
  -t my-app .

缺点:每个环境需要单独构建镜像

运行时注入(推荐)

# entrypoint.sh
#!/bin/sh
# 将环境变量写入 JS 文件
cat <<EOF > /usr/share/nginx/html/env.js
window.__ENV__ = {
  API_URL: "${API_URL}",
  ENV: "${NODE_ENV}"
}
EOF
nginx -g "daemon off;"

# 运行时传入
docker run \
  -e API_URL=https://api.prod.com \
  -e NODE_ENV=production \
  my-app

优点:一次构建,多环境运行

运行时注入的方式虽然稍微复杂一点,但好处是「一个镜像走天下」——同一个镜像可以在开发、测试、预发布、生产等不同环境运行,只需要传不同的环境变量。

5. CI/CD 流水线:自动构建与部署

有了 Dockerfile,接下来就是把构建和部署自动化。以下是一个完整的 GitHub Actions 流水线示例:

.github/workflows/deploy.yml

name: Build and Deploy

on:
  push:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Login 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
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
            docker stop my-app || true
            docker rm my-app || true
            docker run -d --name my-app \
              -p 80:80 \
              -e API_URL=${{ secrets.API_URL }} \
              ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest

这个流水线做了以下事情:

  • 自动触发:代码推送到 main 分支时自动开始
  • 构建镜像:使用 Docker Buildx 构建并推送到 GitHub Container Registry
  • 双标签:同时打 latest 和 commit hash 标签,方便回滚
  • 层缓存:利用 GitHub Actions Cache 缓存 Docker 层,加速后续构建
  • 自动部署:SSH 到服务器拉取新镜像并重启容器

开发部署好帮手

在配置 CI/CD 流水线时经常需要处理 YAML、JSON 等配置文件。OneKit 提供了 JSON 格式化工具Base64 编解码工具,帮你快速校验和转换配置内容。