Spring Boot: Docker 容器化部署

最后更新:2026-08-26

Docker 是部署的革命——一次构建,处处运行,告别"在我机器上能跑"的借口。

1. 你将学到


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) 多阶段构建流程

100%
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 场景。

📖 小节


📝 作业

  1. 基础题(难度⭐):为 OrderFlow 编写 Dockerfile,构建镜像并在本地运行,验证 /actuator/health 返回 UP。

  2. 进阶题(难度⭐⭐):编写 docker-compose.yml 编排 OrderFlow + MySQL + Redis 三服务,配置 healthcheck 和 depends_on,确保服务启动顺序正确。

  3. 挑战题(难度⭐⭐⭐):优化镜像到 150MB 以下,使用 Distroless 基础镜像,配置 JVM 内存参数(MaxRAMPercentage),添加非根用户和安全相关配置。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