在当今的Web应用开发中,前后端分离架构已成为主流选择。Spring Boot作为Java生态中最流行的后端框架,与Vue.js这一渐进式前端框架的组合,能够快速构建现代化Web应用。而Docker容器化部署则为这种架构提供了完美的运行环境解决方案。
我最近为一个电商项目完成了这样的部署实践,整个过程涉及前端静态资源打包、后端服务容器化、Nginx配置优化等多个环节。下面将详细分享从零开始完成整套部署的具体步骤和踩坑经验。
在开始之前,请确保你的开发机已安装以下工具:
重要提示:生产环境建议使用Linux服务器,本文以CentOS 7为例。Windows/Mac开发环境需要确保Docker Desktop已正确安装并配置共享驱动器权限。
首先在后端项目根目录创建Dockerfile:
dockerfile复制# 使用官方OpenJDK基础镜像
FROM openjdk:11-jdk-slim as builder
# 设置工作目录
WORKDIR /app
# 复制Maven构建文件
COPY pom.xml .
COPY src ./src
# 下载依赖并构建(利用Docker层缓存)
RUN apt-get update && \
apt-get install -y maven && \
mvn dependency:go-offline && \
mvn package -DskipTests
# 最终运行时镜像
FROM openjdk:11-jre-slim
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
# 设置JVM参数(生产环境推荐配置)
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC"
# 暴露端口(与application.yml中配置一致)
EXPOSE 8080
# 启动命令
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]
关键优化点:
前端项目需要先进行生产环境打包,然后通过Nginx提供服务。创建Dockerfile:
dockerfile复制# 构建阶段
FROM node:14-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 生产环境
FROM nginx:1.21-alpine
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
配套的nginx.conf配置:
nginx复制server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
# API代理配置
location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
创建统一的编排文件管理前后端服务:
yaml复制version: '3.8'
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: spring-backend
restart: unless-stopped
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_URL=jdbc:mysql://mysql:3306/app_db
depends_on:
- mysql
networks:
- app-network
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: vue-frontend
restart: unless-stopped
ports:
- "80:80"
depends_on:
- backend
networks:
- app-network
mysql:
image: mysql:5.7
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: app_db
MYSQL_USER: app_user
MYSQL_PASSWORD: userpass
volumes:
- mysql_data:/var/lib/mysql
networks:
- app-network
volumes:
mysql_data:
networks:
app-network:
driver: bridge
执行以下命令启动完整服务栈:
bash复制# 构建并启动所有服务(-d表示后台运行)
docker-compose up -d --build
# 查看运行状态
docker-compose ps
# 查看实时日志
docker-compose logs -f backend
dockerfile复制ENV JAVA_OPTS="-Xms1g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=4 -XX:ConcGCThreads=2"
nginx复制location /static {
alias /usr/share/nginx/html/static;
expires 365d;
add_header Cache-Control "public";
}
yaml复制environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} # 从环境变量读取
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASSWORD}
yaml复制deploy:
resources:
limits:
cpus: '2'
memory: 2G
如果前端直接访问后端API出现CORS错误,有两种解决方式:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.maxAge(3600);
}
}
nginx复制location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
在Dockerfile中添加时区设置:
dockerfile复制# 后端Dockerfile
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 前端Dockerfile
RUN apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
检查Vue项目的vue.config.js:
javascript复制module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? '/' : '/',
assetsDir: 'static'
}
dockerfile复制# 在ENTRYPOINT中添加日志重定向
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar >> /var/log/app.log 2>&1"]
yaml复制logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
在docker-compose中添加健康检查:
yaml复制healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
示例workflow文件:
yaml复制name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Build and push
run: |
docker-compose -f docker-compose.prod.yml build
docker-compose -f docker-compose.prod.yml push
- name: SSH Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PROD_SERVER_IP }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker-compose -f docker-compose.prod.yml pull
docker-compose -f docker-compose.prod.yml up -d
创建环境特定的compose文件:
yaml复制# docker-compose.override.yml(开发环境)
version: '3.8'
services:
backend:
environment:
- SPRING_PROFILES_ACTIVE=dev
volumes:
- ./backend:/app
yaml复制# docker-compose.prod.yml(生产环境)
version: '3.8'
services:
backend:
environment:
- SPRING_PROFILES_ACTIVE=prod
deploy:
resources:
limits:
cpus: '2'
memory: 2G
实际部署时通过-f参数指定:
bash复制# 开发环境
docker-compose up
# 生产环境
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
yaml复制services:
redis:
image: redis:6-alpine
container_name: redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- app-network
Spring Boot配置:
properties复制spring.redis.host=redis
spring.redis.port=6379
nginx复制upstream backend {
server backend1:8080;
server backend2:8080;
server backend3:8080;
}
server {
location /api {
proxy_pass http://backend;
}
}
对应的compose文件需要扩展多个后端实例。
推荐的项目目录结构:
code复制project-root/
├── backend/
│ ├── src/
│ ├── Dockerfile
│ └── pom.xml
├── frontend/
│ ├── src/
│ ├── Dockerfile
│ ├── nginx.conf
│ └── package.json
├── docker-compose.yml
├── docker-compose.prod.yml
└── README.md
在最近的生产部署中,我们遇到了几个典型问题:
前端路由刷新404:
这是Vue history模式常见问题,最终通过Nginx的try_files配置解决:
nginx复制location / {
try_files $uri $uri/ /index.html;
}
数据库连接超时:
MySQL容器启动较慢,导致后端启动时连接失败。解决方案:
yaml复制depends_on:
mysql:
condition: service_healthy
静态资源缓存失效:
通过为静态资源添加hash指纹解决:
javascript复制// vue.config.js
module.exports = {
filenameHashing: true
}
容器内存泄漏:
通过限制容器内存并添加OOM Killer防护:
yaml复制deploy:
resources:
limits:
memory: 2G