一、核心概念

1.1 什么是 Docker?

  • Docker 是一个开源的容器化平台,允许你将应用及其所有依赖(代码、运行时、库、环境变量、配置文件)打包到一个轻量级、可移植的容器 (Container) 中。
  • 容器在宿主机的操作系统内核上运行,通过命名空间 (Namespaces)控制组 (cgroups) 实现隔离,比虚拟机更高效。

1.2 为什么用 Docker 部署 Spring Boot?

优势 说明
环境一致性 “Build Once, Run Anywhere”。开发、测试、生产环境完全一致,避免“在我机器上能跑”问题。
快速部署与扩展 秒级启动/停止容器,轻松实现水平扩展(Scale Out)。
资源隔离与高效 容器共享宿主机内核,资源开销远小于虚拟机,密度更高。
简化依赖管理 所有依赖打包在镜像中,无需在宿主机上安装 Java、Tomcat 等。
微服务架构基础 Docker 是实现微服务部署、编排(如 Kubernetes)的核心技术。
版本控制 镜像可版本化(如 myapp:1.0.0),便于回滚和管理。

1.3 核心术语

术语 说明
镜像 (Image) 一个只读模板,包含运行应用所需的一切(代码、依赖、配置、基础 OS 层)。由 Dockerfile 构建而成。
容器 (Container) 镜像的运行实例。你可以启动、停止、移动或删除容器。容器是进程隔离的。
Dockerfile 一个文本文件,包含一系列指令(FROM, COPY, RUN, CMD 等),用于自动化构建镜像。
仓库 (Registry) 存储和分发 Docker 镜像的服务。公共仓库:Docker Hub;私有仓库:Harbor, AWS ECR, 阿里云容器镜像服务
层 (Layer) 镜像是由一系列只读层叠加而成。每条 Dockerfile 指令(RUN, COPY 等)会创建一个新层。层是可缓存的,提高构建效率。
多阶段构建 (Multi-stage Build) 在一个 Dockerfile 中使用多个 FROM 指令,将构建过程(需要 JDK)和运行时(只需要 JRE)分离,显著减小最终镜像大小
卷 (Volume) 用于持久化数据或在容器间共享数据的机制。数据独立于容器生命周期。
网络 (Network) 容器间通信的虚拟网络。Docker 提供 bridge, host, overlay 等网络模式。

二、详细操作步骤

步骤 1:准备 Spring Boot 应用

  1. 创建或获取一个 Spring Boot 项目
  2. 确保应用可独立运行:使用 spring-boot-maven-pluginspring-boot-gradle-plugin 构建一个可执行的 Fat Jar
  3. 构建 Fat Jar
    # Maven
    mvn clean package
    # Gradle
    ./gradlew clean build
    
    生成的 JAR 文件通常在 target/build/libs/ 目录下,如 myapp-0.0.1-SNAPSHOT.jar

步骤 2:创建 Dockerfile

在项目根目录创建名为 Dockerfile 的文件(无后缀)。

方案 A:基础方案(不推荐用于生产)

# Dockerfile (基础版 - 不推荐)
# 1. 基础镜像:选择一个包含 JRE 的镜像
FROM openjdk:8-jre-slim

# 2. 设置工作目录
WORKDIR /app

# 3. 将本地构建好的 Fat Jar 复制到容器内的 /app 目录
# 注意:需要先在宿主机执行 mvn package
COPY target/myapp-*.jar app.jar

# 4. 暴露应用端口(如 8080)
EXPOSE 8080

# 5. 容器启动时运行的命令
# 使用 exec 格式避免 shell 问题
CMD ["java", "-jar", "app.jar"]

方案 B:多阶段构建(推荐!生产最佳实践)

# Dockerfile (多阶段构建 - 推荐)
# ==================== 第一阶段:构建 (Builder) ====================
# 使用包含 JDK 的镜像进行编译
FROM maven:3.8.6-openjdk-8-slim AS builder

# 设置工作目录
WORKDIR /workspace

# 先复制 pom.xml,利用 Docker 缓存优化构建速度
COPY pom.xml .

# 下载依赖(如果 pom.xml 未变,此层可缓存)
RUN mvn dependency:go-offline -B

# 复制源码
COPY src src

# 执行编译并打包,生成 Fat Jar
RUN mvn package -DskipTests

# ==================== 第二阶段:运行时 ====================
# 使用极小的 JRE 镜像作为运行时基础
FROM openjdk:8-jre-slim

