云计算百科
云计算领域专业知识百科平台

Docker 部署 SpringBoot:从镜像构建到服务器运行

Docker 部署 SpringBoot:从镜像构建到服务器运行

前三篇分别介绍了 Docker 的核心概念、Dockerfile 构建和 Compose 编排,到这里,我们已经能在本地把一个 Spring Boot 应用连同 MySQL、Redis 一起运行起来。

但本地跑通不等于完成部署。服务器上没有你刚刚构建的镜像,生产环境不能继续使用开发时的数据库密码,服务器的内存和网络配置也与本地不同。直接把本地的 Compose 文件复制过去,应用可能启动失败,也可能容器看起来正常,实际却无法连接数据库、读取配置或对外提供服务。

这篇把这些步骤串起来:从本地构建并验证镜像,到把镜像交付到服务器,最后通过 Compose 启动整套服务。

目录

  • 完整部署流程
  • 本地构建 SpringBoot 镜像
  • 本地运行验证
  • 使用 Compose 编排应用和依赖
  • 把镜像交付到服务器
  • 服务器启动应用
  • 开发环境和生产环境的区别
  • 常见部署问题
  • 小结

完整部署流程

从本地代码到服务器运行,核心步骤如下:

在这里插入图片描述

本地构建 SpringBoot 镜像

假设你已经有一个可以运行的 SpringBoot 项目,先写 Dockerfile:

# ———- 构建阶段 ———-
FROM maven:3.9-eclipse-temurin-17 AS builder

WORKDIR /build

# 先复制 pom.xml,利用构建缓存
COPY pom.xml .
RUN mvn dependency:go-offline -B

