前言
Docker 容器化是现代应用部署的标准方式。Spring Boot 应用可以方便地容器化部署。本文将介绍 Spring Boot Docker 化的完整方案。
基础 Dockerfile
1. 简单 Dockerfile
# 基础镜像
FROM eclipse-temurin:21-jre-alpine
# 维护者信息
LABEL maintainer="your-email@example.com"
# 设置工作目录
WORKDIR /app
# 复制 jar 文件
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar
# 暴露端口
EXPOSE 8080
# 启动命令
ENTRYPOINT ["java", "-jar", "app.jar"]
2. 构建镜像
# 构建 jar
mvn clean package -DskipTests
# 构建镜像
docker build -t demo:1.0.0 .
# 运行容器
docker run -d -p 8080:8080 --name demo demo:1.0.0
# 查看日志
docker logs -f demo
多阶段构建
1. 使用 Maven 多阶段
# 构建阶段
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
# 复制 pom.xml
COPY pom.xml .
# 下载依赖(利用缓存)
RUN mvn dependency:go-offline -B
# 复制源代码
COPY src ./src
# 构建 jar
RUN mvn clean package -DskipTests -B
# 运行阶段
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
# 创建非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# 复制 jar 文件
COPY --from=build /app/target/demo-0.0.1-SNAPSHOT.jar app.jar
# 切换用户
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
2. 使用 Jib 插件
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<from>
<image>eclipse-temurin:21-jre-alpine</image>
</from>
<to>
<image>demo:1.0.0</image>
</to>
<container>
<ports>
<port>8080</port>
</ports>
<user>appuser</user>
<environment>
<SPRING_PROFILES_ACTIVE>prod</SPRING_PROFILES_ACTIVE>
</environment>
</container>
</configuration>
</plugin>
# 构建镜像
mvn jib:build
# 构建并推送到仓库
mvn jib:build -Dimage=registry.example.com/demo:1.0.0
镜像优化
1. 分层构建
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
# 复制依赖层
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar
# 提取层
RUN java -Djarmode=layertools -jar app.jar extract
# 按顺序复制层(利用缓存)
ADD --chown=appuser:appgroup app/dependencies/ ./
ADD --chown=appuser:appgroup app/spring-boot-loader/ ./
ADD --chown=appuser:appgroup app/snapshot-dependencies/ ./
ADD --chown=appuser:appgroup app/application/ ./
EXPOSE 8080
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
2. 使用 GraalVM Native
# 构建阶段
FROM ghcr.io/graalvm/graalvm-ce:ol8-java21 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
# 安装 Native Image
RUN gu install native-image
# 构建原生镜像
RUN mvn -Pnative native:compile -B
# 运行阶段
FROM alpine:3.19
WORKDIR /app
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=build /app/target/demo app
USER appuser
EXPOSE 8080
ENTRYPOINT ["./demo"]
3. 镜像大小对比
| 类型 | 大小 | 启动时间 |
|---|---|---|
| 传统 JAR | 500MB | 5-10s |
| 分层 JAR | 350MB | 3-5s |
| Native Image | 50MB | 0.1s |
环境配置
1. 环境变量
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar
# 默认环境变量
ENV SPRING_PROFILES_ACTIVE=prod
ENV JAVA_OPTS="-Xms512m -Xmx512m"
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
# 运行时有覆盖
docker run -d -p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=dev \
-e JAVA_OPTS="-Xms1g -Xmx1g" \
demo:1.0.0
2. 配置文件挂载
# docker-compose.yml
version: '3.8'
services:
app:
image: demo:1.0.0
ports:
- "8080:8080"
volumes:
- ./application-prod.yml:/app/config/application-prod.yml
- ./logs:/app/logs
environment:
- SPRING_PROFILES_ACTIVE=prod
- TZ=Asia/Shanghai
restart: always
Docker Compose
1. 完整配置
# docker-compose.yml
version: '3.8'
services:
app:
build: .
image: demo:1.0.0
container_name: demo-app
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/demo
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD}
- SPRING_REDIS_HOST=redis
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- ./logs:/app/logs
- ./config:/app/config
networks:
- demo-network
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
mysql:
image: mysql:8.0
container_name: demo-mysql
environment:
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
- MYSQL_DATABASE=demo
- TZ=Asia/Shanghai
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- demo-network
restart: always
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: demo-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- demo-network
restart: always
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
nginx:
image: nginx:alpine
container_name: demo-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
networks:
- demo-network
restart: always
volumes:
mysql-data:
redis-data:
networks:
demo-network:
driver: bridge
2. 启动命令
# 启动所有服务
docker-compose up -d
# 查看状态
docker-compose ps
# 查看日志
docker-compose logs -f app
# 停止所有服务
docker-compose down
# 停止并删除数据
docker-compose down -v
CI/CD 集成
1. GitHub Actions
# .github/workflows/docker.yml
name: Docker Build and Push
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
demo:latest
demo:${{ github.ref_name }}
cache-from: type=registry,ref=demo:buildcache
cache-to: type=registry,ref=demo:buildcache,mode=max
2. GitLab CI
# .gitlab-ci.yml
stages:
- build
- docker
- deploy
build:
stage: build
image: maven:3.9-eclipse-temurin-21
script:
- mvn clean package -DskipTests
artifacts:
paths:
- target/*.jar
docker:
stage: docker
image: docker:24
services:
- docker:24-dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
deploy:
stage: deploy
image: bitnami/kubectl
script:
- kubectl set image deployment/demo demo=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
最佳实践
1. Dockerfile 规范
# ✅ 推荐
FROM eclipse-temurin:21-jre-alpine
LABEL maintainer="team@example.com"
WORKDIR /app
COPY --chown=appuser:appgroup target/*.jar app.jar
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]
# ❌ 不推荐
FROM ubuntu:latest # 镜像太大
RUN apt-get update && apt-get install -y openjdk-21-jdk # 层数太多
COPY . . # 包含不必要的文件
USER root # 不使用 root
CMD java -jar app.jar # 不使用 ENTRYPOINT
2. .dockerignore
# .dockerignore
target/
!.m2/wrapper/distribution/
!src/
!pom.xml
!.dockerignore
!Dockerfile
.git/
.gitignore
*.md
.idea/
*.iml
.vscode/
node_modules/
3. 安全配置
# 使用非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# 只读文件系统
RUN chmod -R 555 /app
# 最小化基础镜像
FROM eclipse-temurin:21-jre-alpine
# 扫描漏洞
# docker scan demo:1.0.0
4. 日志管理
# docker-compose.yml
services:
app:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
总结
Docker 容器化要点:
- ✅ Dockerfile - 多阶段构建、分层优化
- ✅ 镜像优化 - GraalVM Native、层缓存
- ✅ Docker Compose - 多服务编排
- ✅ CI/CD - GitHub Actions、GitLab CI
- ✅ 最佳实践 - 安全、日志、规范
Docker 是 Spring Boot 部署的标准方式。