# 创建非 root 用户(安全最佳实践)
RUN addgroup --system spring && adduser --system spring --ingroup spring
USER spring:spring

# 设置工作目录
WORKDIR /app

# 从第一阶段的构建结果中复制 Fat Jar
# 注意:路径是第一阶段的 WORKDIR
COPY --from=builder /workspace/target/myapp-*.jar app.jar

# 暴露端口
EXPOSE 8080

# 使用 exec 格式运行
# 可以添加 JVM 参数
CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]

多阶段构建优势

  • 最终镜像极小:只包含 JRE 和你的 JAR,不含 Maven、JDK、源码。
  • 安全性更高:运行时容器没有构建工具和源码。
  • 构建缓存pom.xml 和依赖层可缓存,加快后续构建。

步骤 3:构建 Docker 镜像

Dockerfile 所在目录执行:

# 构建镜像,-t 指定镜像名称和标签
# 格式:docker build -t <镜像名>:<标签> <上下文路径>
# . 表示当前目录为构建上下文
docker build -t my-spring-boot-app:1.0.0 .

# 查看构建的镜像
docker images | grep my-spring-boot-app
# 输出示例:
# my-spring-boot-app   1.0.0   a1b2c3d4e5f6   20 minutes ago   250MB

步骤 4:运行容器

# 基本运行
docker run -d --name myapp-container -p 8080:8080 my-spring-boot-app:1.0.0

# 常用参数详解:
# -d: 后台运行 (detached)
# --name: 指定容器名称
# -p: 端口映射,宿主机端口:容器端口
# -e: 设置环境变量
# -v: 挂载卷
# --rm: 容器停止后自动删除 (适合临时测试)
# --network: 指定网络

步骤 5:验证应用

# 查看容器运行状态
docker ps

# 查看容器日志
docker logs myapp-container

# 访问应用
curl http://localhost:8080/actuator/health

# 进入容器内部 (调试用)
docker exec -it myapp-container /bin/sh
# 或 bash (如果镜像有 bash)
# docker exec -it myapp-container /bin/bash

步骤 6:使用 .dockerignore (可选但推荐)

创建 .dockerignore 文件,防止不必要的文件被复制到构建上下文中,加快构建速度。

# .dockerignore
target/
!target/*.jar
*.log
.git
.gitignore
README.md
Dockerfile
.dockerignore
*.md
**/.idea/
**/*.iml
**/.classpath
**/.project
**/.settings/
**/node_modules/

步骤 7:推送镜像到仓库(生产环境)

# 1. 登录到镜像仓库 (例如 Docker Hub)
docker login

# 2. 给本地镜像打标签 (Tag),格式: <仓库地址>/<用户名>/<镜像名>:<标签>
# Docker Hub 示例:
docker tag my-spring-boot-app:1.0.0 your-dockerhub-username/my-spring-boot-app:1.0.0
# 私有仓库示例 (阿里云):
# docker tag my-spring-boot-app:1.0.0 registry.cn-hangzhou.aliyuncs.com/your-namespace/my-spring-boot-app:1.0.0

# 3. 推送镜像
docker push your-dockerhub-username/my-spring-boot-app:1.0.0
# 或
# docker push registry.cn-hangzhou.aliyuncs.com/your-namespace/my-spring-boot-app:1.0.0

步骤 8:使用 docker-compose (本地开发/测试)

创建 docker-compose.yml 文件,用于定义和运行多容器应用。

# docker-compose.yml
version: '3.8' # 指定版本

services:
  # 定义你的 Spring Boot 应用服务
  app:
    # 构建方式:基于当前目录的 Dockerfile
    build:
      context: .
      # 可指定 Dockerfile 文件名
      # dockerfile: Dockerfile
    # 或使用已构建的镜像
    # image: my-spring-boot-app:1.0.0
    # 端口映射
    ports:
      - "8080:8080"
    # 环境变量
    environment:
      SPRING_PROFILES_ACTIVE: "docker"
      SPRING_DATASOURCE_URL: "jdbc:mysql://db:3306/mydb"
      # ... 其他配置
    # 依赖的服务,确保 db 先启动
    depends_on:
      - db
    # 挂载卷 (用于配置文件热更新,开发环境)
    # volumes:
    #   - ./config/application-docker.yml:/app/config/application-docker.yml
    # 重启策略
    restart: unless-stopped

  # 定义数据库服务 (MySQL)
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: mydb
      MYSQL_USER: user
      MYSQL_PASSWORD: userpass
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql # 持久化数据
    restart: unless-stopped

  # 定义 Redis 服务
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    restart: unless-stopped

