
先交代一句这篇文章不是那种“安装完就算完事”的教程。我写它是因为发现太多人卡在同一个地方——照着网上的命令敲完了容器也起来了但稍微换个环境、换个项目就一脸懵。所以这篇主要解决三件事Docker 和 Docker Compose 到底在解决什么问题完整的部署流程每一步在干什么以及出问题时怎么快速定位。我自己这些年从单机部署到多服务编排再到现在几乎每个项目都离不开 Docker踩过的坑不算少。这篇文章里的内容全是我亲手验证过的操作和配置不是从文档里抄来的概念。你可以把这篇文章当成一份“抄作业指南”照着做能跑通做完之后你还能明白为什么要这么做。1. 部署这件事为什么非得用 Docker1.1 从“在我电脑上是好的”说起先说一个所有开发者都经历过的场景。代码本地跑得好好的一到服务器就崩这个服务依赖 Java 8那个服务需要 Java 17MySQL 版本换了一下整个应用连不上数据库。这些问题的根源只有一个运行环境不一致。Docker 的思路特别直接把应用连同它需要的运行环境、依赖库、配置一起打包成一个“集装箱”。这个集装箱在任何地方打开里面的东西都一模一样。开发机上是这样测试机上是这样生产服务器上也是这样。环境差异导致的问题从根上就被消灭了。这不是什么高深的技术就是一个朴素的思想——标准化。就像麦当劳的汉堡配方固定、流程固定在北京吃和在上海吃味道不会有太大差别。Docker 干的就是这件事把“怎么做汉堡”这件事标准化。1.2 Docker Compose 解决的是什么问题单个容器好理解但现实中的项目很少只有一个模块。一个典型的 Web 应用至少需要前端 Nginx、后端应用、数据库 MySQL、缓存 Redis。如果你手动去跑四个 docker run 命令会遇到一堆麻烦容器之间的网络怎么打通、启动顺序怎么控制、环境变量怎么管理、数据怎么持久化。Docker Compose 就是一个“编排工具”。你用一份 YAML 文件把所有的服务定义好包括镜像、端口、环境变量、依赖关系、数据卷然后一个命令全部启动。它就是那个帮你把集装箱按顺序、按规则摆放好的吊车。更关键的是这份 YAML 文件是文本可以提交到 Git 里。新同事入职拉下代码docker compose up -d一套环境就起来了。这就是基础设施即代码的雏形整个部署过程变得透明、可审计、可复制。1.3 这篇教程能带你走到哪一步我不打算只讲理论也不打算只丢几个命令然后说“亲测有效”。这篇教程的目标是你从零开始在 Windows 或 Linux 上装好 Docker理解镜像和容器的关系掌握常用命令最后用 Docker Compose 把一个带 MySQL 和 Redis 的后端服务完整地跑起来。整个过程我会边操作边解释每个步骤的作用和背后的原理都会说清楚。学完这篇你自己去部署 Dify、JumpServer、GitLab 这类开源项目或者把自己的项目容器化基本上不会再有障碍。2. 环境准备先把 Docker 装好2.1 Windows 和 macOS 怎么装 Docker DesktopDocker Desktop 是官方提供的图形化桌面工具内置了 Docker Engine、Docker Compose、Kubernetes装一个全都有。Windows 用户要注意Docker Desktop 依赖 WSL 2 或 Hyper-V 虚拟化这是大多数人安装时遇到问题的第一道坎。安装流程分成三步。第一步确认虚拟化已开启。打开任务管理器切到“性能”标签页看“虚拟化”那一项是不是“已启用”。如果显示未启用需要进 BIOS/UEFI 开启 Intel VT-x 或 AMD-V。不同主板的设置入口不太一样但基本都在 Advanced 或 Configuration 菜单下找找 Virtualization Technology、SVM Mode 这类选项就行。提示开启虚拟化后一定要断电重启只按重启键有时候不生效。这个细节很多人忽略结果装完还是报错以为电脑不支持。第二步安装 WSL 2。打开 PowerShell管理员模式执行wsl --install装完重启电脑然后执行wsl --set-default-version 2把默认版本设为 WSL 2。这一步是有原因的WSL 2 比 WSL 1 有更完整的 Linux 内核Docker 跑在上面的文件读写性能要好得多。第三步去 Docker 官网下载 Docker Desktop 安装包双击按提示装就行。装完启动等右下角鲸鱼图标稳定不再转圈就说明引擎已经起来了。macOS 用户更简单下载 Docker Desktop for Mac装完打开输入一次密码授权安装 Docker 的辅助工具就完事了。如果有 Apple Silicon 芯片记得选对应 ARM 版本的安装包。2.2 Linux 服务器怎么装 Docker Engine服务器上一般不用 Docker Desktop直接装 Docker Engine 加 Compose 插件就行。以 Ubuntu 为例官方推荐用 apt 仓库安装而不是用 curl 脚本一键装原因是方便后续升级。先卸载可能存在的旧版本sudo apt-get remove docker docker-engine docker.io containerd runc然后安装依赖并添加官方 GPG 密钥和仓库sudo apt-get update sudo apt-get install ca-certificates curl gnupg lsb-release sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null接着安装 Docker 引擎和 Compose 插件sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin装完先别急着用执行一下验证sudo docker run hello-world能打印出 Hello from Docker! 就说明整个链路是通的。注意默认情况下 docker 命令需要 root 权限。把当前用户加入 docker 组可以免 sudo 使用但这也等于把这个用户提升到接近 root 的权限。单机开发环境这么做没问题生产服务器要慎重评估。sudo usermod -aG docker $USER执行完要重新登录一次才能生效。2.3 虚拟化相关的常见报错和解决办法先说 Windows 上最典型的报错Docker Desktop failed to start because virtualisation support wasnt detected或者信息里带 virtualization support not detected。这个报错 90% 是虚拟化没开或者没开对。排查思路按这个顺序来第一确认 BIOS 里的虚拟化开了。不同品牌的主板叫法不一样Intel 平台叫 Intel Virtualization TechnologyAMD 平台叫 SVM Mode还有的板子叫 Virtualization开了就行。第二确认 WSL 2 是真装好了。在 PowerShell 里执行wsl --status如果显示“默认版本2”基本就正常。如果显示 WSL 1执行wsl --set-default-version 2再wsl --shutdown重启 WSL。第三检查 Windows 功能里 Hyper-V 和“适用于 Linux 的 Windows 子系统”是否启用。控制面板 → 程序 → 启用或关闭 Windows 功能把这两项勾上重启电脑。再有一种情况是 Windows 系统版本太旧。WSL 2 要求 Windows 10 版本 2004 以上或者 Windows 11。老版本系统的处理办法不一样但建议直接升级系统别折腾兼容方案。Linux 服务器上如果遇到虚拟化相关报错多数是在虚拟机里再套虚拟机。这种情况要用 docker 的“嵌套虚拟化”模式比较麻烦更推荐直接换一台物理机或者用云服务器。3. Docker 核心概念镜像、容器、数据卷、网络3.1 镜像和容器的关系用面向对象来理解“镜像”这个词很形象但也容易让人误解。你可以把镜像理解成一个只读的模板里面装好了操作系统的基础层、运行环境、应用代码、配置文件。它自己不运行只是静静地躺在那里。“容器”则是镜像的一个运行实例。镜像启动了就变成容器。容器有完整的运行环境有独立的文件系统但它和宿主机共享内核。你可以在容器里装软件、写文件、跑进程但容器一删除这些改动就都没了。用面向对象的思路来说镜像是类容器是对象。类定义了属性和方法对象才真正占用内存、执行逻辑。你可以从同一个镜像启动无数个容器它们之间互不干扰。举一个实际场景同一个 Java 后端镜像A 容器用 8080 端口连接 MySQLB 容器用 8081 端口两个容器在同一台服务器上同时跑互相看不到对方内部的文件变化。这就实现了隔离。3.2 镜像从哪来Docker Hub 和 DockerfileDocker 镜像的获取方式有两种从注册中心拉取或者自己构建。Docker Hub 是默认的公共镜像仓库里面有海量的官方镜像比如 nginx、mysql、redis、ubuntu、openjdk。拉取命令非常简单docker pull nginx:latest docker pull mysql:8.0 docker pull redis:7.2注意镜像名后面的标签tag它代表版本。生产环境一定要指定具体版本不要用 latest。原因很直白latest 不是固定的你两个月后重新拉取得到的东西可能和当初完全不一样这会给排查问题带来极大的不确定性。自己构建镜像要写 Dockerfile。这就是一封“写给 Docker 的安装说明书”告诉它要基于哪个基础镜像、拷贝哪些文件、执行什么命令、暴露哪个端口。比如用 openjdk 部署一个 Spring Boot 项目Dockerfile 可以长这样FROM openjdk:17-jdk-slim WORKDIR /app COPY target/app.jar . EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]构建命令是docker build -t myapp:1.0.0 .这里-t指定镜像名和标签最后的.指定构建上下文当前目录。构建好的镜像可以推到私有仓库或 Docker Hub供其他机器拉取。技巧基础镜像尽量选带-slim或者-alpine后缀的精简版本。同一个功能的镜像slim 版可能只有完整版的三分之一大小拉取更快占用磁盘更少被攻击面也更小。3.3 数据卷容器删了数据不能跟着没前面提到容器删除后改动全没了。这对无状态的应用比如 Nginx 提供静态页面没影响但对 MySQL、Redis、文件上传这类有状态的服务简直就是灾难。解决办法是数据卷Volume。它的本质是把宿主机上的一个目录“挂载”到容器内部。容器往挂载目录里写东西实际上写到了宿主机容器删了宿主机的目录还在新容器挂载同一个目录数据就还在。Docker 的-v参数有两种用法命名卷和绑定挂载。命名卷由 Docker 管理数据存在 Docker 的专属目录里。命令像这样docker run -v mydata:/var/lib/mysql mysql:8.0绑定挂载则直接把宿主机的一个具体路径挂进去docker run -v /home/user/data:/var/lib/mysql mysql:8.0生产环境推荐使用绑定挂载因为数据就在宿主机上一个明确的路径里备份、迁移都直观方便。数据卷这一点在 Compose 文件里也是重头戏后面实战部分会具体演示。3.4 网络模式容器之间怎么互相访问Docker 容器的网络默认是桥接模式。每个容器内部有一个独立的 IP宿主机可以通过映射出来的端口访问容器容器之间可以通过容器名互相访问。端口映射是-p参数格式是“宿主机端口:容器端口”。比如-p 8080:80意思是访问宿主机的 8080 端口流量会转到容器内的 80 端口。这个映射关系在 Compose 文件里同样会用到。容器之间访问最好的方式是通过 Compose 创建的自定义网络。在同一个 Compose 网络里的服务可以直接用服务名当作主机名访问。比如后端应用要连 MySQL数据库地址写成mysql:3306而不是localhost:3306因为对容器来说它自己的 localhost 就是自己不是别的容器。我遇到过不少新手在这一步卡住应用能启动但报“连接数据库超时”怎么排查都发现不了问题。十有八九就是数据库地址写成了 localhost而实际上应该写成服务名。4. Docker 常用命令速查这些命令够用 90% 的场景4.1 镜像管理三件套镜像管理的核心操作就三个看、拉、删。# 查看本机所有镜像 docker images # 拉取指定镜像 docker pull nginx:1.24 # 删除镜像 docker rmi nginx:1.24说一个常见现象docker images里出现一堆none标签的镜像。这是悬空镜像通常是因为重新构建同名镜像后旧版本没有清理。可以用下面的命令一键清掉docker image prune -f这个命令会删掉所有none的悬空镜像不影响正在使用的。我习惯在每次重新构建镜像后执行一次磁盘空间能省不少。4.2 容器生命周期操作容器的操作比镜像多一些但规律性很强。以下是我每天都会用到的一套命令# 运行一个容器-d 后台运行--name 起名字-p 映射端口 docker run -d --name mynginx -p 8080:80 nginx:1.24 # 查看正在运行的容器 docker ps # 查看所有容器包括停止的 docker ps -a # 停止容器 docker stop mynginx # 启动已停止的容器 docker start mynginx # 重启容器 docker restart mynginx # 进入容器内部用 bash 交互 docker exec -it mynginx bash # 查看容器日志-f 是持续跟踪 docker logs -f mynginx # 删除容器-f 强制删除正在运行的容器 docker rm -f mynginx这里面docker exec -it是排查问题的利器。容器起不来了或者起来了但报错进去看一眼日志、检查一下配置比盲猜高效得多。有些精简镜像里没有 bash只有 sh可以把 bash 换成 sh。提示容器被删了但镜像还在数据卷还在但是容器内的文件没了。所以一定要把需要保留的数据挂载到宿主机养成从第一天就做好数据持久化的习惯。4.3 日志、资源、系统信息部署完项目最常用的可能就是看日志和看资源占用。# 动态查看实时日志 docker logs -f 容器名 # 查看最近 100 行日志 docker logs --tail 100 容器名 # 查看容器的资源占用CPU、内存、网络 docker stats # 查看容器详细信息包括 IP、网络、挂载 docker inspect 容器名 # 查看 Docker 系统占用的磁盘空间 docker system dfdocker system df这个命令容易被忽略但它能直接告诉你镜像、容器、数据卷、构建缓存分别占了多少空间。磁盘满了不知道删什么的时候先跑这个。4.4 一键清理让服务器“重获新生”有一个命令我强烈建议你在掌握之前不要随便用但掌握之后会爱不释手docker system prune -a它会删掉所有未被使用的镜像、停止的容器、未用的网络以及构建缓存。执行完Docker 的磁盘占用通常能掉一大半。注意-a参数的含义是“删除所有未被容器引用的镜像”意味着你本地如果有一些旧版本镜像也会被一起清掉。在测试环境没问题生产环境删之前确认一下没有特殊需求。5. 实战用 Docker Compose 部署一套完整的 MySQL Redis Spring Boot 应用5.1 写一个基础的 docker-compose.ymlCompose 文件的默认名字是docker-compose.yml新版也支持compose.yaml。我个人习惯用docker-compose.yml兼容性最好。一个最小可用的组成结构如下version: 3.8 services: mysql: image: mysql:8.0 container_name: myapp-mysql restart: always environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: myapp ports: - 3306:3306 volumes: - ./data/mysql:/var/lib/mysql redis: image: redis:7.2 container_name: myapp-redis restart: always ports: - 6379:6379 volumes: - ./data/redis:/data app: image: myapp:1.0.0 container_name: myapp-backend restart: always ports: - 8080:8080 depends_on: - mysql - redis environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/myapp?useUnicodetruecharacterEncodingutf8 SPRING_DATASOURCE_USERNAME: root SPRING_DATASOURCE_PASSWORD: root123456 SPRING_REDIS_HOST: redis SPRING_REDIS_PORT: 6379几个核心点解释一下。restart: always的意思是容器意外退出后会自动拉起。服务器重启后Docker 服务会自动把标记了always的容器启动。这是生产环境必备配置否则一次宿主机重启你的所有依赖服务全得手动起。depends_on只是控制启动顺序不保证依赖服务“已可用”。MySQL 容器起来了不代表 MySQL 已经可以接受连接了。所以如果你的应用启动得太快导致连不上数据库通常的解法是在应用代码里加一个连接重试机制。environment这一节是把环境变量传给容器。Spring Boot 应用会读取SPRING_DATASOURCE_URL这类标准环境变量自动覆盖 application.yml 里的配置。所以说容器化部署根本不需要改动代码里的配置只要环境变量传对就行。启动只需要一条命令docker compose up -d-d是后台运行。执行完docker compose ps可以看所有服务的状态。5.2 MySQL 生产环境部署的关键参数如果只是本地测试上面的 MySQL 配置够用了。但要放到生产环境至少还要注意几件事。第一MySQL 8.0 默认的认证插件是caching_sha2_password一些老客户端连接会报错。在 Compose 里可以加一条命令参数来兼容mysql: image: mysql:8.0 command: - --default-authentication-pluginmysql_native_password第二时区问题。MySQL 默认时区是 UTC如果你的应用和数据库交互涉及时间字段务必在连接串里加上serverTimezoneAsia/Shanghai否则查询出来的时间会差 8 个小时。第三字符集问题。 在 MySQL 8.0 中默认已经是 utf8mb4不需要额外配置。但你的应用在建表时如果没有显式指定字符集最好在连接串里加上characterEncodingutf8。第四生产环境不要用 root 用户跑业务。应该单独创建一个应用用户只授权业务库的权限。虽然这在 Compose 里配置起来稍麻烦但安全边际会高很多。5.3 Redis 的持久化配置Redis 默认是没有持久化的数据全在内存里重启就没了。生产环境几乎都要开启 AOF 或 RDB 持久化。在 Compose 里挂载配置文件的写法是redis: image: redis:7.2 container_name: myapp-redis restart: always command: redis-server /usr/local/etc/redis/redis.conf ports: - 6379:6379 volumes: - ./config/redis/redis.conf:/usr/local/etc/redis/redis.conf - ./data/redis:/dataredis.conf 里把appendonly yes打开数据写入会追加到文件里就算容器重启数据也能恢复。这也是为什么要在宿主机上挂一个./data/redis目录AOF 文件就写在那里。经验Redis 的数据目录权限容易出问题。如果容器日志报“Cant open the append-only file: Permission denied”多半是宿主机的挂载目录权限不对。在宿主机执行chown -R 999:999 ./data/redis通常能解决因为 Redis 容器内默认以 uid 999 运行。5.4 一个命令启停整个项目Compose 最有价值的地方在于整组服务可以用一条命令管理。# 后台启动所有服务 docker compose up -d # 查看所有服务状态 docker compose ps # 查看所有服务日志 docker compose logs -f # 停止所有服务容器不会被删除 docker compose stop # 停止并删除容器、网络 docker compose down # 停止并删除容器、网络同时删除数据卷 docker compose down -vdocker compose down -v里的-v一定要慎用。它会把数据卷也删了。如果有一次你执行过这个命令并且数据没有备份MySQL 里的数据就彻底没了。每次执行这个命令之前都必须确认数据已经同步或备份完。5.5 部署后的验证步骤部署完不能直接收工至少要把以下四步走一遍才算真正完成第一步看所有容器的状态。docker compose ps正常情况下所有服务应该都是Up或running。如果有Restarting说明容器在崩溃重启循环马上往下看日志。第二步看应用日志里有没有异常。docker compose logs app重点是看应用是否成功连上 MySQL 和 Redis。如果有连接失败先确认应用里的数据库地址是不是写成了mysql、Redis 地址是不是redis。第三步从宿主机访问一次接口。curl http://localhost:8080/api/health这一步验证端口映射是否生效网络链路是否打通。第四步重启一次整个容器组确认数据还在。docker compose restart重启后再查一次数据库数据是否还在。如果数据丢了说明数据卷挂载没生效这是继续使用之前必须修复的大坑。6. 实战进阶给后端服务构建镜像6.1 用 IDEA 构建 Docker 镜像我在实际项目里经常遇到开发人员问Java 项目的 Docker 镜像到底哪里来其实最简单的流程就是本地打 jar 包然后通过 Dockerfile 构建镜像。如果你用的是 IntelliJ IDEA装一个 Docker 插件在 Dockerfile 上右键就能直接构建镜像。就前端而言Node 项目也有类似的流程本地 build 出静态文件再放到 Nginx 镜像里跑。先看一个更精细的 Java DockerfileFROM maven:3.8-openjdk-17 AS build WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package -DskipTests FROM openjdk:17-jdk-slim WORKDIR /app COPY --frombuild /build/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]这里用了多阶段构建。第一阶段用 Maven 镜像把源码编译成 jar 包第二阶段只拿 jar 包做一个精简运行镜像。这样做的好处是最终镜像里只有运行时需要的 Java 环境没有几百兆的 Maven 依赖镜像体积从 700MB 直接变成 300MB 不到。注意多阶段构建第一阶段里的RUN mvn dependency:go-offline是把依赖先下载好形成一个缓存层。以后只要不改 pom.xml重新构建时这层会被 Docker 缓存直接命中速度飞快。第一行FROM maven:3.8-openjdk-17 AS build里AS build给这个阶段起了一个名字后面的COPY --frombuild就是从那个阶段拷贝产物过来这种写法算是多阶段构建的标准范式。构建命令是docker build -t myapp:1.0.0 .如果你不是 Java 项目是 Node 前端项目Dockerfile 长这个样子FROM node:18-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build FROM nginx:alpine COPY --frombuild /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD [nginx, -g, daemon off;]这个思路是一样的构建阶段的产物只有 dist 目录运行时只交给 Nginx 去做静态托管。6.2 镜像构建提速的小技巧第一次构建比较慢是正常的但第二次如果还是很慢就要优化构建策略了。有一条隐藏的规则是Dockerfile 的每一行指令都会生成一个缓存层只有这一行没变后续才会继续走缓存。所以你要把“变化最少”的指令写在前面“变化最多”的放在后面。比如 pom.xml 一般很少变先 COPY 它再RUN mvn dependency:go-offline这样源码变动不会导致依赖重新下载。把COPY . .放在最后面每次源码变了最多只是这一层开始重建前面的缓存全部命中。还有一个我经常用的参数--cache-from在某些场景可以基于远程缓存构建。但对于大多数项目把指令顺序写对就已经能获得很好的缓存命中率了。6.3 构建时最常见的诡异问题我自己构建镜像时踩过一个大坑某些依赖下载特别慢甚至超时。解决办法是给构建过程配镜像加速器。如果你的服务器在国内建议把 Docker Hub 的官方源换成国内镜像源。配置位置在/etc/docker/daemon.json{ registry-mirrors: [https://docker.m.daocloud.io] }改完重启 Dockersudo systemctl restart docker之后docker pull的速度会快很多。这个配置同样适用于 Compose 拉取镜像的过程。Docker Desktop 则在 Settings → Docker Engine 里改同样的 JSON。提示如果docker build过程中网络一直不稳定可以给 buildx 配置 HTTP 代理或者直接在 Dockerfile 里用国内镜像的软件源。具体做法是RUN sed -i s|deb.debian.org|mirrors.aliyun.com|g /etc/apt/sources.list再加上 apt-get update。这样至少基础的 apt 操作不会卡。7. 生产环境十个细节决定部署成败7.1 安全设置Container 权限最小化容器并不是一个完全安全的“沙箱”。一个容器如果以 root 用户运行一旦被攻破攻击者可以直接操作宿主机的很多资源。所以生产环境建议给容器设置只读文件系统、禁止权限提升、非 root 用户运行。Compose 文件里对应写法是这样app: image: myapp:1.0.0 security_opt: - no-new-privileges:true read_only: true tmpfs: - /tmp user: 1000:1000read_only: true把容器的文件系统设为只读任何写入都会被拒绝但tmpfs: /tmp开了一个内存临时目录供运行时使用。这样就算容器被入侵攻击者很难留下持久化的文件。7.2 日志管理别让日志撑爆磁盘Docker 日志有一个很明显的特点它会把容器写往 stdout/stderr 的全部内容存到本地文件里而且默认是没有大小限制的。一个频繁打印日志的服务几天时间日志文件就能撑爆磁盘这绝不是危言耸听。在 Compose 文件里给日志加上轮转配置app: image: myapp:1.0.0 logging: driver: json-file options: max-size: 10m max-file: 3这样单个日志文件最大 10MB最多保留 3 个文件。满了就丢弃最旧的日志。代价是日志只能保留比较短的时间长期审计日志需要单独接入 ELK 或 Loki 这类集中日志平台。但起码系统不会被日志撑爆。7.3 健康检查容器真的“健康”吗restart: always只能保证容器进程不退出但如果容器内部的应用已经假死比如线程池耗尽、连接池打满容器本身还活着Docker 也不会重启它。这时候就用上健康检查了。在 Compose 里配置app: image: myapp:1.0.0 healthcheck: test: [CMD, curl, -f, http://localhost:8080/api/health] interval: 30s timeout: 5s retries: 3 start_period: 40sstart_period是给应用预留的启动时间。刚启动的 40 秒内的健康检查失败不计入重试次数这个参数能避免应用启动慢被误杀。如果你的镜像里没有 curl精简版镜像很常见可以考虑用wget或者改用 Java 的角度来检查。总之健康检查的地址一定要是一个轻量接口不要做复杂逻辑否则检查本身就会拖垮服务。7.4 资源限制防止容器把宿主机吃光默认情况下Docker 容器能使用宿主机所有内存和 CPU。如果一个容器出现内存泄漏整个宿主机可能因此崩溃。生产环境必须限制资源app: image: myapp:1.0.0 deploy: resources: limits: cpus: 1.0 memory: 1g reservations: cpus: 0.25 memory: 512Mlimits是硬上限超过会触发 OOM 被杀或者 CPU 被限制。reservations是预留的最小资源。用 Docker Compose 时deploy.resources在非 Swarm 模式下也能生效实测没问题。7.5 数据备份这是最后一条防线不管做了多少高可用备份永远是必须的。容器化的数据库备份其实就是备份宿主机的挂载目录。MySQL 的简单备份可以直接用docker exec执行 mysqldumpdocker exec myapp-mysql mysqldump -uroot -proot123456 myapp backup_$(date %F).sql把它写进 crontab 每天执行一次再配一个异地同步或者上传到对象存储数据库的安全感就有了。8. 常见问题排查这些坑你一定躲不开8.1 权限问题Got permission denied在 Linux 上执行docker ps报Got permission denied while trying to connect to the Docker daemon socket。原因基本上只有一个当前用户不在 docker 组里。sudo usermod -aG docker $USER然后重新登录让组权限生效。如果你是在脚本里用 docker 命令记得脚本里也用sudo或者确保执行用户已经加入了 docker 组。8.2 端口占用Address already in usedocker run -p 3306:3306的时候报bind: address already in use说明宿主机的 3306 端口已经被某个进程占用了。最可能的情况是宿主机自己也装了一个 MySQL占用了同一个端口。两种解决办法一是停掉宿主机的服务把端口让出来二是改 Comose 里的端口映射比如-p 3307:3306宿主机用 3307容器里依然是 3306。推荐改端口映射服务之间互相不干扰。排查占用的命令# Linux sudo lsof -i :3306 # Windows管理员 PowerShell netstat -ano | findstr :33068.3 容器一直重启Restartingdocker ps里显示状态是Restarting说明容器一直在重启。先看日志docker logs 容器名常见原因有几类启动命令写错、环境变量缺失、配置文件路径不对、依赖服务没就绪。如果是应用连不上数据库日志里通常有Connection refused或Communications link failure。这时把depends_on改得更严格或者在应用代码里加等待重试逻辑。我给一个偷懒但实用的办法在 Compose 里给应用环境变量加SPRING_DATASOURCE_HIKARI_INITIALIZATION_FAIL_TIMEOUT: 60000这类参数让连接池的初始化失败超时时间长一点这样数据库还在启动的过程中应用就能熬到数据库就绪不用频繁重启。8.4 Docker Desktop 连不上引擎Windows 上 Docker Desktop 启动后报failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxen。这种情况通常是 docker 引擎没真正起来。解决方法先去看 Docker Desktop 的主界面确认引擎图标是不是绿色的。如果一直在转圈点右上角的齿轮 → Restart重新启动。还不行的话在 PowerShell 里执行wsl --shutdown然后重新启动 Docker Desktop。这一步是彻底重置 WSL 里的 Docker 虚拟机很多诡异问题都能解决。如果还是不行检查 Windows 功能里 WSL 和虚拟机平台是否都启用了。很多时候系统更新后这些功能会被重置。8.5 磁盘空间占满No space left on device容器日志、镜像、数据卷、构建缓存任何一个都可能撑爆磁盘。先看占用docker system df然后用docker system prune -a清一遍脏数据。如果还不够检查一下日志目录确认 Compose 里的日志轮转配置是否生效。一个多年来值得养成的习惯是每周跑一次docker system df就像每周检查邮箱一样很多小问题在变严重之前就被发现。9. 最后说点个人经验我先说结论Docker Compose 是我目前见过的“性价比”最高的部署方案。它不完美但在绝大多数中小型项目中它能把从前需要一周才能搞定的环境搭建压缩到半天以内而且整个过程完全可复制、可回滚。踩过这么多次坑后我最大的体会是用 Docker 不是在解决“代码跑不起来”的问题而是在解决“环境不可控”的问题。当你开始把部署当成“写配置 维护配置”的过程而不是“敲命令 祈祷成功”的过程时你才算真正用好了 Docker。给你一个特别实用的建议每部署一个新项目把docker-compose.yml、Dockerfile、部署步骤提交到一个专门的仓库里。下次再遇到类似场景直接复制改改就能用。我自己的经验是这个“部署配置仓库”比任何文档都可靠。因为代码会过时文档会失准但一份真实跑通的配置永远不会骗你。