Docker 多服务镜像构建完整教程
本文将手把手教你如何使用 Docker 多阶段构建技术,将一个 Spring Boot 多模块项目构建成三个独立的服务镜像。适合 Java 全栈开发者学习。
📋 目录
- 项目背景
- 技术架构
- 准备工作
- Dockerfile 详解
- .dockerignore 配置
- 构建镜像
- 运行容器
- 生产部署
- 常见问题
项目背景
我们有一个基于若依框架的 Spring Boot 多模块项目,包含三个服务:
- ayy-server:核心业务服务(端口 48087)
- ayy-snailjob-server:分布式任务调度服务(端口 9090)
- ayy-monitor-admin:监控管理服务(端口 8800, 17888)
项目结构如下:
project-root/
├── pom.xml # 父 POM
├── ruoyi-admin/ # 主服务模块
│ ├── pom.xml
│ └── src/
├── ruoyi-extend/
│ ├── ruoyi-monitor-admin/ # 监控服务模块
│ │ ├── pom.xml
│ │ └── src/
│ └── ruoyi-snailjob-server/ # 任务调度服务模块
│ ├── pom.xml
│ └── src/
├── Dockerfile # 多阶段构建文件
└── .dockerignore # Docker 忽略文件
技术架构
核心技术栈
- Java 17:运行环境
- Maven 3.9.6:项目构建工具
- Spring Boot:应用框架
- Docker Multi-stage Build:多阶段构建
- Alpine Linux:精简基础镜像
为什么使用多阶段构建?
传统方式需要:
- 本地编译 → 生成 JAR
- 编写 Dockerfile → 复制 JAR
- 构建镜像
多阶段构建的优势:
✅ 一个 Dockerfile 完成所有事情
✅ 构建环境与运行环境分离(builder 阶段用完即弃)
✅ 最终镜像体积小(只包含 JRE + JAR)
✅ 利用 Docker 缓存加速构建
✅ 一次构建多个镜像
准备工作
1. 安装 Docker
# macOS
brew install docker# Ubuntu/Debian
sudo apt-get update
sudo apt-get install docker.io# 验证安装
docker --version
2. 启用 BuildKit(推荐)
BuildKit 是 Docker 的新一代构建引擎,支持并行构建和高级缓存。
# 临时启用
export DOCKER_BUILDKIT=1# 永久启用(添加到 ~/.bashrc 或 ~/.zshrc)
echo 'export DOCKER_BUILDKIT=1' >> ~/.bashrc
source ~/.bashrc
3. 项目准备
确保项目根目录有以下文件:
pom.xml(父 POM)Dockerfile.dockerignore
Dockerfile 详解
我们的 Dockerfile 分为 4 个阶段:1 个构建阶段 + 3 个运行阶段。
第一阶段:构建阶段(builder)
# -------------------------------
# 构建阶段:使用 Maven 构建产物
# -------------------------------
FROM maven:3.9.6-eclipse-temurin-17 AS builderWORKDIR /build# 先复制 pom 文件,利用 Docker 层缓存
COPY pom.xml .
COPY ruoyi-admin/pom.xml ./ruoyi-admin/
COPY ruoyi-extend/ruoyi-monitor-admin/pom.xml ./ruoyi-extend/ruoyi-monitor-admin/
COPY ruoyi-extend/ruoyi-snailjob-server/pom.xml ./ruoyi-extend/ruoyi-snailjob-server/# 下载依赖(利用缓存层)
RUN --mount=type=cache,target=/root/.m2 \mvn dependency:go-offline -B || true# 复制源码
COPY . .# 构建(跳过测试)
RUN --mount=type=cache,target=/root/.m2 \mvn -B clean package -DskipTests
关键点解析:
-
分层复制 POM:先复制 POM 文件再复制源码
- 依赖不变时,Docker 会使用缓存,避免重复下载
-
Maven 缓存挂载:
--mount=type=cache,target=/root/.m2- Maven 本地仓库会被缓存,大幅加速后续构建
-
跳过测试:
-DskipTests- 构建镜像时跳过单元测试,节省时间
第二阶段:ayy-server 运行镜像
# -------------------------------
# 运行阶段:ayy-server
# -------------------------------
FROM eclipse-temurin:17-jre-alpine AS ayy-server# 创建非 root 用户
RUN addgroup -S adduser && adduser -S adduser -G adduserWORKDIR /app# 拷贝构建产物
COPY --from=builder --chown=adduser:adduser /build/ruoyi-admin/target/*.jar ./app.jar# 创建日志和配置目录
RUN mkdir -p /app/logs /app/config && \chown -R adduser:adduser /app# 时区设置
ENV TZ=Asia/Shanghai \JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Djava.security.egd=file:/dev/./urandom" \SPRING_PROFILES_ACTIVE=prod \SERVER_PORT=48087EXPOSE 48087USER adduser# 健康检查(可选)
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \CMD wget --no-verbose --tries=1 --spider http://localhost:${SERVER_PORT}/actuator/health || exit 1ENTRYPOINT ["sh", "-c", "exec java ${JAVA_OPTS} -Dspring.profiles.active=${SPRING_PROFILES_ACTIVE} -jar app.jar"]
关键点解析:
-
使用 JRE 而非 JDK:
eclipse-temurin:17-jre-alpine:精简镜像(~180MB)- Alpine Linux:基于 musl libc 的超轻量级发行版
-
安全性考虑:
- 创建非 root 用户运行应用
- 使用
--chown直接设置文件权限
-
JVM 参数优化:
-Xms512m -Xmx1024m:堆内存配置-XX:+UseG1GC:使用 G1 垃圾回收器-XX:MaxGCPauseMillis=200:最大停顿时间 200ms
-
健康检查:
- 需要应用开启 Spring Boot Actuator
- 自动检测服务是否健康
-
灵活配置:
- 通过环境变量控制 JVM 参数和 Spring Profile
- 运行时可通过
-e参数覆盖
第三、四阶段:另外两个服务
结构与 ayy-server 类似,只是:
- 复制的 JAR 路径不同
- 暴露的端口不同
.dockerignore 配置
.dockerignore 文件告诉 Docker 哪些文件不需要复制到镜像中。
# Maven 构建产物(避免复制本地编译的 target 目录)
target/
*/target/
**/target/
**/dependency-reduced-pom.xml# IDE 配置文件
.idea/
*.iml
.vscode/
.history/
.eclipse/
.settings/# Git 相关
.git/
.gitignore
.gitattributes# 日志文件
**/logs/
*.log# 临时文件
**/*.tmp
**/*.temp
**/*.swp
**/*.swo
**/*.bak
**/*.DS_Store
*~# Docker 相关(避免递归)
Dockerfile
docker-compose.yml
.dockerignore# 文档
README.md
docs/# CI/CD 配置
.github/
.gitlab-ci.yml
Jenkinsfile# 前端构建产物
**/node_modules/
**/dist/# 测试覆盖率
**/coverage/
**/.nyc_output/# 其他
.gitee/
.run/
.image/
script/
为什么需要 .dockerignore?
- 减少构建上下文大小:避免复制无用文件
- 加快构建速度:传输到 Docker daemon 的数据更少
- 避免缓存失效:无关文件变化不会触发重新构建
构建镜像
方式一:单独构建(适合开发测试)
1. 构建 ayy-server
docker build --target ayy-server -t ayy-server:latest .
命令解析:
--target ayy-server:指定构建目标阶段-t ayy-server:latest:镜像名称和标签.:构建上下文(当前目录)
2. 构建 ayy-snailjob-server
docker build --target ayy-snailjob-server -t ayy-snailjob-server:latest .
3. 构建 ayy-monitor-admin
docker build --target ayy-monitor-admin -t ayy-monitor-admin:latest .
方式二:带版本号构建(适合生产发布)
# 定义版本号
VERSION=1.0.0# 构建三个镜像(同时打 VERSION 和 latest 标签)
docker build --target ayy-server -t ayy-server:${VERSION} -t ayy-server:latest .
docker build --target ayy-snailjob-server -t ayy-snailjob-server:${VERSION} -t ayy-snailjob-server:latest .
docker build --target ayy-monitor-admin -t ayy-monitor-admin:${VERSION} -t ayy-monitor-admin:latest .
方式三:使用脚本批量构建
创建 build.sh:
#!/bin/bash
set -eVERSION=${1:-latest}echo "开始构建镜像,版本: ${VERSION}"# 启用 BuildKit
export DOCKER_BUILDKIT=1# 构建 ayy-server
echo "构建 ayy-server..."
docker build --target ayy-server -t ayy-server:${VERSION} -t ayy-server:latest .# 构建 ayy-snailjob-server
echo "构建 ayy-snailjob-server..."
docker build --target ayy-snailjob-server -t ayy-snailjob-server:${VERSION} -t ayy-snailjob-server:latest .# 构建 ayy-monitor-admin
echo "构建 ayy-monitor-admin..."
docker build --target ayy-monitor-admin -t ayy-monitor-admin:${VERSION} -t ayy-monitor-admin:latest .echo "✓ 所有镜像构建完成!"# 显示镜像列表
docker images | grep -E "ayy-server|ayy-snailjob-server|ayy-monitor-admin"
使用方法:
# 赋予执行权限
chmod +x build.sh# 构建 latest 版本
./build.sh# 构建指定版本
./build.sh 1.0.0
验证构建结果
# 查看构建的镜像
docker images | grep ayy# 输出示例:
# ayy-server latest abc123def456 2 minutes ago 350MB
# ayy-snailjob-server latest def456ghi789 3 minutes ago 340MB
# ayy-monitor-admin latest ghi789jkl012 4 minutes ago 345MB
运行容器
基础运行
1. 运行 ayy-server
docker run -d \--name ayy-server \-p 48087:48087 \ayy-server:latest
参数说明:
-d:后台运行--name ayy-server:容器名称-p 48087:48087:端口映射(宿主机:容器)
2. 运行 ayy-snailjob-server
docker run -d \--name ayy-snailjob-server \-p 9090:9090 \ayy-snailjob-server:latest
3. 运行 ayy-monitor-admin
docker run -d \--name ayy-monitor-admin \-p 8800:8800 \-p 17888:17888 \ayy-monitor-admin:latest
高级运行(带配置)
docker run -d \--name ayy-server \-p 48087:48087 \-e SPRING_PROFILES_ACTIVE=prod \-e JAVA_OPTS="-Xms1g -Xmx2g" \-e SPRING_DATASOURCE_URL="jdbc:mysql://192.168.1.100:3306/ayy?useUnicode=true&characterEncoding=utf8" \-e SPRING_DATASOURCE_USERNAME=root \-e SPRING_DATASOURCE_PASSWORD=password \-e SPRING_REDIS_HOST=192.168.1.100 \-e SPRING_REDIS_PORT=6379 \-v /data/logs/ayy-server:/app/logs \-v /data/config/ayy-server:/app/config:ro \--restart unless-stopped \ayy-server:latest
高级参数说明:
-
-e:环境变量注入SPRING_PROFILES_ACTIVE:Spring 环境配置JAVA_OPTS:JVM 参数SPRING_DATASOURCE_*:数据库配置SPRING_REDIS_*:Redis 配置
-
-v:数据卷挂载/data/logs/ayy-server:/app/logs:日志持久化/data/config/ayy-server:/app/config:ro:配置文件(只读)
-
--restart unless-stopped:自动重启策略
查看容器状态
# 查看运行中的容器
docker ps# 查看容器日志
docker logs -f ayy-server# 查看容器资源使用情况
docker stats ayy-server# 进入容器(调试用)
docker exec -it ayy-server sh
停止和清理
# 停止容器
docker stop ayy-server ayy-snailjob-server ayy-monitor-admin# 删除容器
docker rm ayy-server ayy-snailjob-server ayy-monitor-admin# 删除镜像
docker rmi ayy-server:latest ayy-snailjob-server:latest ayy-monitor-admin:latest
生产部署
使用 Docker Compose(推荐)
创建 docker-compose.yml:
services:mysql:image: mysql:8.0container_name: ayy-mysqlenvironment:MYSQL_ROOT_PASSWORD: your_passwordMYSQL_DATABASE: ayyTZ: Asia/Shanghaiports:- "3306:3306"volumes:- mysql-data:/var/lib/mysqlnetworks:- ayy-networkrestart: unless-stoppedredis:image: redis:7-alpinecontainer_name: ayy-redisports:- "6379:6379"volumes:- redis-data:/datanetworks:- ayy-networkrestart: unless-stoppedcommand: redis-server --appendonly yesayy-server:image: ayy-server:latestcontainer_name: ayy-serverports:- "48087:48087"environment:SPRING_PROFILES_ACTIVE: prodJAVA_OPTS: "-Xms1g -Xmx2g"SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/ayy?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/ShanghaiSPRING_DATASOURCE_USERNAME: rootSPRING_DATASOURCE_PASSWORD: your_passwordSPRING_REDIS_HOST: redisSPRING_REDIS_PORT: 6379volumes:- ./logs/ayy-server:/app/logsnetworks:- ayy-networkdepends_on:- mysql- redisrestart: unless-stoppedayy-snailjob-server:image: ayy-snailjob-server:latestcontainer_name: ayy-snailjob-serverports:- "9090:9090"environment:SPRING_PROFILES_ACTIVE: prodJAVA_OPTS: "-Xms512m -Xmx1g"volumes:- ./logs/ayy-snailjob-server:/app/logsnetworks:- ayy-networkdepends_on:- mysql- redisrestart: unless-stoppedayy-monitor-admin:image: ayy-monitor-admin:latestcontainer_name: ayy-monitor-adminports:- "8800:8800"- "17888:17888"environment:SPRING_PROFILES_ACTIVE: prodJAVA_OPTS: "-Xms512m -Xmx1g"volumes:- ./logs/ayy-monitor-admin:/app/logsnetworks:- ayy-networkdepends_on:- mysqlrestart: unless-stoppednetworks:ayy-network:driver: bridgevolumes:mysql-data:redis-data:
使用方法:
# 启动所有服务
docker-compose up -d# 查看服务状态
docker-compose ps# 查看日志
docker-compose logs -f ayy-server# 停止所有服务
docker-compose down# 停止并删除数据卷
docker-compose down -v
推送到镜像仓库
# 登录镜像仓库(以阿里云为例)
docker login --username=your_username registry.cn-hangzhou.aliyuncs.com# 打标签
docker tag ayy-server:latest registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-server:1.0.0
docker tag ayy-snailjob-server:latest registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-snailjob-server:1.0.0
docker tag ayy-monitor-admin:latest registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-monitor-admin:1.0.0# 推送镜像
docker push registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-server:1.0.0
docker push registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-snailjob-server:1.0.0
docker push registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-monitor-admin:1.0.0
生产服务器拉取运行
# 拉取镜像
docker pull registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-server:1.0.0# 运行
docker run -d \--name ayy-server \-p 48087:48087 \-e SPRING_PROFILES_ACTIVE=prod \-v /data/logs:/app/logs \--restart unless-stopped \registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-server:1.0.0
常见问题
Q1: 构建很慢,如何优化?
A: 使用以下技巧:
- 启用 BuildKit
export DOCKER_BUILDKIT=1
- 使用国内 Maven 镜像
在项目根目录创建 .mvn/maven.config:
-Dmaven.repo.local=/root/.m2/repository
在 Dockerfile 添加镜像配置:
RUN mkdir -p /root/.m2 && \echo '<settings><mirrors><mirror><id>aliyun</id><mirrorOf>*</mirrorOf><url>https://maven.aliyun.com/repository/public</url></mirror></mirrors></settings>' > /root/.m2/settings.xml
- 利用缓存
分层复制 POM 文件,依赖不变时会使用缓存。
Q2: 健康检查一直失败?
A: 检查以下几点:
- 确认应用已开启 Actuator
pom.xml 添加依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.yml 配置:
management:endpoints:web:exposure:include: healthendpoint:health:show-details: always
- 临时禁用健康检查
在 Dockerfile 中注释掉 HEALTHCHECK 行。
Q3: 镜像太大怎么办?
A: 优化建议:
- 已使用 Alpine 基础镜像(~180MB)
- 清理 Maven 缓存(仅在构建阶段)
- 移除不必要的依赖
查看镜像层大小:
docker history ayy-server:latest
Q4: 如何查看容器内的日志?
A: 三种方式:
# 方式 1: 查看容器标准输出
docker logs -f ayy-server# 方式 2: 挂载日志目录(推荐)
docker run -v /data/logs:/app/logs ...# 方式 3: 进入容器查看
docker exec -it ayy-server sh
cd /app/logs
tail -f application.log
Q5: 如何覆盖配置文件?
A: 使用数据卷挂载:
# 准备配置文件
mkdir -p /data/config/ayy-server
cp application-prod.yml /data/config/ayy-server/# 挂载配置目录
docker run -v /data/config/ayy-server:/app/config:ro ...
在 application.yml 中配置:
spring:config:additional-location: file:/app/config/
Q6: 如何回滚到旧版本?
A: 使用版本标签:
# 停止当前版本
docker stop ayy-server
docker rm ayy-server# 运行旧版本
docker run -d \--name ayy-server \-p 48087:48087 \ayy-server:1.0.0 # 指定旧版本
总结
本教程涵盖了 Docker 多阶段构建的完整流程:
✅ Dockerfile 编写:多阶段构建、层缓存优化
✅ .dockerignore 配置:减少构建上下文
✅ 镜像构建:单独构建、批量构建、版本管理
✅ 容器运行:基础运行、高级配置、日志管理
✅ 生产部署:Docker Compose、镜像仓库、自动化
参考资料
- Docker 官方文档
- Spring Boot Docker 最佳实践
- Maven Docker 插件