COPY src ./src
RUN mvn package -DskipTests -B && \\
mv target/*.jar target/app.jar

# ———- 运行阶段 ———-
FROM eclipse-temurin:17-jre

WORKDIR /app

RUN addgroup –system appgroup && adduser –system –ingroup appgroup appuser
USER appuser

COPY –from=builder /build/target/app.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

多阶段构建把编译和运行分开。构建阶段包含完整的 Maven 环境,运行阶段只保留 JRE,避免把源码、Maven 依赖和构建工具带入最终镜像。

注意 mv target/*.jar target/app.jar 这一步。SpringBoot 打包后 target 目录下可能有多个 jar(比如 app.jar 和 app-sources.jar),用 *.jar 直接 COPY 可能匹配到不需要的文件。先重命名再 COPY,更严谨。

构建镜像:

docker build -t my-app:1.0 .

本地运行验证

在推送到服务器之前,先在本地确认镜像能正常运行:

docker run -d –name my-app -p 8080:8080 my-app:1.0

验证方式:

# 检查容器是否在运行
docker ps

# 查看启动日志,确认没有异常
docker logs my-app

# 测试接口
curl http://localhost:8080/actuator/health

如果这一步就出问题,先在本地解决。不要把没验证过的镜像传到服务器——在服务器上排查问题比本地慢得多。

确认没问题后,清理测试容器:

docker stop my-app && docker rm my-app

使用 Compose 编排应用和依赖

单个应用容器只是整个服务的一部分。一个典型的 SpringBoot 项目还需要 MySQL 和 Redis。用 Compose 把它们编排在一起:

services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: myapp
volumes:
mysqldata:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
retries: 10

redis:
image: redis:7
command: redisserver requirepass ${REDIS_PASSWORD}
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 5s
timeout: 3s
retries: 5

my-app:
image: myapp:1.0
ports:
"8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/myapp
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: ${MYSQL_ROOT_PASSWORD}
SPRING_REDIS_HOST: redis
SPRING_REDIS_PASSWORD: ${REDIS_PASSWORD}
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s

volumes:
mysql-data:

几个细节:

端口只映射需要外部访问的服务。 my-app 的 8080 端口映射了,因为你要从浏览器或 curl 访问。MySQL 和 Redis 没有映射端口——它们只需要被 my-app 访问,不需要从宿主机或外部网络连进来。Compose 自动创建的网络中,容器之间用服务名互通,不需要暴露端口。

如果开发时需要用 Navicat 连 MySQL 调试,可以在本地单独加 ports: – "3306:3306",但不要把这个配置带到服务器上。

Redis 也加了健康检查。 depends_on 只保证容器启动顺序,不保证服务就绪。Redis 启动虽然快,但加上健康检查后,condition: service_healthy 能确保 Redis 真正可以接受连接后才启动 my-app,和 MySQL 的处理方式保持一致。

密码用变量引用,不要硬编码。 创建一个 .env 文件存放实际值:

MYSQL_ROOT_PASSWORD=your-strong-password
REDIS_PASSWORD=your-redis-password

这个文件不要提交到版本控制。在 .gitignore 中加上 .env。

本地验证 Compose 编排:

docker compose up -d
docker compose ps # 确认三个服务都正常运行
docker compose logs -f my-app # 跟踪应用日志

三个服务全部正常后,进入下一步。

把镜像交付到服务器

镜像在本地构建好了,下一步是传到服务器

个人项目或小规模部署,用文件传输:

# 本地导出镜像
docker save my-app:1.0 -o my-app.tar

# 传到服务器
scp my-app.tar user@server:/tmp/

# 服务器上导入
ssh user@server "docker load -i /tmp/my-app.tar"

docker save 把镜像打包成 tar 文件,docker load 在服务器上还原。简单直接,不需要额外搭建 Registry。

团队项目推荐用镜像仓库:

# 登录仓库
docker login my-harbor.com

# 打标签
docker tag my-app:1.0 my-harbor.com/backend/my-app:1.0

# 推送
docker push my-harbor.com/backend/my-app:1.0

服务器上拉取:

docker login my-harbor.com
docker pull my-harbor.com/backend/my-app:1.0

镜像仓库的好处是版本管理和多人协作。一个人部署用 save/load 没问题,多个人部署同一个服务时,Registry 会省很多事。

服务器启动应用

服务器上需要安装 Docker 和 Docker Compose。安装过程不展开了,Docker 官方文档针对各个 Linux 发行版都有详细说明。

把以下文件传到服务器:

/opt/my-app/
├── docker-compose.yml
├── .env

启动服务:

cd /opt/my-app
docker compose up -d

确认服务正常:

# 查看容器状态
docker compose ps

# 检查应用日志
docker compose logs -f my-app

# 从外部测试接口
curl http://服务器IP:8080/actuator/health

后续更新应用,如果是用镜像仓库:

docker compose pull # 拉取新版本镜像
docker compose up -d # Compose 自动检测变化并重建容器

如果是用文件传输:

# 本地重新构建并导出
docker build -t my-app:2.0 .
docker save my-app:2.0 -o my-app.tar
scp my-app.tar user@server:/tmp/

# 服务器上导入并更新
ssh user@server "docker load -i /tmp/my-app.tar"
ssh user@server "cd /opt/my-app && docker compose up -d"

开发环境和生产环境的区别

本地开发用的 docker-compose.yml 不能原封不动搬到服务器。几个关键差异:

配置隔离

开发环境和生产环境的数据库密码、Redis 配置通常不同。用 .env 文件分离:

# 开发环境 .env.dev
MYSQL_ROOT_PASSWORD=dev123
REDIS_PASSWORD=devredis

# 生产环境 .env.prod
MYSQL_ROOT_PASSWORD=<强密码>
REDIS_PASSWORD=<强密码>

启动时指定环境文件:

docker compose –env-file .env.prod up -d

敏感信息不要提交到版本控制。

数据持久化

Compose 中定义的 volume(比如 mysql-data)在 docker compose down 后仍然保留。但如果执行 docker compose down -v,volume 会被删除,数据库数据就没了。

生产环境操作时要特别注意这一点。数据库备份应该独立于 Docker 的 volume 管理。

JVM 内存限制

开发环境笔记本有 16GB 内存,随便跑。服务器上如果只有 2GB 内存,三个服务一起跑可能直接 OOM。

容器环境下,JVM 需要感知内存限制。在 Dockerfile 的 ENTRYPOINT 中加入参数:

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

然后在 Compose 中配置:

my-app:
environment:
JAVA_OPTS: "-XX:MaxRAMPercentage=75.0 -XX:+UseContainerSupport"
deploy:
resources:
limits:
memory: 512M

-XX:MaxRAMPercentage=75.0 让 JVM 按容器内存限制的 75% 分配堆内存,留一部分给非堆区域。-XX:+UseContainerSupport 在 JDK 10+ 默认开启,确保 JVM 能正确识别容器的内存限制。

注意 ENTRYPOINT 的写法。如果用 ENTRYPOINT ["java", "-jar", "app.jar"] 这种 exec 形式,JVM 不会读取 JAVA_OPTS 环境变量。必须用 shell 形式 sh -c 让 shell 展开变量。

日志

容器内的应用应该把日志输出到 stdout/stderr,而不是写入容器内的文件。SpringBoot 默认这样做,但如果你自定义了 logback 配置写入文件,需要改回来。

日志输出到 stdout 后,用 docker logs 查看:

docker compose logs -f my-app

Docker 默认会保留日志文件,但不会自动清理。可以在 Compose 中限制日志大小:

my-app:
logging:
options:
max-size: "10m"
max-file: "3"

每个容器最多保留 3 个 10MB 的日志文件,超出后自动轮转。

常见部署问题

部署失败时,按这个顺序排查:

1. 容器有没有启动

docker compose ps

如果容器状态是 Exited 或者根本没出现,看日志:

docker compose logs my-app

常见原因:镜像不存在、Dockerfile 语法错误、ENTRYPOINT 指定的命令不存在。

2. 服务之间能不能通信

容器启动了但应用报连接错误,检查网络配置:

错误写法:jdbc:mysql://localhost:3306/myapp
正确写法:jdbc:mysql://mysql:3306/myapp

容器内的 localhost 指向容器自己,不是宿主机,也不是其他容器。Compose 网络中用服务名访问其他服务。

3. 配置是否正确

数据库连接失败,检查顺序:

  • 依赖服务的健康检查是否通过(docker compose ps 看 STATUS 列)
  • 连接地址写的是服务名还是 localhost
  • 密码是否和 .env 中的一致
  • 环境变量名是否和应用配置中的对应(比如 SPRING_DATASOURCE_PASSWORD)
  • 4. 时区问题

    容器默认使用 UTC 时区。如果数据库存储的时间比实际时间早 8 小时,在 Compose 中设置时区:

    my-app:
    environment:
    TZ: Asia/Shanghai

    小结

    部署链路:写 Dockerfile → 本地构建验证 → Compose 编排 → 镜像交付到服务器 → 启动服务。

    Docker 解决的是环境一致性的问题,本地能跑的镜像,服务器上大概率也能跑。但服务器部署还有配置隔离、数据持久化、资源限制这些事要处理,这些不能靠容器本身解决,需要在 Compose 配置中逐一处理。


    部署 checklist:

  • Dockerfile 写好了,多阶段构建,jar 文件名确定
  • 镜像本地构建成功,docker run 能正常启动
  • Compose 编排本地跑通,所有服务能互相访问
  • 镜像交付到服务器(Registry 或 save/load)
  • 服务器安装了 Docker 和 Docker Compose
  • 生产配置单独准备(.env),密码没有硬编码
  • JVM 内存参数已设置,ENTRYPOINT 能读取 JAVA_OPTS
  • docker compose up -d,服务正常运行
  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » Docker 部署 SpringBoot:从镜像构建到服务器运行
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!