# 定义卷
volumes:
  db_data:

使用 docker-compose

# 构建并启动所有服务
docker-compose up -d

# 查看服务状态
docker-compose ps

# 查看日志
docker-compose logs -f app # -f 表示实时输出

# 停止并删除所有服务
docker-compose down

# 仅停止
# docker-compose stop

三、常见错误与解决方案

错误现象 原因分析 解决方案
COPY failed: no such file or directory DockerfileCOPY 的路径在构建上下文中不存在 1. 确认 COPY 的源路径相对于 docker build 命令的上下文路径正确。
2. 确保 Fat Jar 已构建 (mvn package)。
3. 检查 .dockerignore 是否误排除了 JAR 文件。
java.lang.UnsupportedClassVersionError 镜像中的 JRE 版本低于应用编译的 JDK 版本 检查 DockerfileFROM 镜像(如 openjdk:17-jre-slim)是否匹配应用的 sourceCompatibility/targetCompatibility
应用启动后立即退出 CMDENTRYPOINT 配置错误,或应用启动失败 1. 使用 docker logs <container_name> 查看详细错误日志。
2. 检查 CMD 语法(推荐 exec 格式)。
3> 确认主类和 JAR 名称正确。
端口无法访问 (Connection refused) 1. 容器未正确映射端口 (-p)
2. 应用监听地址非 0.0.0.0
3. 防火墙/安全组限制
1. 确认 docker run -p 8080:8080
2. 确保 Spring Boot 应用监听 0.0.0.0 (默认)。
3> 检查宿主机防火墙和云服务商安全组。
docker: permission denied Docker 守护进程权限问题 (Linux) 将用户加入 docker 组:sudo usermod -aG docker $USER,然后重新登录。
镜像构建缓慢 1. 未利用缓存
2. 依赖下载慢
1. 使用 .dockerignore
2. 优化 Dockerfile 顺序(不变的指令放前面)。
3> 配置 Docker 镜像加速器(如阿里云镜像加速)。
Cannot connect to the Docker daemon Docker 服务未启动 启动 Docker 服务:sudo systemctl start docker (Linux)。

四、注意事项

  1. 基础镜像选择
    • 优先选择 -slim, -alpine 等精简版镜像(如 openjdk:17-jre-slim)。
    • Alpine 镜像更小,但基于 musl libc,某些 JNI 库可能有兼容性问题。
    • 避免使用 latest 标签,使用具体版本(如 17-jre-slim)保证可重复性。
  2. 非 root 用户
    • 生产环境必须在镜像中创建非 root 用户并使用 USER 指令切换,提高安全性。
  3. JVM 参数
    • 容器中设置 JVM 内存 (-Xmx, -Xms) 时,需考虑容器内存限制。
    • 使用 -XX:+UseContainerSupport (JDK 8u191+, 10+) 让 JVM 识别容器内存限制。
    • 使用 -Djava.security.egd=file:/dev/./urandom 加速启动(避免 /dev/random 阻塞)。
  4. 健康检查
    • Dockerfiledocker-compose.yml 中添加 HEALTHCHECK 指令,让 Docker 监控应用状态。
    HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
      CMD curl -f http://localhost:8080/actuator/health || exit 1
    
  5. 日志管理
    • 应用日志输出到 stdout/stderr,由 Docker 收集(docker logs)。
    • 避免在容器内写大日志文件到磁盘,除非挂载了卷。
  6. 数据持久化
    • 数据库、文件上传等需要持久化的数据,必须使用 Volume 挂载,不能存储在容器内部。
  7. 网络
    • 默认 bridge 网络,容器间可通过服务名通信(在 docker-compose 中)。
    • 生产环境考虑使用更复杂的网络方案(如 overlay for Swarm/K8s)。

五、使用技巧

5.1 优化 Dockerfile 构建缓存

# 好的做法:先复制依赖文件,利用缓存
COPY pom.xml .
RUN mvn dependency:go-offline -B # 依赖不变时,此层可缓存
COPY src src
RUN mvn package -DskipTests # 只有源码改变时才重新编译

5.2 使用 .dockerignore

如前所述,防止无关文件进入构建上下文。

