Docker 部署

将 Spring Boot 应用打包成 Docker 镜像,是现代部署的标准实践。本文涵盖镜像构建、多阶段构建、docker-compose 编排、健康检查等常见场景。


一、Dockerfile 基础写法

简单版(适合快速验证)

FROM eclipse-temurin:21-jre-alpine
 
WORKDIR /app
 
# 复制 fat jar
COPY target/myapp-*.jar app.jar
 
EXPOSE 8080
 
ENTRYPOINT ["java", "-jar", "app.jar"]

生产推荐:分层拷贝

Spring Boot 默认将 fat jar 按依赖/快照/应用代码分层,利用 Docker 层缓存加速增量构建:

# 先解压 fat jar(构建时执行一次)
java -Djarmode=layertools -jar target/myapp.jar extract --destination target/extracted
FROM eclipse-temurin:21-jre-alpine AS builder
WORKDIR /extracted
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
RUN java -Djarmode=layertools -jar app.jar extract
 
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
 
# 按变化频率从低到高分层(低频层在上,充分利用缓存)
COPY --from=builder /extracted/dependencies/          ./
COPY --from=builder /extracted/spring-boot-loader/    ./
COPY --from=builder /extracted/snapshot-dependencies/ ./
COPY --from=builder /extracted/application/           ./
 
EXPOSE 8080
 
ENTRYPOINT ["org.springframework.boot.loader.launch.JarLauncher"]

二、多阶段构建(含编译)

将编译和运行环境隔离,最终镜像不含 JDK 和 Maven:

# ── 阶段一:编译 ──────────────────────────────────────────
FROM maven:3.9-eclipse-temurin-21 AS build
 
WORKDIR /src
 
# 先复制 pom.xml 下载依赖(利用缓存)
COPY pom.xml .
RUN mvn dependency:go-offline -q
 
# 再复制源码编译
COPY src ./src
RUN mvn package -DskipTests -q
 
