Skip to content
清晨的一缕阳光
返回

Spring Boot Docker 容器化部署

前言

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. 镜像大小对比

类型大小启动时间
传统 JAR500MB5-10s
分层 JAR350MB3-5s
Native Image50MB0.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 容器化要点:

Docker 是 Spring Boot 部署的标准方式。


分享这篇文章到:

上一篇文章
Spring Boot 邮件发送实战
下一篇文章
Java 枚举详解