📚 目录 前言与环境准备 准备 Java 项目 编写 Dockerfile 构建与运行镜像 进阶配置 使用 Docker Compose 最佳实践 常见问题排查 1. 前言与环境准备 1.1 为什么使用 Docker 运行 Java 应用? ┌─────────────────────────────────────────────────────────────────┐ │ 传统部署 vs Docker 部署 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 传统部署: Docker 部署: │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ App.jar │ │ Container │ │ │ ├─────────────┤ │ ┌─────────┐ │ │ │ │ JDK 8 │ ──────► │ │ App.jar │ │ │ │ ├─────────────┤ │ ├─────────┤ │ │ │ │ CentOS │ │ │ JDK │ │ │ │ └─────────────┘ │ ├─────────┤ │ │ │ │ │ Linux │ │ │ │ ❌ 环境不一致 │ └─────────┘ │ │ │ ❌ 依赖冲突 └─────────────┘ │ │ ❌ 部署复杂 │ │ ✅ 环境一致 │ │ ✅ 隔离性好 │ │ ✅ 快速部署 │ └─────────────────────────────────────────────────────────────────┘1.2 环境准备 # 检查 Docker 是否安装 docker --version# Docker version 24.0.0, build xxxxx # 检查 Docker 服务状态 systemctl status docker# 如未安装,执行以下命令(以 CentOS 为例) yuminstall -y docker-ce docker-ce-cli containerd.io systemctl start docker systemctlenable docker1.3 项目结构 my-java-app/ ├── src/ │ └── main/ │ └── java/ │ └── com/example/ │ └── Application.java ├── target/ │ └── my-app-1.0.0.jar # 打包后的 JAR 文件 ├── Dockerfile # Docker 构建文件 ├── docker-compose.yml # Docker Compose 配置 └── pom.xml2. 准备 Java 项目 2.1 示例 Spring Boot 应用 // Application.java package com. example ; import org. springframework. boot. SpringApplication ; import org. springframework. boot. autoconfigure. SpringBootApplication ; import org. springframework. web. bind. annotation. GetMapping ; import org. springframework. web. bind. annotation. RestController ; @SpringBootApplication @RestController public class Application { public static void main ( String [ ] args) { SpringApplication . run ( Application . class , args) ; } @GetMapping ( "/hello" ) public String hello ( ) { return "Hello from Docker! 🐳" ; } @GetMapping ( "/health" ) public String health ( ) { return "OK" ; } } 2.2 Maven 打包配置 <!-- pom.xml --> < project> < groupId> com.example</ groupId> < artifactId> my-app</ artifactId> < version> 1.0.0</ version> < packaging> jar</ packaging> < parent> < groupId> org.springframework.boot</ groupId> < artifactId> spring-boot-starter-parent</ artifactId> < version> 2.7.0</ version> </ parent> < dependencies> < dependency> < groupId> org.springframework.boot</ groupId> < artifactId> spring-boot-starter-web</ artifactId> </ dependency> </ dependencies> < build> < plugins> < plugin> < groupId> org.springframework.boot</ groupId> < artifactId> spring-boot-maven-plugin</ artifactId> </ plugin> </ plugins> <!-- 指定打包后的文件名 --> < finalName> my-app</ finalName> </ build> </ project> 2.3 打包 JAR 文件 # 使用 Maven 打包 mvn clean package -DskipTests# 验证 JAR 文件 ls -lh target/my-app.jar# 本地测试运行 java -jar target/my-app.jar3. 编写 Dockerfile 3.1 基础版 Dockerfile # Dockerfile # 使用官方 OpenJDK 镜像作为基础镜像 FROM openjdk:8-jdk-alpine # 维护者信息 LABEL maintainer="your-email@example.com" LABEL version="1.0" LABEL description="My Java Application" # 设置工作目录 WORKDIR /app # 复制 JAR 文件到容器 COPY target/my-app.jar app.jar # 暴露应用端口 EXPOSE 8080 # 启动命令 ENTRYPOINT ["java", "-jar", "app.jar"]3.2 优化版 Dockerfile(推荐) # Dockerfile # ============ 第一阶段:构建阶段 ============ FROM maven:3.8.6-openjdk-8-slim AS builder WORKDIR /build # 先复制 pom.xml,利用 Docker 缓存机制 COPY pom.xml . RUN mvn dependency:go-offline -B # 复制源代码并构建 COPY src ./src RUN mvn clean package -DskipTests # ============ 第二阶段:运行阶段 ============ FROM openjdk:8-jre-alpine # 创建非 root 用户(安全最佳实践) RUN addgroup -S appgroup && adduser -S appuser -G appgroup WORKDIR /app # 从构建阶段复制 JAR 文件 COPY --from=builder /build/target/*.jar app.jar # 更改文件所有权 RUN chown -R appuser:appgroup /app # 切换到非 root 用户 USER appuser # 暴露端口 EXPOSE 8080 # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD wget --quiet --tries=1 --spider http://localhost:8080/health || exit 1 # JVM 参数优化 ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC" # 启动命令 ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]3.3 不同 JDK 版本选择 # ============ JDK 版本选择参考 ============ # JDK 8 (经典稳定版) FROM openjdk:8-jdk-alpine # 完整 JDK,105MB FROM openjdk:8-jre-alpine # 仅运行时,85MB (推荐) # JDK 11 (LTS 长期支持版) FROM openjdk:11-jdk-slim # 精简版 FROM eclipse-temurin:11-jre # Eclipse Temurin (推荐) # JDK 17 (最新 LTS 版) FROM eclipse-temurin:17-jdk-alpine FROM eclipse-temurin:17-jre-alpine # 推荐 # JDK 21 (最新 LTS 版) FROM eclipse-temurin:21-jre-alpine3.4 镜像大小对比 ┌─────────────────────────────────────────────────────────────┐ │ 镜像大小对比 │ ├─────────────────────────────┬───────────────────────────────┤ │ 基础镜像 │ 大小 │ ├─────────────────────────────┼───────────────────────────────┤ │ openjdk:8 │ ~500MB │ │ openjdk:8-jdk-alpine │ ~105MB │ │ openjdk:8-jre-alpine │ ~85MB ⭐ 推荐 │ │ eclipse-temurin:17-jre │ ~260MB │ │ eclipse-temurin:17-jre-alpine │ ~180MB │ └─────────────────────────────┴───────────────────────────────┘4. 构建与运行镜像 4.1 构建 Docker 镜像 # 基本构建命令 docker build -t my-java-app:1.0.0. # 带详细日志的构建 docker build --progress= plain -t my-java-app:1.0.0. # 不使用缓存构建 docker build --no-cache -t my-java-app:1.0.0. # 构建时传递参数 docker build\ --build-argJAR_FILE = target/my-app.jar\ -t my-java-app:1.0.0. 4.2 查看镜像信息 # 列出所有镜像 docker images# 输出示例: # REPOSITORY TAG IMAGE ID CREATED SIZE # my-java-app 1.0.0 a1b2c3d4e5f6 30 seconds ago 145MB # 查看镜像详细信息 docker inspect my-java-app:1.0.0# 查看镜像历史层 dockerhistory my-java-app:1.0.04.3 运行容器 # 基本运行 docker run -d -p8080 :8080 --name my-app my-java-app:1.0.0# 带环境变量运行 docker run -d\ -p8080 :8080\ --name my-app\ -eSPRING_PROFILES_ACTIVE = prod\ -eDATABASE_URL = jdbc:mysql://db:3306/mydb\ my-java-app:1.0.0# 带资源限制运行 docker run -d\ -p8080 :8080\ --name my-app\ --memory= 512m\ --cpus= 1.0 \ my-java-app:1.0.0# 完整运行命令 docker run -d\ --name my-app\ -p8080 :8080\ -v /host/logs:/app/logs\ -v /host/config:/app/config:ro\ -eJAVA_OPTS = "-Xms256m -Xmx512m" \ -eSPRING_PROFILES_ACTIVE = prod\ --memory= 512m\ --cpus= 1.0 \ --restart= unless-stopped\ my-java-app:1.0.04.4 运行参数说明 ┌──────────────────────────────────────────────────────────────────┐ │ Docker Run 参数说明 │ ├────────────────────┬─────────────────────────────────────────────┤ │ 参数 │ 说明 │ ├────────────────────┼─────────────────────────────────────────────┤ │ -d │ 后台运行容器 │ │ -p 8080:8080 │ 端口映射 (宿主机:容器) │ │ --name my-app │ 指定容器名称 │ │ -e KEY=VALUE │ 设置环境变量 │ │ -v /host:/container│ 挂载数据卷 │ │ --memory=512m │ 限制内存使用 │ │ --cpus=1.0 │ 限制 CPU 使用 │ │ --restart │ 重启策略 (no/always/unless-stopped) │ │ --network │ 指定网络 │ └────────────────────┴─────────────────────────────────────────────┘4.5 验证运行状态 # 查看运行中的容器 dockerps # 查看容器日志 docker logs -f my-app# 查看最近 100 行日志 docker logs --tail100 my-app# 进入容器内部 dockerexec -it my-app /bin/sh# 测试应用 curl http://localhost:8080/hello# 输出: Hello from Docker! 🐳 # 查看容器资源使用 docker stats my-app5. 进阶配置 5.1 多环境配置 # Dockerfile with args FROM eclipse-temurin:17-jre-alpine ARG PROFILE=dev ARG APP_PORT=8080 ENV SPRING_PROFILES_ACTIVE=${PROFILE} ENV SERVER_PORT=${APP_PORT} WORKDIR /app COPY target/*.jar app.jar EXPOSE ${APP_PORT} ENTRYPOINT ["sh", "-c", "java -jar app.jar"]# 构建不同环境的镜像 docker build --build-argPROFILE = dev -t my-app:dev. docker build --build-argPROFILE = prod -t my-app:prod. 5.2 配置文件外置 # application.yml server : port : ${ SERVER_PORT: 8080 } spring : profiles : active : ${ SPRING_PROFILES_ACTIVE: dev} datasource : url : ${ DATABASE_URL: jdbc: h2: mem: testdb} username : ${ DATABASE_USER: sa} password : ${ DATABASE_PASSWORD: } logging : level : root : ${ LOG_LEVEL: INFO} file : path : /app/logs# 运行时挂载外部配置 docker run -d\ -p8080 :8080\ -v /opt/config/application-prod.yml:/app/config/application.yml:ro\ -v /opt/logs:/app/logs\ my-java-app:1.0.05.3 JVM 参数优化 # Dockerfile FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY target/*.jar app.jar # 容器环境 JVM 优化参数 ENV JAVA_OPTS="\ -XX:+UseContainerSupport \ -XX:MaxRAMPercentage=75.0 \ -XX:InitialRAMPercentage=50.0 \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=100 \ -XX:+UseStringDeduplication \ -Djava.security.egd=file:/dev/./urandom \ -Dfile.encoding=UTF-8" EXPOSE 8080 ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]5.4 时区配置 FROM eclipse-temurin:17-jre-alpine # 设置时区为中国 ENV TZ=Asia/Shanghai RUN apk add --no-cache tzdata && \ ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \ echo $TZ > /etc/timezone WORKDIR /app COPY target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]6. 使用 Docker Compose 6.1 基础配置 # docker-compose.yml version : '3.8' services : app : build : context : .dockerfile : Dockerfileimage : my- java- app: 1.0.0container_name : my- java- appports : - "8080:8080" environment : - SPRING_PROFILES_ACTIVE=prod- TZ=Asia/Shanghaivolumes : - ./logs: /app/logsrestart : unless- stoppedhealthcheck : test : [ "CMD" , "wget" , "-q" , "--spider" , "http://localhost:8080/health" ] interval : 30stimeout : 10sretries : 3 start_period : 60s6.2 完整微服务配置 # docker-compose.yml version : '3.8' services : # Java 应用 app : build : .image : my- java- app: 1.0.0container_name : my- java- appports : - "8080:8080" environment : - SPRING_PROFILES_ACTIVE=prod- DATABASE_URL=jdbc: mysql: //mysql: 3306/mydb? useSSL=false&serverTimezone=Asia/Shanghai - DATABASE_USER=root- DATABASE_PASSWORD=rootpassword- REDIS_HOST=redis- REDIS_PORT=6379volumes : - app- logs: /app/logsdepends_on : mysql : condition : service_healthyredis : condition : service_startednetworks : - app- networkrestart : unless- stoppeddeploy : resources : limits : cpus : '1.0' memory : 512M# MySQL 数据库 mysql : image : mysql: 8.0 container_name : my- mysqlenvironment : - MYSQL_ROOT_PASSWORD=rootpassword- MYSQL_DATABASE=mydb- TZ=Asia/Shanghaivolumes : - mysql- data: /var/lib/mysql- ./init.sql: /docker- entrypoint- initdb.d/init.sql: roports : - "3306:3306" networks : - app- networkhealthcheck : test : [ "CMD" , "mysqladmin" , "ping" , "-h" , "localhost" ] interval : 10stimeout : 5sretries : 5 restart : unless- stopped# Redis 缓存 redis : image : redis: 7- alpinecontainer_name : my- redisports : - "6379:6379" volumes : - redis- data: /datanetworks : - app- networkrestart : unless- stopped# Nginx 反向代理 nginx : image : nginx: alpinecontainer_name : my- nginxports : - "80:80" - "443:443" volumes : - ./nginx.conf: /etc/nginx/nginx.conf: ro- ./ssl: /etc/nginx/ssl: rodepends_on : - appnetworks : - app- networkrestart : unless- stoppednetworks : app-network : driver : bridgevolumes : app-logs : mysql-data : redis-data : 6.3 Docker Compose 常用命令 # 启动所有服务 docker-compose up -d# 查看服务状态 docker-composeps # 查看服务日志 docker-compose logs -f app# 重新构建并启动 docker-compose up -d --build# 停止所有服务 docker-compose down# 停止并删除数据卷 docker-compose down -v# 重启单个服务 docker-compose restart app# 扩展服务实例 docker-compose up -d --scaleapp = 3 7. 最佳实践 7.1 Dockerfile 最佳实践 # ✅ 最佳实践示例 FROM eclipse-temurin:17-jre-alpine # 1. 使用标签 LABEL maintainer="dev@example.com" \ version="1.0.0" \ description="Production Java Application" # 2. 创建非 root 用户 RUN addgroup -S appgroup && adduser -S appuser -G appgroup # 3. 设置工作目录 WORKDIR /app # 4. 最小化层数,清理缓存 RUN apk add --no-cache tzdata curl && \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone && \ apk del tzdata # 5. 复制文件并设置权限 COPY --chown=appuser:appgroup target/*.jar app.jar # 6. 切换用户 USER appuser # 7. 设置健康检查 HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1 # 8. 暴露端口 EXPOSE 8080 # 9. 使用 ENTRYPOINT ENTRYPOINT ["java", \ "-XX:+UseContainerSupport", \ "-XX:MaxRAMPercentage=75.0", \ "-jar", "app.jar"]7.2 安全最佳实践 ┌─────────────────────────────────────────────────────────────────┐ │ Docker 安全最佳实践 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ✅ 使用非 root 用户运行应用 │ │ ✅ 使用最小化基础镜像 (alpine) │ │ ✅ 定期更新基础镜像 │ │ ✅ 扫描镜像漏洞 (Trivy, Snyk) │ │ ✅ 不在镜像中存储敏感信息 │ │ ✅ 使用 secrets 管理敏感数据 │ │ ✅ 限制容器资源使用 │ │ ✅ 只暴露必要端口 │ │ ✅ 使用只读文件系统 (--read-only) │ │ │ └─────────────────────────────────────────────────────────────────┘7.3 镜像优化清单 # 1. 使用多阶段构建减少镜像大小 # 2. 使用 .dockerignore 排除无关文件 # .dockerignore .git .gitignore README.md .idea *.iml target/! target/*.jar node_modules *.log docker-compose*.yml8. 常见问题排查 8.1 问题排查命令 # 查看容器日志 docker logs my-app# 实时查看日志 docker logs -f my-app# 进入容器调试 dockerexec -it my-app /bin/sh# 查看容器进程 dockertop my-app# 查看容器资源使用 docker stats my-app# 检查容器详细信息 docker inspect my-app# 查看容器文件系统变化 dockerdiff my-app8.2 常见问题与解决方案 ┌─────────────────────────────────────────────────────────────────┐ │ 常见问题排查 │ ├────────────────────────┬────────────────────────────────────────┤ │ 问题 │ 解决方案 │ ├────────────────────────┼────────────────────────────────────────┤ │ OOM (内存不足) │ 增加 --memory 限制 │ │ │ 调整 JVM 参数 -Xmx │ ├────────────────────────┼────────────────────────────────────────┤ │ 容器启动后立即退出 │ 检查 ENTRYPOINT/CMD │ │ │ 查看 docker logs │ ├────────────────────────┼────────────────────────────────────────┤ │ 端口无法访问 │ 检查端口映射 -p │ │ │ 检查防火墙设置 │ ├────────────────────────┼────────────────────────────────────────┤ │ 时区不正确 │ 设置 TZ 环境变量 │ │ │ 挂载时区文件 │ ├────────────────────────┼────────────────────────────────────────┤ │ 中文乱码 │ 设置 LANG=C.UTF-8 │ │ │ 安装字体包 │ ├────────────────────────┼────────────────────────────────────────┤ │ 无法连接数据库 │ 检查网络配置 │ │ │ 使用容器名作为主机名 │ └────────────────────────┴────────────────────────────────────────┘8.3 性能监控 # 实时监控资源使用 docker stats# 使用 jps 查看 Java 进程 dockerexec my-app jps -l# 查看 JVM 堆内存使用 dockerexec my-app jmap -heap< PID> # 导出堆转储文件 dockerexec my-app jmap -dump:format= b,file= /tmp/heap.hprof< PID> dockercp my-app:/tmp/heap.hprof ./heap.hprof📝 总结 ┌─────────────────────────────────────────────────────────────────┐ │ Docker 部署 Java 应用流程 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 1. 准备阶段 │ │ └── 打包 JAR 文件 (mvn package) │ │ │ │ 2. 容器化阶段 │ │ ├── 编写 Dockerfile │ │ ├── 构建镜像 (docker build) │ │ └── 测试运行 (docker run) │ │ │ │ 3. 部署阶段 │ │ ├── 推送镜像到仓库 (docker push) │ │ ├── 拉取镜像 (docker pull) │ │ └── 运行容器 (docker-compose up) │ │ │ │ 4. 运维阶段 │ │ ├── 日志监控 (docker logs) │ │ ├── 性能监控 (docker stats) │ │ └── 故障排查 (docker exec) │ │ │ └─────────────────────────────────────────────────────────────────┘快速参考命令 # 完整工作流 mvn clean package -DskipTests# 1. 打包 docker build -t my-app:1.0.0. # 2. 构建镜像 docker run -d -p8080 :8080 my-app:1.0.0# 3. 运行容器 curl http://localhost:8080/hello# 4. 验证 docker logs -f< container-id> # 5. 查看日志