5.3 动态注入配置

  • 环境变量:在 docker run -edocker-compose.yml 中设置 SPRING_PROFILES_ACTIVE, SPRING_DATASOURCE_URL 等。
  • 外部配置文件:通过 -v 挂载卷,将宿主机的配置文件映射到容器内。
    docker run -d \
      -v /host/config/application-prod.yml:/app/config/application-prod.yml \
      my-spring-boot-app:1.0.0
    
    Spring Boot 会自动加载 config/ 目录下的配置。

5.4 调试容器

# 查看容器内进程
docker top myapp-container

# 查看容器资源使用 (CPU, 内存)
docker stats myapp-container

# 进入容器
docker exec -it myapp-container sh

# 查看容器详细信息 (IP, 端口映射等)
docker inspect myapp-container

5.5 使用 BuildKit (Docker 18.09+)

启用 BuildKit 可以获得更好的性能和特性(如秘密管理)。

# 临时启用
DOCKER_BUILDKIT=1 docker build -t myapp:1.0.0 .

# 或在 ~/.docker/config.json 中永久启用
# { "features": { "buildkit": true } }

六、最佳实践

  1. 使用多阶段构建:这是减小镜像大小和提高安全性的黄金标准
  2. 选择合适的精简基础镜像*-slim*-alpine
  3. 以非 root 用户运行:显著降低安全风险。
  4. 设置合理的资源限制
    docker run -d \
      --memory=512m \
      --cpus=1.0 \
      my-spring-boot-app:1.0.0
    
  5. 配置健康检查 (HEALTHCHECK):让编排系统(如 K8s)能正确管理容器生命周期。
  6. 日志输出到 stdout/stderr:方便集中收集(如 ELK, Fluentd)。
  7. 使用 .dockerignore:优化构建过程。
  8. 镜像版本化:使用语义化版本号(1.0.0, v1.2.3),避免 latest
  9. 使用 docker-compose 管理多服务:简化本地开发和测试。
  10. 集成 CI/CD:在 Jenkins, GitLab CI, GitHub Actions 等中自动化构建、测试、推送镜像。
  11. 安全扫描:使用 docker scan <image> 或 Trivy 等工具扫描镜像漏洞。
  12. 监控:结合 Prometheus/Grafana 监控容器和应用指标。

七、性能优化

  1. 减小镜像大小
    • 多阶段构建。
    • 选择精简基础镜像。
    • 删除不必要的文件(如文档、缓存)。
    • 合并 RUN 指令(减少层数,但注意可读性和缓存)。
  2. JVM 调优
    • 设置合理的堆内存-Xms-Xmx 设为相同值,避免动态调整开销。
    • 考虑容器内存限制:确保 -Xmx < 容器内存限制,留出空间给非堆内存和系统。
    • 选择合适的 GC:对于响应时间敏感的应用,考虑 G1GCZGC/Shenandoah (JDK 11+)。
    • 利用容器支持:确保使用 -XX:+UseContainerSupport
  3. 优化构建速度
    • .dockerignore
    • 利用构建缓存(依赖文件放前面)。
    • 使用 BuildKit。
    • 配置镜像加速器。
  4. 网络优化
    • 对于高并发应用,考虑使用 host 网络模式(--network=host)减少网络开销(牺牲隔离性)。
    • 确保 EXPOSE 端口与应用实际监听端口一致。
  5. 资源隔离
    • 使用 --memory, --cpus 限制容器资源,防止一个容器耗尽宿主机资源。
  6. 应用自身优化
    • Spring Boot 应用本身的性能(数据库查询、缓存、异步处理)是关键。容器化不会解决应用层面的性能瓶颈。

总结

使用 Docker 部署 Spring Boot 应用已成为现代开发的标准实践。

核心步骤回顾

  1. 构建 Fat Jar (mvn package)。
  2. 编写 Dockerfile务必使用多阶段构建
  3. 构建镜像 (docker build)。
  4. 运行容器 (docker run)。
  5. (可选) 使用 docker-compose 管理多服务。
  6. (生产) 推送镜像到仓库
  7. (生产) 配置健康检查、资源限制、安全策略

成功关键安全(非 root 用户、最小权限)、效率(小镜像、快构建)、可观测性(日志、监控)、自动化(CI/CD)。遵循最佳实践,你的 Spring Boot 应用将高效、可靠地运行在 Docker 容器中。