2026/8/5 10:32:47

Docker部署实战:从零到一构建Spring Boot应用容器化部署全流程

Docker部署实战:从零到一构建Spring Boot应用容器化部署全流程 1. 项目概述为什么选择Docker部署如果你是一名开发者或者运维大概率已经不止一次听过Docker的大名。但真正要把一个项目比如一个Spring Boot应用、一个Node.js服务或者一个Python脚本从你的开发机搬到一台Linux服务器上跑起来这个过程可能依然会让你头疼。传统的部署方式我们得在服务器上配环境装Java、配Python解释器、搞数据库、调各种依赖库版本一个环节出错就可能要折腾半天。更别提“在我机器上好好的怎么到服务器上就不行了”这种经典难题。Docker的出现就是为了解决这个“环境一致性”的痛点。你可以把它理解为一个超级轻量级的“集装箱”。你的应用代码、运行环境、系统工具、系统库所有依赖都被打包进这个标准化的集装箱里。这个集装箱在任何安装了Docker引擎的机器上都能以完全相同的方式运行彻底屏蔽了底层操作系统和硬件的差异。所以部署就变成了一个极其简单的动作把集装箱镜像拉到服务器上然后启动它运行容器。这次我们就来手把手、超详细地走一遍使用Docker将一个项目部署到Linux服务器的完整流程。我会假设你有一台干净的CentOS 7或Ubuntu 20.04服务器我们从零开始涵盖Docker安装、镜像构建、容器运行、网络配置、数据持久化等核心环节并分享我踩过的坑和积累的实操技巧。无论你是刚接触Docker的新手还是想系统梳理流程的老手这篇指南都能给你一个清晰的路线图。2. 核心思路与方案选型理解Docker部署的底层逻辑在动手之前我们需要先理清几个核心概念和为什么选择这套方案。Docker部署的核心是“镜像”和“容器”。镜像是静态的、分层的模板定义了运行环境容器则是镜像运行时的动态实例。我们的部署流程本质上就是创建镜像、分发镜像、运行容器的过程。2.1 为什么是Docker Compose对于简单的单应用部署直接使用docker run命令就够了。但现实中一个项目往往由多个服务组成比如一个Web应用需要搭配数据库、缓存Redis、消息队列等。这时手动管理多个docker run命令及其复杂的参数网络、卷、环境变量就非常繁琐且容易出错。因此我强烈推荐使用Docker Compose。它是一个用于定义和运行多容器Docker应用的工具。你只需要在一个docker-compose.yml文件中用YAML语法描述所有服务容器的配置然后一个docker-compose up命令就能启动整个应用栈。它解决了服务编排的基础问题让部署声明化、可版本化是单机多服务部署的绝佳选择。对于更复杂的集群部署才需要考虑Kubernetes。2.2 镜像构建策略Dockerfile的学问镜像是部署的基石而Dockerfile是构建镜像的“菜谱”。如何编写一个高效、安全、可维护的Dockerfile是部署成功的关键。这里有几个核心原则使用官方基础镜像尽量使用openjdk:11-jre-slim、node:16-alpine这类官方维护的、体积小的基础镜像。Alpine Linux镜像体积非常小但某些情况下兼容性可能有问题生产环境需充分测试。利用构建缓存Docker构建是分层的每一行指令都会产生一个层。为了加速构建应该把变化频率低的指令如安装系统依赖放在前面变化频率高的指令如复制应用代码放在后面。多阶段构建对于需要编译的应用如Java、Go强烈推荐使用多阶段构建。在第一阶段构建阶段使用包含完整编译工具的大镜像进行编译在第二阶段运行阶段仅复制第一阶段的编译产物到一个干净的小镜像中。这能极大减小最终镜像的体积提升安全性和拉取速度。非root用户运行默认情况下容器内进程以root用户运行存在安全风险。应该在Dockerfile中创建并使用一个非root用户来运行应用。我们的方案将围绕一个典型的Web应用以Spring Boot为例展开使用多阶段构建的Dockerfile并通过Docker Compose编排应用与MySQL数据库。3. 环境准备Linux服务器与Docker安装假设我们拿到了一台全新的CentOS 7服务器。下面是从系统配置到Docker安装的详细步骤。3.1 系统基础配置首先通过SSH登录服务器。进行一些基础优化# 更新系统包首次更新可能较慢 sudo yum update -y # 安装常用的工具 sudo yum install -y vim wget curl net-tools lsof # 关闭防火墙和SELinux仅用于实验环境生产环境需配置安全组和防火墙规则 sudo systemctl stop firewalld sudo systemctl disable firewalld sudo setenforce 0 sudo sed -i s/SELINUXenforcing/SELINUXdisabled/g /etc/selinux/config注意生产环境中绝对不建议直接关闭防火墙和SELinux。正确的做法是配置防火墙如firewalld或iptables开放Docker及你的应用所需端口并针对SELinux进行策略调整或设置为宽容模式。3.2 Docker引擎安装与配置我们将使用Docker官方仓库进行安装确保获得最新稳定版本。# 1. 卸载旧版本如有 sudo yum remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine # 2. 安装yum工具包并添加Docker官方仓库 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 3. 安装Docker引擎社区版、命令行工具和容器运行时接口 sudo yum install -y docker-ce docker-ce-cli containerd.io # 4. 启动Docker并设置开机自启 sudo systemctl start docker sudo systemctl enable docker # 5. 验证安装 sudo docker --version sudo docker run hello-world如果能看到Docker版本信息以及Hello from Docker!的提示说明安装成功。国内用户必备操作配置镜像加速器从Docker Hub拉取镜像速度可能很慢需要配置国内镜像加速器。# 创建或修改Docker守护进程配置文件 sudo tee /etc/docker/daemon.json -EOF { registry-mirrors: [ https://docker.mirrors.ustc.edu.cn, https://hub-mirror.c.163.com, https://mirror.baidubce.com ], log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 } } EOF # 重新加载配置并重启Docker sudo systemctl daemon-reload sudo systemctl restart docker # 检查加速器是否生效 sudo docker info | grep -A 1 Registry Mirrors3.3 Docker Compose安装Docker Compose是一个独立的二进制文件安装非常简单。# 从Github下载稳定版本的Docker Compose # 请访问 https://github.com/docker/compose/releases 查看最新版本号替换下面的v2.20.0 sudo curl -L https://github.com/docker/compose/releases/download/v2.20.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose # 赋予执行权限 sudo chmod x /usr/local/bin/docker-compose # 创建软链接可选方便直接使用docker-compose命令 sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose # 验证安装 docker-compose --version4. 项目准备与Dockerfile深度解析现在我们在本地开发环境准备一个简单的Spring Boot项目。项目结构如下my-springboot-app/ ├── src/ ├── pom.xml ├── Dockerfile # 镜像构建文件 └── docker-compose.yml # 服务编排文件4.1 编写高效的Dockerfile一个优秀的Dockerfile是部署成功的半壁江山。下面是一个针对Spring Boot Jar包的多阶段构建Dockerfile示例# 第一阶段构建阶段 (Builder Stage) # 使用包含Maven和JDK的较大镜像来编译项目 FROM maven:3.8.4-openjdk-11-slim AS builder # 设置工作目录 WORKDIR /app # 首先复制pom.xml文件利用Docker缓存层 # 如果pom.xml没有变化则不会重复下载依赖极大加速构建 COPY pom.xml . RUN mvn dependency:go-offline -B # 复制源代码并打包 COPY src ./src RUN mvn clean package -DskipTests # 第二阶段运行阶段 (Runtime Stage) # 使用仅包含JRE的轻量级镜像作为运行环境 FROM openjdk:11-jre-slim # 在运行镜像中创建一个非root用户和用户组增强安全性 RUN groupadd -r spring useradd -r -g spring spring USER spring:spring # 设置工作目录 WORKDIR /app # 从构建阶段复制打包好的jar文件 # 注意这里的 --frombuilder 指定了来源阶段 COPY --frombuilder /app/target/*.jar app.jar # 暴露应用端口Spring Boot默认8080 EXPOSE 8080 # 设置JVM运行参数例如堆内存、垃圾回收器等根据实际应用调整 ENV JAVA_OPTS-Xms512m -Xmx512m -XX:UseG1GC # 使用 exec 格式的 ENTRYPOINT 启动应用使应用能接收Unix信号如SIGTERM ENTRYPOINT exec java $JAVA_OPTS -jar app.jar关键点解析与避坑指南多阶段构建FROM ... AS builder定义了构建阶段。最终镜像只包含运行阶段第二个FROM的内容构建工具Maven和中间产物都被丢弃这使得最终镜像体积可能从600MB减少到200MB左右。依赖缓存先单独复制pom.xml并执行mvn dependency:go-offline这能提前下载所有依赖到本地仓库。只要pom.xml不变这一层就会被缓存后续构建无需重复下载网络依赖速度极快。非root用户USER spring:spring指令让容器以非特权用户运行遵循了最小权限原则。特别注意如果应用需要写入容器内某个目录如日志必须确保该目录对该用户有写权限或者在运行阶段通过chown改变目录属主。ENTRYPOINT exec格式使用exec格式ENTRYPOINT [executable, param1, param2]或shell格式中的exec关键字可以让Java进程成为容器内的PID 1进程。这样发送给容器的SIGTERM等终止信号才能被Java进程正确接收从而实现优雅关机避免强制杀死导致事务中断。4.2 编写docker-compose.yml接下来我们编写编排文件定义应用服务及其依赖的数据库。version: 3.8 services: # 主应用服务 app: build: . # 使用当前目录下的Dockerfile构建镜像 container_name: my-springboot-app restart: unless-stopped # 容器退出时总是重启除非手动停止 ports: - 8080:8080 # 主机端口:容器端口 environment: - SPRING_PROFILES_ACTIVEprod - SPRING_DATASOURCE_URLjdbc:mysql://mysql:3306/myappdb?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai - SPRING_DATASOURCE_USERNAMEroot - SPRING_DATASOURCE_PASSWORDYourStrongPassword123! depends_on: - mysql # 声明依赖确保mysql先启动 networks: - app-network # 将主机目录挂载到容器内实现日志、配置文件持久化 volumes: - ./logs:/app/logs - ./config/application-prod.yml:/app/config/application-prod.yml:ro # 只读挂载配置文件 # MySQL数据库服务 mysql: image: mysql:8.0 container_name: mysql-for-app restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: YourStrongPassword123! MYSQL_DATABASE: myappdb MYSQL_USER: myappuser MYSQL_PASSWORD: MyAppUserPass123! networks: - app-network volumes: # 数据持久化将主机上的mysql-data目录挂载到容器的数据目录 - mysql-data:/var/lib/mysql # 初始化脚本可以将建表SQL放在./init-scripts目录下容器启动时会自动执行 - ./init-scripts:/docker-entrypoint-initdb.d:ro command: - --default-authentication-pluginmysql_native_password - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci # 定义命名卷和数据卷 volumes: mysql-data: # 声明一个命名卷Docker会自动管理其在主机上的存储位置 # 定义自定义网络使服务间可以通过服务名通信 networks: app-network: driver: bridge编排文件核心配置解读服务间通信通过自定义网络app-network服务间可以直接使用服务名如mysql作为主机名进行通信。这是Docker Compose提供的DNS功能比使用IP地址更稳定可靠。数据持久化命名卷Named Volumemysql-data是一个命名卷Docker会将其存储在主机的一个特定路径下通常/var/lib/docker/volumes/。即使容器被删除卷中的数据依然保留是最推荐的数据持久化方式。绑定挂载Bind Mount./logs:/app/logs是将主机当前目录下的logs文件夹挂载到容器内。适合存放日志、配置文件等需要直接从主机访问的文件。环境变量敏感信息如数据库密码通过environment传入。切勿将密码硬编码在Dockerfile或代码中。更安全的方式是使用Docker SecretsSwarm模式或外部配置中心。depends_on这仅控制容器的启动顺序先启动mysql再启动app并不保证mysql数据库服务已就绪。对于数据库应用需要有重连机制。更健壮的做法是使用wait-for-it.sh或dockerize等工具在应用启动前检测依赖服务端口。5. 完整部署流程实操环境与文件准备就绪现在我们登录Linux服务器开始部署。5.1 传输项目文件到服务器在本地项目根目录包含Dockerfile和docker-compose.yml的目录下操作# 使用scp命令将整个项目目录压缩后上传到服务器的/opt目录 tar -czf my-app.tar.gz . scp my-app.tar.gz useryour_server_ip:/opt/ # 登录服务器 ssh useryour_server_ip # 进入/opt目录并解压 cd /opt tar -xzf my-app.tar.gz -C my-springboot-app cd my-springboot-app5.2 构建与启动在服务器上的项目目录中执行以下命令# 使用Docker Compose构建镜像并启动所有服务 # -d 参数表示在后台运行守护进程模式 docker-compose up -d --build这个命令会执行以下操作根据docker-compose.yml中app服务的build: .配置读取当前目录的Dockerfile构建镜像。从Docker Hub拉取mysql:8.0镜像如果本地没有。创建app-network网络。创建mysql-data命名卷。按依赖顺序启动mysql和app容器。5.3 检查部署状态启动后我们需要确认服务是否正常运行。# 查看所有由当前docker-compose管理的容器状态 docker-compose ps # 查看所有容器的实时日志组合输出 docker-compose logs -f # 单独查看某个服务的日志例如查看应用启动日志 docker-compose logs -f app # 查看容器内进程 docker-compose top # 进入应用容器内部进行检查类似于ssh进容器 docker-compose exec app sh # 进入容器后可以检查环境变量、查看进程等 # echo $SPRING_DATASOURCE_URL # ps aux # exit 退出容器如果看到应用日志显示“Started Application in X seconds”且无报错同时MySQL容器健康运行说明部署基本成功。5.4 验证服务可用性在服务器上或从本地机器通过curl或浏览器访问应用。# 在服务器上测试本地访问 curl http://localhost:8080/your-api-endpoint # 如果服务器防火墙/安全组开放了8080端口可以从外部访问 # 浏览器打开http://your_server_ip:80806. 生产环境进阶配置与优化基础的跑起来只是第一步要用于生产环境还需要考虑更多。6.1 资源限制与监控默认情况下容器可以使用宿主机的所有资源。这可能导致某个容器耗尽资源影响其他服务。我们需要设置资源限制。修改docker-compose.yml中的app服务部分app: build: . ... deploy: # 注意deploy部分仅在docker-compose up时生效docker run需用其他参数 resources: limits: cpus: 1.0 # 限制最多使用1个CPU核心 memory: 1024M # 限制最多使用1GB内存 reservations: cpus: 0.5 memory: 512M对于单机Docker Compose也可以使用docker run风格的资源限制在compose文件的service下直接配置app: build: . ... mem_limit: 1024m mem_reservation: 512m cpus: 1.0监控可以使用docker stats命令实时查看容器资源使用情况或集成Prometheus、cAdvisor等监控工具。6.2 日志管理策略默认的json-file日志驱动会将日志存储在主机/var/lib/docker/containers/下容易撑满磁盘。我们已在daemon.json中配置了日志轮转max-size和max-file。更佳实践是使用集中式日志系统。使用日志驱动发送到外部可以配置Docker使用syslog、journald或gelf等驱动将日志直接发送到日志服务器如ELK Stack。应用日志挂载到主机如我们之前做的./logs:/app/logs然后在主机上使用logrotate等工具管理日志文件。6.3 健康检查配置为服务添加健康检查能让Docker Compose更好地了解服务状态并可用于服务发现等高级场景。在docker-compose.yml中为app服务添加app: ... healthcheck: test: [CMD, curl, -f, http://localhost:8080/actuator/health] # Spring Boot Actuator健康端点 interval: 30s timeout: 10s retries: 3 start_period: 40s这样执行docker-compose ps时可以看到服务状态是healthy还是unhealthy。6.4 使用.env文件管理环境变量将敏感信息写在docker-compose.yml中并不安全。我们可以使用环境变量文件。创建.env文件务必加入.gitignoreMYSQL_ROOT_PASSWORDYourStrongPassword123! MYSQL_PASSWORDMyAppUserPass123!修改docker-compose.yml引用环境变量mysql: ... environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_PASSWORD: ${MYSQL_PASSWORD}启动时Docker Compose会自动读取同目录下的.env文件。7. 日常运维与问题排查实录部署完成后日常的启停、更新、备份和问题排查是运维常态。7.1 常用运维命令# 停止所有服务但保留容器和网络 docker-compose stop # 停止并移除所有容器、网络默认不移除卷和镜像 docker-compose down # 停止并移除所有容器、网络、卷谨慎使用会删除数据库数据 docker-compose down -v # 重启某个服务例如只重启应用 docker-compose restart app # 查看服务日志 docker-compose logs app # 在运行的服务中执行命令例如查看应用环境变量 docker-compose exec app env # 拉取服务的最新镜像如果使用远程镜像而非本地构建 docker-compose pull # 重新构建并启动服务适用于代码更新后 docker-compose up -d --build7.2 常见问题与排查技巧问题1容器启动后立即退出Exited (1)排查首先查看日志docker-compose logs app。最常见的原因是应用启动失败检查日志中的Java报错可能是数据库连接失败、配置文件错误、端口占用等。Dockerfile中ENTRYPOINT/CMD错误命令执行失败。确保启动命令正确例如java -jar的参数路径无误。容器内用户权限不足如果使用了非root用户检查其是否对要写入的目录如日志目录有权限。可以在Dockerfile的USER指令前用root权限创建目录并修改属主。问题2应用无法连接到数据库Connection refused排查确认MySQL容器是否正常运行docker-compose ps。进入应用容器测试网络连通性docker-compose exec app ping mysql。进入应用容器测试端口连通性docker-compose exec app nc -zv mysql 3306。检查应用容器的环境变量SPRING_DATASOURCE_URL是否正确docker-compose exec app env | grep SPRING。确保主机名是mysql服务名端口是3306。检查MySQL容器日志看是否有初始化错误docker-compose logs mysql。问题3构建镜像时下载依赖超时或失败解决为Docker构建配置代理在构建命令前设置环境变量或在Dockerfile中使用ARG定义构建参数来设置代理。使用国内Maven仓库在项目pom.xml中或本地Mavensettings.xml中配置阿里云等镜像仓库。对于Docker构建可以将配置好的settings.xml复制到镜像中COPY settings.xml /root/.m2/。问题4磁盘空间不足清理Docker会占用大量磁盘空间定期清理无用资源。# 删除所有已停止的容器 docker container prune -f # 删除所有未被使用的镜像悬空镜像 docker image prune -f # 删除所有未被使用的卷谨慎确认卷内无重要数据 docker volume prune -f # 删除所有未被使用的网络 docker network prune -f # 一键清理所有未被使用的资源容器、镜像、网络、卷构建缓存 docker system prune -af --volumes问题5如何更新已部署的应用流程将最新代码提交并推送到Git仓库。在服务器上拉取最新代码git pull。重新构建镜像并启动docker-compose up -d --build。Docker Compose会检测到镜像变化先构建新镜像然后停止旧容器用新镜像启动新容器实现无缝更新会有极短服务中断。如果更新失败可以快速回滚到上一个版本的镜像。7.3 数据备份与恢复对于数据库等有状态服务备份至关重要。备份MySQL数据卷# 创建一个临时容器挂载MySQL的数据卷和主机备份目录执行备份命令 docker run --rm -v mysql-data:/volume -v /opt/backups:/backup alpine \ tar -czf /backup/mysql-backup-$(date %Y%m%d-%H%M%S).tar.gz -C /volume ./从备份恢复# 首先停止并删除现有的MySQL容器和数据卷注意这会丢失现有数据 docker-compose down -v # 重新启动服务会创建新的空数据卷 docker-compose up -d mysql # 等待MySQL容器完全启动后执行恢复 docker run --rm -v mysql-data:/volume -v /opt/backups:/backup alpine \ sh -c rm -rf /volume/* tar -xzf /backup/mysql-backup-20231027-120000.tar.gz -C /volume # 重启应用容器 docker-compose up -d app整个流程走下来从一台裸机Linux服务器到成功运行一个由Docker容器化的完整应用栈涉及了环境准备、镜像构建、服务编排、生产优化和运维排查的全链路。Docker部署的魅力在于其声明式和一致性一旦Dockerfile和docker-compose.yml定型在任何环境下的部署都变得可预测且高效。记住多实践、多踩坑遇到问题善用docker-compose logs和docker exec进入容器排查你的容器化部署技能会越来越熟练。