# ── 阶段二:解压分层 ──────────────────────────────────────
FROM eclipse-temurin:21-jre-alpine AS extract
WORKDIR /extracted
COPY --from=build /src/target/*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract
 
# ── 阶段三:运行镜像 ──────────────────────────────────────
FROM eclipse-temurin:21-jre-alpine
 
# 创建非 root 用户,提升安全性
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
 
WORKDIR /app
 
COPY --from=extract /extracted/dependencies/          ./
COPY --from=extract /extracted/spring-boot-loader/    ./
COPY --from=extract /extracted/snapshot-dependencies/ ./
COPY --from=extract /extracted/application/           ./
 
EXPOSE 8080
 
# 配置 JVM 参数(适配容器内存限制)
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
 
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher"]

三、使用 Buildpack(无需 Dockerfile)

Spring Boot Maven/Gradle 插件内置 Cloud Native Buildpacks 支持,无需手写 Dockerfile:

# Maven
./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=myapp:latest
 
# Gradle
./gradlew bootBuildImage --imageName=myapp:latest
<!-- pom.xml:自定义镜像名和基础镜像 -->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <image>
            <name>registry.example.com/myapp:${project.version}</name>
            <builder>paketobuildpacks/builder-jammy-base</builder>
            <env>
                <BP_JVM_VERSION>21</BP_JVM_VERSION>
                <BPE_JAVA_TOOL_OPTIONS>-XX:MaxRAMPercentage=75.0</BPE_JAVA_TOOL_OPTIONS>
            </env>
        </image>
    </configuration>
</plugin>

Buildpack 优势:自动分层、安全修复、无需维护 Dockerfile;劣势:首次构建较慢,定制灵活性略低。


四、docker-compose 本地开发

# docker-compose.yml
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/mydb
      - SPRING_DATASOURCE_USERNAME=postgres
      - SPRING_DATASOURCE_PASSWORD=secret
      - SPRING_DATA_REDIS_HOST=redis
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
 
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: secret
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
 
  redis:
    image: redis:7-alpine
    command: redis-server --requirepass secret
    volumes:
      - redis_data:/data
 
volumes:
  postgres_data:
  redis_data:
# docker-compose.override.yml(本地开发覆盖,不提交到仓库)
services:
  app:
    volumes:
      - ./target:/app/target   # 热部署时挂载
    environment:
      - SPRING_DEVTOOLS_RESTART_ENABLED=true

五、配置与密钥管理

通过环境变量覆盖配置

Spring Boot 自动将环境变量映射到配置属性(._,大写):

# SPRING_DATASOURCE_URL → spring.datasource.url
docker run -e SPRING_DATASOURCE_URL=jdbc:postgresql://... myapp

application-docker.yml

# src/main/resources/application-docker.yml
spring:
  datasource:
    url: ${DB_URL:jdbc:postgresql://db:5432/mydb}
    username: ${DB_USER:postgres}
    password: ${DB_PASSWORD}
  data:
    redis:
      host: ${REDIS_HOST:redis}
      password: ${REDIS_PASSWORD}
 
logging:
  level:
    root: INFO
  # 容器环境输出 JSON 便于日志聚合
  pattern:
    console: '{"time":"%d","level":"%level","logger":"%logger","msg":"%msg"}%n'

Docker Secrets(生产推荐)

# docker-compose.yml(Swarm 模式)
services:
  app:
    environment:
      - DB_PASSWORD_FILE=/run/secrets/db_password
    secrets:
      - db_password
 
secrets:
  db_password:
    external: true

六、健康检查与优雅停机

# application.yml
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  health:
    livenessstate:
      enabled: true
    readinessstate:
      enabled: true

容器健康检查路径:

路径含义用于
/actuator/health/liveness应用是否存活Docker HEALTHCHECK / K8s livenessProbe
/actuator/health/readiness应用是否就绪K8s readinessProbe
/actuator/health综合健康状态监控面板

SIGTERM 信号处理与优雅停机详见 优雅停机;Actuator 端点配置详见 Actuator监控


七、JVM 容器适配

# 推荐 JVM 参数(Java 11+ 原生支持容器资源限制)
JAVA_OPTS="\
  -XX:+UseContainerSupport \        # 读取 cgroup 内存限制(默认已开启)
  -XX:MaxRAMPercentage=75.0 \       # 最多使用容器内存的 75%
  -XX:InitialRAMPercentage=50.0 \   # 初始堆大小
  -XX:+ExitOnOutOfMemoryError \     # OOM 时退出让容器重启
  -XX:+HeapDumpOnOutOfMemoryError \ # OOM 时输出 heap dump
  -XX:HeapDumpPath=/tmp/heapdump.hprof \
  -Dfile.encoding=UTF-8 \
  -Duser.timezone=Asia/Shanghai"
# Dockerfile 中使用
ENV JAVA_TOOL_OPTIONS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Dfile.encoding=UTF-8"

JAVA_TOOL_OPTIONS 会被所有 JVM 进程自动读取;JAVA_OPTS 需要在 ENTRYPOINT 中显式引用。


八、镜像优化建议

措施效果
使用 Alpine/Distroless 基础镜像减少镜像体积 100~200 MB
分层构建(依赖层在前)仅代码变更时无需重新下载依赖层
非 root 用户运行降低安全风险
.dockerignore 排除无关文件缩短构建上下文传输时间
多阶段构建排除 JDK/Maven最终镜像仅含 JRE
GraalVM 原生镜像极致压缩,启动 < 100 ms

.dockerignore 示例:

.git
.gitignore
target/
*.md
*.log
.idea/
*.iml

常用命令

# 构建镜像
docker build -t myapp:1.0.0 .
 
# 运行容器
docker run -d \
  --name myapp \
  -p 8080:8080 \
  -e SPRING_PROFILES_ACTIVE=docker \
  --memory=512m \
  --cpus=1.0 \
  myapp:1.0.0
 
# 查看日志
docker logs -f myapp
 
# 进入容器
docker exec -it myapp sh
 
# 启停(docker-compose)
docker-compose up -d
docker-compose down -v   # -v 同时删除 volume

相关链接