前端部署这事儿我一开始也想过得简单了。以为跑个`npm run build`,然后扔到服务器上就行。结果呢?同步卡壳、版本混乱、回滚全靠手速——踩过的坑,一个没落下。
后来我花了两个晚上,用GitHub Actions配合Docker和Nginx搞了一套自动化流程。现在从代码push到线上更新,全程不到3分钟。真的,再也没出过幺蛾子。下面是我的配置,你可以参考。
(这里你想象一张流程图:开发者Push代码 → GitHub Actions触发 → Docker构建镜像 → 推送镜像到Registry → 服务器拉取镜像并部署 → Nginx反向代理)
先来看核心:为啥选GitHub Actions + Docker。工具很多,比如Jenkins、GitLab CI、Travis CI……但我选了GitHub Actions,说白了就一个原因:**不用额外维护**。你的代码在GitHub上,CI/CD也在GitHub上,省去搭服务器跑Jenkins的麻烦。你懂的。
至于Docker,它解决了最让我头疼的“环境不一致”问题。以前开发环境跑得好好的,部署到服务器就404,查了半天发现是Nginx配置少了个`try_files`。用Docker后,整个运行环境打包进镜像,服务器上拉起来就跑,再也不用重复排查环境差异。
来看看一个典型的项目结构:
““
my-app/
├── .github/
│ └── workflows/
│ └── deploy.yml # GitHub Actions 配置
├── docker/
│ ├── Dockerfile # 生产环境镜像构建
│ └── nginx.conf # Nginx 配置模板
├── src/ # 前端源码
├── public/ # 静态资源
├── package.json
└── dist/ # 构建产物(gitignore)
““
注意看`.github/workflows/`目录,这是GitHub Actions的约定路径。你放一个`.yml`文件,GitHub就会自动识别并执行。我第一次配置时,Docker构建失败了三次,原因是Node版本没指定,默认用的Ubuntu自带的12.x,而我的项目需要18.x。所以,别踩这个坑。
先贴一个能用的完整配置:
“`yaml
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
– name: Checkout code
uses: actions/checkout@v3
– name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ’18’
– name: Install dependencies
run: npm ci
– name: Build application
run: npm run build
– name: Build Docker image
run: |
docker build -t my-app:${{ github.sha }} -f docker/Dockerfile .
– name: Push image to registry
run: |
docker tag my-app:${{ github.sha }} myregistry.com/my-app:latest
docker push myregistry.com/my-app:latest
– name: Deploy to server
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker pull myregistry.com/my-app:latest
docker stop my-app-container || true
docker rm my-app-container || true
docker run -d –name my-app-container -p 3000:80 myregistry.com/my-app:latest
“`
为啥这么写?`github.sha`是每次提交的唯一哈希,用它做镜像标签可以保证每个版本都能追溯。`secrets`是GitHub提供的变量管理,你的服务器IP、SSH密钥放在这里,别人看不到。一开始我搞错了,没加`secrets`,结果密码暴露了,吓得我赶紧换了密钥。
顺手说下Dockerfile的优化。很多人写的Dockerfile又大又慢,一个镜像动辄几百MB,构建一次要5分钟。其实前端项目的Dockerfile有个最佳实践:**多阶段构建**。
看这个:
“`dockerfile
# docker/Dockerfile
# 第一阶段:构建
FROM node:18-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# 第二阶段:运行(只复制构建产物)
FROM nginx:stable-alpine
COPY –from=builder /app/dist /usr/share/nginx/html
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD [“nginx”, “-g”, “daemon off;”]
“`
这个设计真的反人类吗?不,它很聪明。第一阶段用Node镜像装依赖、构建,第二阶段只拿构建产物和Nginx镜像结合。最终镜像只包含Nginx和静态文件,体积从500MB降到20MB,构建时间从3分半降到45秒。真的好用,推荐。
对了,Nginx配置也有陷阱。很多教程直接给你一个默认Nginx配置,结果单页应用的路由刷新就404。因为前端路由是前端处理的,Nginx不知道`/user/123`应该返回`index.html`。
正确的配置:
“`nginx
# docker/nginx.conf
server {
listen 80;
server_name yourdomain.com;
# 静态资源缓存策略
location /assets/ {
expires 1y;
add_header Cache-Control “public, immutable”;
}
# 单页应用路由处理
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
}
“`
这套方案我用了半年,从3秒→0.5秒的构建时间,部署从手动10分钟到自动不到3分钟。踩过的坑不少,但结果值得。你试试,有问题随时问我。