springboot
Docker 容器化与生产部署
By AI-Writer 13 min read
Docker 容器化与生产部署
将 Spring Boot 应用容器化是现代云原生部署的标准方式。Spring Boot 内置的 fat jar 机制和分层镜像构建能力,让 Docker 镜像既小又快。本文覆盖从镜像构建到 CI/CD 流水线的完整流程。
Spring Boot fat jar 原理
Spring Boot 应用打包后是一个可直接运行的 fat jar(也称 uber-jar),其内部结构如下:
plaintext
boot-app.jar
├── META-INF/
│ └── MANIFEST.MF
│ Main-Class: org.springframework.boot.loader.JarLauncher
├── BOOT-INF/
│ ├── classes/ # 项目编译后的 class 文件和资源
│ │ └── com/example/demo/
│ │ └── DemoApplication.class
│ └── lib/ # 所有依赖 JAR
│ ├── spring-boot-3.4.0.jar
│ ├── spring-core-6.x.jar
│ ├── HikariCP-5.x.jar
│ └── ...关键点:Main-Class 不是你的启动类,而是 JarLauncher。它使用自定义类加载器,按以下顺序加载:
BOOT-INF/classes/— 项目代码BOOT-INF/lib/*.jar— 依赖 JAR
这样同一个依赖 JAR 在 fat jar 中只能存在一份,同时与用户代码分离,为分层镜像提供了基础。
分层 Docker 镜像构建(Layered JAR)
Spring Boot 3.x 支持分层 jar(layered jar),将 fat jar 的内容分成多个逻辑层,从而实现增量构建,大幅缩短镜像构建和推送时间。
启用分层
xml
<!-- pom.xml -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>
</plugins>
</build>查看分层结构:
bash
./mvnw spring-boot:layered-jar
java -Djarmode=layertools -jar target/demo-0.0.1.jar list输出:
plaintext
dependencies
spring-boot-loader
snapshot-dependencies
applicationDockerfile(分层构建)
dockerfile
# multi-stage build:先构建 jar,再分 layer 打包
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY .mvn/ .mvn/
COPY mvnw pom.xml ./
RUN chmod +x mvnw
RUN ./mvnw dependency:go-offline -B
COPY src ./src
RUN ./mvnw package -DskipTests -B
# 从分层 jar 中提取各层
FROM eclipse-temurin:21-jre-alpine AS runtime
# 分离各层,提高构建缓存命中率
COPY --from=builder /app/target/demo-0.0.1-SNAPSHOT.jar /app/app.jar
RUN java -Djarmode=layertools -jar /app/app.jar extract
# 顺序:dependencies(变动最少)→ snapshot-dependencies → spring-boot-loader → application
FROM eclipse-temurin:21-jre-alpine
ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC"
# 按顺序 COPY,利用 Docker 缓存
COPY --from=runtime app/dependencies/ ./
COPY --from=runtime app/spring-boot-loader/ ./
COPY --from=runtime app/snapshot-dependencies/ ./
COPY --from=runtime app/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]构建优化效果
| 方式 | 镜像大小(典型) | 首次构建 | 依赖变更后重建 |
|---|---|---|---|
| 普通 fat jar | ~350 MB | 慢 | 整个镜像重传 |
| 分层镜像 | ~220 MB(基础 JRE 层复用) | 中 | 仅 application 层重传 |
| 多阶段 + Alpine JRE | ~180 MB | 快 | 仅 application 层重传 |
docker-compose 多容器编排
典型微服务依赖:MySQL + Redis + App
yaml
# docker-compose.yml
version: "3.9"
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/blog?useSSL=false&allowPublicKeyRetrieval=true
- SPRING_DATASOURCE_USERNAME=blog
- SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD}
- SPRING_REDIS_HOST=redis
- SPRING_REDIS_PORT=6379
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
mysql:
image: mysql:8.0
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
- MYSQL_DATABASE=blog
- MYSQL_USER=blog
- MYSQL_PASSWORD=${DB_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
- ./docker/mysql/init:/docker-entrypoint-initdb.d # SQL 初始化脚本
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${DB_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 5
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes
volumes:
mysql-data:
redis-data:启动命令
bash
# 启动所有服务(后台运行)
docker-compose up -d --build
# 查看日志
docker-compose logs -f app
# 停止并清理
docker-compose down -v生产环境配置
生产环境 application-prod.yml
yaml
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://${DB_HOST}:3306/blog?useSSL=true&requireSSL=true
username: ${DB_USER}
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
jpa:
hibernate:
ddl-auto: validate # 生产环境用 validate,仅校验不修改表结构
open-in-view: false # 生产环境关闭 OSIV,防止长事务
properties:
hibernate:
generate_statistics: false
data:
redis:
host: ${REDIS_HOST}
port: 6379
password: ${REDIS_PASSWORD}
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 4
server:
port: 8080
compression:
enabled: true
http2:
enabled: true
logging:
level:
root: INFO
com.example: INFO
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: when_authorizedGitHub Actions CI/CD
完整流水线
yaml
# .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# ============ Job 1:构建与测试 ============
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: ./mvnw clean package -B -DskipTests=false
- name: Run tests
run: ./mvnw test
- name: Upload JAR artifact
uses: actions/upload-artifact@v4
with:
name: app-jar
path: target/demo-0.0.1-SNAPSHOT.jar
# ============ Job 2:构建镜像 ============
docker:
runs-on: ubuntu-latest
needs: build
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Download JAR artifact
uses: actions/download-artifact@v4
with:
name: app-jar
path: target/
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ============ Job 3:部署(仅 push 到 main 时)===========
deploy:
runs-on: ubuntu-latest
needs: docker
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Deploy to server via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}
docker-compose -f /opt/blog/docker-compose.yml down
docker-compose -f /opt/blog/docker-compose.yml up -d
docker image prune -f健康检查
java
// 自定义健康指示器
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
private final DataSource dataSource;
@Override
public Health health() {
try (Connection conn = dataSource.getConnection()) {
return Health.up()
.withDetail("database", conn.getCatalog())
.withDetail("connectionPool", getPoolStats())
.build();
} catch (SQLException e) {
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
}
private Map<String, Object> getPoolStats() {
HikariDataSource hikari = (HikariDataSource) dataSource;
return Map.of(
"active", hikari.getHikariPoolMXBean().getActiveConnections(),
"idle", hikari.getHikariPoolMXBean().getIdleConnections(),
"total", hikari.getHikariPoolMXBean().getTotalConnections()
);
}
}小结
- Spring Boot fat jar 通过
JarLauncher+ 自定义类加载器实现,内部分为classes和lib两部分 - 分层镜像利用 Docker 的分层缓存机制,仅在代码变更时重建对应层,大幅提升 CI/CD 效率
docker-compose编排多容器时,使用depends_on+condition: service_healthy确保依赖服务就绪后再启动- 生产环境关键配置:
ddl-auto: validate、open-in-view: false、HikariCP 连接池调优 - GitHub Actions CI/CD 流水线分为构建 → 测试 → 打包镜像 → 部署 四个阶段
#springboot
#docker
#docker-compose
#cicd
#github-actions
评论
A
Written by
AI-Writer
Related Articles
springboot
#1 Spring Boot 3 环境准备与项目初始化
搭建 Spring Boot 3.x 开发环境、使用 Spring Initializr 快速创建项目、理解 Maven/Gradle 构建配置、项目目录结构与第一个可运行应用
Read More springboot
#9 Docker 容器化与生产部署
Spring Boot fat jar 原理、分层 Docker 镜像构建(layered jar)、docker-compose 多容器编排、GitHub Actions CI/CD 流水线与生产环境配置示例
Read More