📚 目录
- 前言与环境准备
- [准备 Java 项目](#准备 Java 项目)
- [编写 Dockerfile](#编写 Dockerfile)
- 构建与运行镜像
- 进阶配置
- [使用 Docker Compose](#使用 Docker Compose)
- 最佳实践
- 常见问题排查
1. 前言与环境准备
1.1 为什么使用 Docker 运行 Java 应用?
复制代码
┌─────────────────────────────────────────────────────────────────┐
│ 传统部署 vs Docker 部署 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 传统部署: Docker 部署: │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ App.jar │ │ Container │ │
│ ├─────────────┤ │ ┌─────────┐ │ │
│ │ JDK 8 │ ──────► │ │ App.jar │ │ │
│ ├─────────────┤ │ ├─────────┤ │ │
│ │ CentOS │ │ │ JDK │ │ │
│ └─────────────┘ │ ├─────────┤ │ │
│ │ │ Linux │ │ │
│ ❌ 环境不一致 │ └─────────┘ │ │
│ ❌ 依赖冲突 └─────────────┘ │
│ ❌ 部署复杂 │
│ ✅ 环境一致 │
│ ✅ 隔离性好 │
│ ✅ 快速部署 │
└─────────────────────────────────────────────────────────────────┘
1.2 环境准备
bash
复制代码
# 检查 Docker 是否安装
docker --version
# Docker version 24.0.0, build xxxxx
# 检查 Docker 服务状态
systemctl status docker
# 如未安装,执行以下命令(以 CentOS 为例)
yum install -y docker-ce docker-ce-cli containerd.io
systemctl start docker
systemctl enable docker
1.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.xml
2. 准备 Java 项目
2.1 示例 Spring Boot 应用
java
复制代码
// 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 打包配置
xml
复制代码
<!-- 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 文件
bash
复制代码
# 使用 Maven 打包
mvn clean package -DskipTests
# 验证 JAR 文件
ls -lh target/my-app.jar
# 本地测试运行
java -jar target/my-app.jar
3. 编写 Dockerfile
3.1 基础版 Dockerfile
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
复制代码
# 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 版本选择
dockerfile
复制代码
# ============ 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-alpine
3.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 镜像
bash
复制代码
# 基本构建命令
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-arg JAR_FILE=target/my-app.jar \
-t my-java-app:1.0.0 .
4.2 查看镜像信息
bash
复制代码
# 列出所有镜像
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
# 查看镜像历史层
docker history my-java-app:1.0.0
4.3 运行容器
bash
复制代码
# 基本运行
docker run -d -p 8080:8080 --name my-app my-java-app:1.0.0
# 带环境变量运行
docker run -d \
-p 8080:8080 \
--name my-app \
-e SPRING_PROFILES_ACTIVE=prod \
-e DATABASE_URL=jdbc:mysql://db:3306/mydb \
my-java-app:1.0.0
# 带资源限制运行
docker run -d \
-p 8080:8080 \
--name my-app \
--memory=512m \
--cpus=1.0 \
my-java-app:1.0.0
# 完整运行命令
docker run -d \
--name my-app \
-p 8080:8080 \
-v /host/logs:/app/logs \
-v /host/config:/app/config:ro \
-e JAVA_OPTS="-Xms256m -Xmx512m" \
-e SPRING_PROFILES_ACTIVE=prod \
--memory=512m \
--cpus=1.0 \
--restart=unless-stopped \
my-java-app:1.0.0
4.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 验证运行状态
bash
复制代码
# 查看运行中的容器
docker ps
# 查看容器日志
docker logs -f my-app
# 查看最近 100 行日志
docker logs --tail 100 my-app
# 进入容器内部
docker exec -it my-app /bin/sh
# 测试应用
curl http://localhost:8080/hello
# 输出: Hello from Docker! 🐳
# 查看容器资源使用
docker stats my-app
5. 进阶配置
5.1 多环境配置
dockerfile
复制代码
# 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"]
bash
复制代码
# 构建不同环境的镜像
docker build --build-arg PROFILE=dev -t my-app:dev .
docker build --build-arg PROFILE=prod -t my-app:prod .
5.2 配置文件外置
yaml
复制代码
# 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
bash
复制代码
# 运行时挂载外部配置
docker run -d \
-p 8080:8080 \
-v /opt/config/application-prod.yml:/app/config/application.yml:ro \
-v /opt/logs:/app/logs \
my-java-app:1.0.0
5.3 JVM 参数优化
dockerfile
复制代码
# 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 时区配置
dockerfile
复制代码
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 基础配置
yaml
复制代码
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
image: my-java-app:1.0.0
container_name: my-java-app
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- TZ=Asia/Shanghai
volumes:
- ./logs:/app/logs
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
6.2 完整微服务配置
yaml
复制代码
# docker-compose.yml
version: '3.8'
services:
# Java 应用
app:
build: .
image: my-java-app:1.0.0
container_name: my-java-app
ports:
- "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=6379
volumes:
- app-logs:/app/logs
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
networks:
- app-network
restart: unless-stopped
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
# MySQL 数据库
mysql:
image: mysql:8.0
container_name: my-mysql
environment:
- MYSQL_ROOT_PASSWORD=rootpassword
- MYSQL_DATABASE=mydb
- TZ=Asia/Shanghai
volumes:
- mysql-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
ports:
- "3306:3306"
networks:
- app-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
# Redis 缓存
redis:
image: redis:7-alpine
container_name: my-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- app-network
restart: unless-stopped
# Nginx 反向代理
nginx:
image: nginx:alpine
container_name: my-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- app-network
restart: unless-stopped
networks:
app-network:
driver: bridge
volumes:
app-logs:
mysql-data:
redis-data:
6.3 Docker Compose 常用命令
bash
复制代码
# 启动所有服务
docker-compose up -d
# 查看服务状态
docker-compose ps
# 查看服务日志
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 --scale app=3
7. 最佳实践
7.1 Dockerfile 最佳实践
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 镜像优化清单
bash
复制代码
# 1. 使用多阶段构建减少镜像大小
# 2. 使用 .dockerignore 排除无关文件
# .dockerignore
.git
.gitignore
README.md
.idea
*.iml
target/
!target/*.jar
node_modules
*.log
docker-compose*.yml
8. 常见问题排查
8.1 问题排查命令
bash
复制代码
# 查看容器日志
docker logs my-app
# 实时查看日志
docker logs -f my-app
# 进入容器调试
docker exec -it my-app /bin/sh
# 查看容器进程
docker top my-app
# 查看容器资源使用
docker stats my-app
# 检查容器详细信息
docker inspect my-app
# 查看容器文件系统变化
docker diff my-app
8.2 常见问题与解决方案
复制代码
┌─────────────────────────────────────────────────────────────────┐
│ 常见问题排查 │
├────────────────────────┬────────────────────────────────────────┤
│ 问题 │ 解决方案 │
├────────────────────────┼────────────────────────────────────────┤
│ OOM (内存不足) │ 增加 --memory 限制 │
│ │ 调整 JVM 参数 -Xmx │
├────────────────────────┼────────────────────────────────────────┤
│ 容器启动后立即退出 │ 检查 ENTRYPOINT/CMD │
│ │ 查看 docker logs │
├────────────────────────┼────────────────────────────────────────┤
│ 端口无法访问 │ 检查端口映射 -p │
│ │ 检查防火墙设置 │
├────────────────────────┼────────────────────────────────────────┤
│ 时区不正确 │ 设置 TZ 环境变量 │
│ │ 挂载时区文件 │
├────────────────────────┼────────────────────────────────────────┤
│ 中文乱码 │ 设置 LANG=C.UTF-8 │
│ │ 安装字体包 │
├────────────────────────┼────────────────────────────────────────┤
│ 无法连接数据库 │ 检查网络配置 │
│ │ 使用容器名作为主机名 │
└────────────────────────┴────────────────────────────────────────┘
8.3 性能监控
bash
复制代码
# 实时监控资源使用
docker stats
# 使用 jps 查看 Java 进程
docker exec my-app jps -l
# 查看 JVM 堆内存使用
docker exec my-app jmap -heap <PID>
# 导出堆转储文件
docker exec my-app jmap -dump:format=b,file=/tmp/heap.hprof <PID>
docker cp 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) │
│ │
└─────────────────────────────────────────────────────────────────┘
快速参考命令
bash
复制代码
# 完整工作流
mvn clean package -DskipTests # 1. 打包
docker build -t my-app:1.0.0 . # 2. 构建镜像
docker run -d -p 8080:8080 my-app:1.0.0 # 3. 运行容器
curl http://localhost:8080/hello # 4. 验证
docker logs -f <container-id> # 5. 查看日志