Spring Boot: Docker 容器化部署
最后更新:2026-08-26
Docker 是部署的革命——一次构建,处处运行,告别"在我机器上能跑"的借口。
1. 你将学到
- 多阶段 Dockerfile 构建:Builder 镜像 + Runtime 镜像(Distroless / Eclipse Temurin)
.dockerignore优化与镜像层缓存策略- Docker Compose 编排:OrderFlow + MySQL + Redis 三服务联动
- 环境变量注入与配置外部化
- Bob 构建小于 150MB 的 OrderFlow 生产级 Docker 镜像
2. 一个运维工程师的真实故事
(1) 痛点:环境不一致
Bob 在本地调试好 OrderFlow 后部署到生产,结果 MySQL 驱动版本不匹配、JDK 版本不一致、时区配置不同,服务启动失败。每次部署都要花半天排查环境差异,"在我机器上能跑"成了团队的口头禅。更糟糕的是,3 台服务器需要手动部署 3 次,经常遗漏配置。
(2) Docker 的解法
Docker 把应用和所有依赖打包成一个镜像:
DOCKERFILE
FROM eclipse-temurin:17-jre-alpine
COPY target/orderflow-service.jar /app/app.jar
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
一条命令部署,环境完全一致。
(3) 收益
Bob 用 Docker 后,镜像在本地构建一次,3 台服务器统一拉取运行,环境差异问题消失。Docker Compose 一条命令启动 OrderFlow + MySQL + Redis 完整环境,新人 5 分钟就能本地跑起来。
3. 多阶段 Dockerfile
(1) 多阶段构建流程
graph TD
A["Stage 1: Builder<br/>Maven + JDK 17<br/>Compile + Package"] --> B["orderflow-service.jar"]
B --> C["Stage 2: Runtime<br/>JRE 17 Alpine<br/>Copy JAR only"]
C --> D["Final Image<br/>~150MB"]
▶ 示例: 多阶段 Dockerfile
DOCKERFILE
# Stage 1: Build
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
# Copy pom.xml first for dependency caching
COPY pom.xml .
RUN mvn dependency:go-offline -B
# Copy source and build
COPY src ./src
RUN mvn package -DskipTests -B
# Stage 2: Runtime
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Copy JAR from builder
COPY --from=builder /build/target/*.jar app.jar
# Set ownership
RUN chown -R appuser:appgroup /app
USER appuser
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
# Run application
ENTRYPOINT ["java", \
"-XX:+UseG1GC", \
"-XX:MaxRAMPercentage=75.0", \
"-jar", "app.jar"]
输出:
TEXT
📖 仅展示
// 执行成功
| 基础镜像 | 大小 | 安全性 | 适用场景 |
|---|---|---|---|
eclipse-temurin:17 |
~450MB | 中 | 开发/测试 |
eclipse-temurin:17-jre-alpine |
~170MB | 中高 | 生产推荐 |
gcr.io/distroless/java17-debian12 |
~100MB | 最高 | 极致安全 |
▶ 示例: Distroless 镜像
DOCKERFILE
# Stage 1: Build
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
# Stage 2: Distroless runtime
FROM gcr.io/distroless/java17-debian12
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
输出:
TEXT
📖 仅展示
// 执行成功
💡 提示: Distroless 镜像没有 shell(无 sh/bash),无法
docker exec -it 进入调试。生产推荐,但调试时用 Alpine 镜像。
4. .dockerignore 与镜像优化
▶ 示例: .dockerignore
TEXT
📖 仅展示
# .dockerignore
.git
.github
.idea
*.md
target/
!target/*.jar
node_modules/
*.log
.env
docker-compose*.yml
Dockerfile*
| 优化策略 | 效果 | 说明 |
|---|---|---|
.dockerignore 排除无用文件 |
减小构建上下文 | 不发送 .git、.idea 等 |
| 先 COPY pom.xml | 缓存依赖层 | 依赖不变时跳过下载 |
| 多阶段构建 | 减小最终镜像 | 不含 Maven 和源码 |
| JRE 替代 JDK | 减小 ~200MB | 运行时不需要编译工具 |
| Alpine 基础镜像 | 减小 ~280MB | 使用 musl libc 的精简 Linux |
5. Docker Compose 多服务编排
▶ 示例: OrderFlow + MySQL + Redis
YAML
# docker-compose.yml
version: "3.9"
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: prod
DB_HOST: mysql
DB_USERNAME: orderflow
DB_PASSWORD: ${DB_PASSWORD:-orderflow123}
REDIS_HOST: redis
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 3s
retries: 5
restart: unless-stopped
mysql:
image: mysql:8.0
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: orderflow
MYSQL_USER: orderflow
MYSQL_PASSWORD: ${DB_PASSWORD:-orderflow123}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root123}
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
volumes:
mysql-data:
redis-data:
输出:
TEXT
📖 仅展示
CONTAINER ID IMAGE STATUS PORTS
abc123 nginx:latest Up 2 hours 0.0.0.0:80->80/tcp
▶ 示例: 启动与验证
BASH
# Build and start all services
docker compose up -d --build
# Check service status
docker compose ps
# View application logs
docker compose logs -f app
# Test API
curl http://localhost:8080/actuator/health
# Stop all services
docker compose down -v
输出:
TEXT
📖 仅展示
CONTAINER ID IMAGE STATUS
abc123 latest Up 2 hours
| 命令 | 说明 |
|---|---|
docker compose up -d |
后台启动 |
docker compose down |
停止并删除容器 |
docker compose logs -f app |
查看应用日志 |
docker compose ps |
查看服务状态 |
docker compose build |
仅构建镜像 |
6. 环境变量注入
▶ 示例: 配置外部化
YAML
# application-prod.yml (in Docker image)
spring:
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:3306/orderflow
username: ${DB_USERNAME:orderflow}
password: ${DB_PASSWORD:defaultpass}
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
| 配置方式 | 优先级 | 适用场景 |
|---|---|---|
Docker Compose environment |
高 | 简单部署 |
.env 文件 |
中 | 本地开发 |
| K8s ConfigMap / Secret | 高 | 生产环境 |
TEXT
📖 仅展示
# .env file
DB_PASSWORD=secure_password_here
DB_ROOT_PASSWORD=root_secure_here
REDIS_PASSWORD=redis_secure_here
7. 综合示例:OrderFlow 完整 Docker 化
DOCKERFILE
# Dockerfile
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
FROM eclipse-temurin:17-jre-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 8080 8081
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-XX:+UseG1GC", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
YAML
# docker-compose.yml
version: "3.9"
services:
app:
build: .
ports:
- "8080:8080"
- "8081:8081"
environment:
SPRING_PROFILES_ACTIVE: prod
DB_HOST: mysql
DB_USERNAME: orderflow
DB_PASSWORD: ${DB_PASSWORD:-orderflow123}
REDIS_HOST: redis
MANAGEMENT_SERVER_PORT: "8081"
depends_on:
mysql: { condition: service_healthy }
redis: { condition: service_healthy }
restart: unless-stopped
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: orderflow
MYSQL_USER: orderflow
MYSQL_PASSWORD: ${DB_PASSWORD:-orderflow123}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root123}
volumes: [mysql-data:/var/lib/mysql]
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s; timeout: 5s; retries: 10
redis:
image: redis:7-alpine
volumes: [redis-data:/data]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s; timeout: 3s; retries: 5
volumes:
mysql-data:
redis-data:
❓ 常见问题
Q Docker 镜像为什么这么大?
A 默认使用 JDK 完整镜像(~450MB)。优化方法:1)JRE 替代 JDK(~170MB);2)Alpine 基础镜像;3)Distroless(~100MB);4)GraalVM 原生镜像(~50MB)。
Q 如何调试 Docker 容器中的应用?
A 1)
docker compose logs -f app 查看日志;2)docker compose exec app sh 进入容器(Alpine 镜像);3)远程调试:ENTRYPOINT ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "app.jar"]。Q depends_on 和 healthcheck 有什么关系?
A
depends_on 只保证容器启动顺序,不保证服务就绪。配合 condition: service_healthy,Docker Compose 会等待 healthcheck 通过后才启动依赖服务。Q 如何减小镜像构建时间?
A 1)先 COPY pom.xml 并
mvn dependency:go-offline,利用 Docker 层缓存;2).dockerignore 排除无关文件;3)使用 BuildKit 缓存挂载。Q Docker Compose 能用于生产吗?
A Docker Compose 适合本地开发和简单部署。生产环境推荐 Docker Swarm 或 Kubernetes,提供自动伸缩、滚动更新、自愈等能力。
Q Alpine 镜像有什么坑?
A 1)使用 musl libc 而非 glibc,某些 Native 库可能不兼容;2)时区数据需额外安装;3)DNS 解析在高并发下有 bug(
ndots 问题)。推荐 Alpine 用于 JRE-only 场景。📖 小节
- 多阶段 Dockerfile:Builder 阶段编译 + Runtime 阶段只含 JAR 和 JRE
.dockerignore排除无关文件,先 COPY pom.xml 利用缓存层- Docker Compose 编排多服务:app + mysql + redis,healthcheck 控制启动顺序
- 环境变量注入配置,敏感信息用 .env 文件或 K8s Secret
- 生产镜像推荐 Eclipse Temurin JRE Alpine(~170MB)或 Distroless(~100MB)
- 非根用户运行、HEALTHCHECK 健康检查是生产必备
📝 作业
-
基础题(难度⭐):为 OrderFlow 编写 Dockerfile,构建镜像并在本地运行,验证
/actuator/health返回 UP。 -
进阶题(难度⭐⭐):编写 docker-compose.yml 编排 OrderFlow + MySQL + Redis 三服务,配置 healthcheck 和 depends_on,确保服务启动顺序正确。
-
挑战题(难度⭐⭐⭐):优化镜像到 150MB 以下,使用 Distroless 基础镜像,配置 JVM 内存参数(
MaxRAMPercentage),添加非根用户和安全相关配置。