前端项目 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 编解码工具,帮你快速校验和转换配置内容。