
1. 内容整体设计与思路拆解1.1 为什么现在部署应用首选 Docker Compose接手过新服务器、新电脑的人都懂那种感受。第一次拿到一台空机器要装 MySQL、装 Redis、装 Nginx、装应用服务光是环境变量和依赖版本就能折腾大半天。装到一半发现系统自带的包管理器版本太老卸载重装又牵扯出一堆依赖冲突——这种场景我经历过太多次了。后来我彻底切换到 Docker Docker Compose 这套方案情况完全变了。Docker 解决的是应用运行环境的问题它把应用连同依赖的库、配置、运行时环境一起打包成一个标准化的镜像只要宿主机有 Docker 引擎就能保证应用以完全相同的方式运行。Docker Compose 则解决的是多容器协作的问题通过一个 YAML 文件定义整个服务栈一键启动、一键停止、一键重建。我举一个生活中的例子。传统部署就像你搬到一个新城市每次都要重新租房、买家具、通水电而 Docker 相当于把整个房间打包成集装箱搬到哪儿都能直接拆箱使用。Compose 又进一步把客厅、卧室、厨房的所有集装箱编成一个完整的家一张清单就能全部还原。这套方案特别适合这几类人群个人开发者在本地搭建开发环境避免污染宿主机小团队需要快速交付、统一上线流程运维人员要管理多个中间件服务比如数据库、消息队列、缓存想要学习容器化部署的初学者用 Docker 入门远比直接啃 Kubernetes 友好我实测下来从零开始在一台全新的 Linux 服务器上把 Docker 环境搭好、跑起第一个业务服务熟练之后只需要十几分钟。这篇博文就把我整个操作流程、关键决策和踩过的坑全部梳理出来按这个步骤走基本不会翻车。1.2 整体部署框架与流程规划在动手安装之前先花两分钟把整套方案的架构看清楚后面操作就会顺畅很多。我的部署框架分四层底层是宿主机操作系统。只要 Linux 内核版本满足要求Docker 就能跑。我生产环境长期用的是 Ubuntu 22.04 LTS稳定、社区资料多、遇到的坑基本都有现成答案。CentOS 7 我也用过但维护期已经结束新项目不建议再选它。第二层是 Docker Engine也就是 Docker 的核心运行引擎。它负责镜像管理、容器生命周期管理、网络和数据卷的管理。对于绝大多数场景我们不需要关心引擎背后的细节只需要会用 docker 命令和它交互就行。第三层是 Docker Compose负责容器编排。写一个 docker-compose.yml 文件把服务定义、网络、数据卷统一管理起来。它解决的是多个容器之间如何配合的问题比如 MySQL 和 Redis 要能互通、应用服务要能访问数据库这些网络关系都由 Compose 管理。最上层是业务应用。比如我自己部署过的典型服务栈MySQL 8.0 做数据存储、Redis 7 做缓存、后端 Java/Go 服务做业务逻辑、Nginx 做反向代理。用 Compose 把这些服务描述清楚一个命令全部启动。整个流程拆解下来就是五步安装 Docker 引擎、安装 Docker Compose、配置镜像加速和基础参数、编写服务编排文件、启动并验证服务。每一步都很清晰尤其是前两步属于会者不难难者不会的部分我接下来详细讲。2. Docker 与 Docker Compose 安装全流程2.1 安装 Docker 引擎Linux 篇先说最常用的 Linux 安装方式。这里强烈建议用官方提供的 Docker 软件仓库安装不要直接用系统自带的 apt install docker 或者 yum install docker因为系统仓库里的版本往往很老功能不完整后续使用会遇到各种兼容问题。以 Ubuntu/Debian 为例官方推荐的做法是先把依赖装好然后添加 Docker 官方 GPG 密钥最后把官方软件源写入系统。我操作时通常直接跑下面这组命令# 更新软件包索引并安装依赖 sudo apt update sudo apt install -y ca-certificates curl # 创建密钥管理目录并下载官方 GPG 密钥 sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod ar /etc/apt/keyrings/docker.asc # 写入 Docker 软件源 echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release echo $VERSION_CODENAME) stable | \ sudo tee /etc/apt/sources.list.d/docker.list /dev/null # 正式安装 Docker 引擎和相关组件 sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin敲完这些命令之后Docker 引擎、命令行工具、containerd 容器运行时、Buildx 构建插件和 Compose 插件就一次性装好了。这里有几个细节值得留意。第一个是 docker-ce 和 docker-ce-cli 是两个独立的包前者是服务端后者是客户端命令使用时是配套关系缺一不可。第二个是 containerd.io 是底层容器运行时虽然大多数时候你不会直接感知它但它的版本如果有问题容器是起不来的。第三个是官方源在国内访问时可能有些慢如果下载超时可以换成清华或者阿里云的镜像源替换源之后再执行安装命令。如果你是 CentOS/RHEL 系统命令略有不同核心思路一致安装 yum-utils添加官方仓库然后 yum install docker-ce。我早期在公司用 CentOS 7 部署时还得额外执行 setenforce 0 关闭 SELinux 或者把 SELinux 策略配置好否则 Docker 启动时会报权限错误这个在 Ubuntu 上基本不用操心。装完之后先别急着用执行一下系统服务设置和状态检查# 设置 Docker 开机自启并立即启动 sudo systemctl enable docker sudo systemctl start docker # 确认安装版本和服务状态 docker --version sudo systemctl status dockersystemctl enable 这一步经常被新手忽略。如果不设置开机自启服务器一旦重启Docker 守护进程不会自动运行所有容器都起不来线上事故就发生了。我把这个习惯当成固定动作每次新机器都会先执行。2.2 Windows 与 macOS 安装注意事项Windows 和 macOS 上的 Docker 安装思路完全不一样核心是通过 Docker Desktop 这个图形化工具来使用。Windows 上最重要的一点是 Docker Desktop 依赖 WSL 2 后端也就是说在安装 Docker Desktop 之前你要先确保 Windows 系统已经启用了 WSL 2 功能。具体操作是以管理员身份打开 PowerShell执行 wsl --install装好之后重启系统然后在 BIOS 里确保虚拟化功能Intel VT-x 或者 AMD-V是开启的。我之前遇到过一台 Windows 机器怎么装都启动不了 Docker弹出错误提示 Virtualization support not detected 或者 Docker Desktop failed to start。查了半天发现是 BIOS 里的虚拟化开关没打开进 BIOS 把开关打开之后就正常了。这个问题与 Docker Desktop 本身无关纯属硬件虚拟化没有启用。macOS 上的安装就简单多了直接从官网下载 Docker Desktop 的 dmg 安装包拖拽安装完打开即可。Apple SiliconM 系列芯片芯片的 Mac建议下载对应 arm64 版本的安装包性能会好很多Intel 芯片的 Mac 则用 amd64 版本。另外Windows 在启动 Docker Desktop 之后如果 WSL 2 里的 Linux 发行版尚未初始化Docker 引擎会显示不健康状态。解决办法是在 PowerShell 里先跑几次 wsl --update让 WSL 内核更新到最新即可。2.3 安装 Docker Compose 并验证可用性现在 Docker Compose 的安装方式有两种主流选择。第一种是作为 Docker 插件安装在装 Docker 引擎时带上 docker-compose-plugin 这个包命令直接用 docker compose注意没有横杠。第二种是独立可执行文件方式把 docker-compose 二进制文件下载到 /usr/local/bin 目录命令是 docker-compose带横杠。这里有一个让很多人疑惑的点docker compose 和 docker-compose 到底有什么区别其实它们是同一个工具的不同版本形态。compose 插件是 Docker 官方推荐的现代方式功能更新及时集成度更高docker-compose 是老的独立版本V1 时代的标准现在官方已经停止维护 V1 了建议新环境一律使用插件方式。可以这样验证插件是否安装成功docker compose version正常情况下会输出类似 Docker Compose version v2.24.0 这样的信息如果你能看到这个输出说明 Compose 已经就绪可以进入下一步了。如果由于某些原因需要独立二进制的方式可以去 GitHub 上 Docker Compose 的 Releases 页面找到对应平台和架构的二进制文件把它下下来赋予执行权限后放到 PATH 目录下。我在内网离线环境部署时就是采用这种方式的下载一个文件传进去就能用非常方便。验证完版本之后建议再执行一个简单的端到端测试确认 Docker 引擎真的可以正常运行容器。我习惯用 hello-world 这个最小化测试镜像docker run --rm hello-world如果一切正常终端会打印一段说明文字告诉你 Docker 已经成功安装并运行了容器。如果这一步报错大概率是镜像拉取网络不通先排查镜像加速配置这个我在下面会详细说。2.4 配置国内镜像加速与基础环境参数很多人在装完 Docker 之后第一次跑 docker pull 就会卡在大片空白等待中然后报错 timeout 或者 connection refused。原因很简单Docker Hub 在国外服务器上国内网络环境直接拉镜像时带宽极差甚至完全不通。解决办法是在 Docker 的配置文件里添加镜像加速器。我用的镜像加速服务有阿里云容器镜像服务、腾讯云加速器、或者是 Docker 官方在中国区的镜像源。每个加速器提供的地址格式不太一样但配置文件的位置是统一的/etc/docker/daemon.jsonWindows 和 macOS 上则是通过 Docker Desktop 的 Settings 界面配置。我的 daemon.json 文件是这样的{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com, https://docker.mirrors.ustc.edu.cn ], log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 }, data-root: /var/lib/docker }除了镜像加速之外这里面还有两个非常实用的配置日志限制和存储路径。日志限制的意思是每个容器产生的日志文件单个最大 10MB、最多保留 3 个文件。不加这个限制的话有些日志输出密集的应用比如 Nginx 的访问日志会把磁盘瞬间打满我踩过这个坑——线上服务跑了两周磁盘直接 100% 占满罪魁祸首就是一个不写访问日志格式规范的应用容器。>sudo systemctl daemon-reload sudo systemctl restart docker重启之后再用 docker info 查看 Registry Mirrors 一栏确认加速器已经挂在生效。实测下来配置加速之后的镜像拉取速度一般能到几 MB/s 甚至更快和之前根本不是一个体验。3. Docker 核心概念与常用操作3.1 镜像、容器与数据卷的边界感很多新手到了 Docker 命令面前就犯迷糊根源在于对镜像Image、容器Container、数据卷Volume这三者的关系理解不到位。我用最通俗的方式拆开讲一遍。镜像是一个只读的模板可以理解成操作系统的 ISO 文件。它包含了运行一个应用需要的全部内容操作系统的基础文件、运行环境、应用代码、依赖库、配置文件。镜像本身不会变启动后的任何修改都不会直接写进镜像里。容器是镜像的运行实例。把镜像想象成一本空白练习册的模板每次启动容器就是在模板上誊写一份新的草稿纸。你在容器里创建的文件、修改的配置都存在于这个容器的可写层里。容器一旦删除这一层就没了里面所有的数据都会消失。数据卷是专门用来持久化数据的独立空间。它独立于容器的生命周期而存在就像一块外接移动硬盘。容器挂掉、删掉、重建数据卷上的数据都还在。数据卷可以挂载到容器内部的某个目录比如 MySQL 数据目录 /var/lib/mysql、Redis 的持久化文件目录 /data。凡是涉及数据库、消息队列、文件存储这类需要保存数据的服务必须把数据目录挂载到数据卷或者宿主机目录上这是生产环境部署的铁律。我见过太多人直接在容器里跑了一个 MySQL容器一删几年的业务数据全没了那种教训真的是代价惨重。3.2 常用 Docker 命令速查与使用场景这里整理一份我在日常使用中真正高频率用到的命令按照需要的时候能想起来的标准来归类。镜像管理这块核心是拉取、查看、构建和删除。拉取一张镜像用 docker pull nginx:1.27如果要指定仓库地址就完整写出镜像地址。查看本地镜像用 docker images删除镜像用 docker rmi 镜像ID。构建自定义镜像用 docker build -t 项目名:版本号 .注意最后的点是构建上下文路径不能省略。容器生命周期管理是最高频的操作。启动一个容器最基础的命令是 docker run但我建议新手上路先掌握它几个最关键的参数-d 表示后台运行-p 映射端口--name 指定容器名字-v 挂载数据卷--restart 设置重启策略。完整示例docker run -d \ --name my-nginx \ -p 8080:80 \ -v /home/user/html:/usr/share/nginx/html \ --restart unless-stopped \ nginx:1.27这条命令的意思是以后台方式启动一个名为 my-nginx 的容器把宿主机的 8080 端口映射到容器内的 80 端口将宿主机的 /home/user/html 目录挂载到容器里 Nginx 的 html 目录同时设置容器退出时自动重启除非手动停止。查看容器运行状态用 docker ps查看所有容器包含已停止的用 docker ps -a。进入一个正在运行的容器内部排查问题用 docker exec -it 容器名 bash。查看日志用 docker logs -f 容器名这个是排障最重要的一条命令没有之一。停止和删除容器分别是 docker stop 容器名 和 docker rm 容器名。我始终觉得这些命令不需要死记硬背多用几次自然就记住了关键是理解每个命令背后的作用对象是镜像还是容器还是数据卷以及容器删除后会发生什么。3.3 Dockerfile 编写要点与镜像构建实战当你要部署自己的应用而不是直接用现成的数据库、缓存镜像时Dockerfile 就成了必须掌握的内容。它的本质是一份如何构建镜像的说明书Docker 会按照里面的指令一步步把镜像组装出来。我以一个 Spring Boot 应用的 Dockerfile 为例# 第一阶段使用 Maven 构建 Java 应用 FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests # 第二阶段只保留运行所需的 JDK 和应用包 FROM eclipse-temurin:17-jre WORKDIR /app COPY --frombuilder /app/target/app.jar . EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]这个 Dockerfile 用到了多阶段构建这是我认为最重要的镜像优化手段。第一阶段负责编译出可执行的 jar 包第二阶段只拿走这个 jar 和运行环境。这样最终镜像的体积大约只有几十 MB而如果直接用带 Maven 的镜像跑体积动辄几个 GB浪费磁盘和推送拉取时间。写 Dockerfile 有几个经验法则值得记住基础镜像优先选官方镜像和带版本号的比如 eclipse-temurin:17-jre 而不是 latest避免将来基础镜像更新导致不可控变化把变化频率低的指令放在前面比如 COPY pom.xml、RUN mvn dependency:go-offline变化频率高的放后面这样能充分利用 Docker 的缓存层构建速度会快很多一个容器只跑一个主进程不要既跑应用又启动数据库。构建完了之后先 docker images 确认镜像存在然后 docker run 启动它访问对应端口看服务是否正常响应。这一流程跑通之后你的构建镜像这一步就算彻底过关了。3.4 容器网络模式选择的三个关键场景网络模式听起来抽象但对部署的影响非常直接。Docker 默认提供几种网络模式bridge、host、none还有就是用户自定义的 bridge 网络。我实际部署时最常用的是默认 bridge 和自定义 bridge。默认 bridge 网络的特点是容器之间可以通过 IP 互相访问但 IP 会动态变化不太方便。我更推荐创建自定义网络用服务名作为域名来互相访问这个在 Compose 里会自动帮我们做好后面会看到。容器对外提供服务时需要用到端口映射。Docker 的端口映射是宿主机端口:容器端口比如 -p 3306:3306。这里有个细节如果宿主机 3306 端口已经被占了你可以映射到别的端口比如 -p 33061:3306这样外部通过 33061 访问 MySQL容器内部依然监听 3306MySQL 的配置完全不用改。host 网络模式比较特殊容器直接使用宿主机的网络栈没有端口映射的概念。这种模式性能损耗最低但隔离性差我一般只在某些对网络延迟要求极高的内部服务上才用生产环境如果没有特殊理由优先用 bridge 端口映射。4. Docker Compose 编排实战一次完整的服务部署4.1 设计一个 MySQL Redis 后端应用的 Compose 文件基础命令掌握之后真正提升部署效率的是 Docker Compose。它把启动一堆容器并处理好它们之间的关系这件事从一条条命令手工执行变成了一次性环境定义。我来展示一个完整的、我在生产环境实际用过的服务栈编排文件。先说这个场景一个典型 Web 应用后端使用 Java 或 Node.js 服务数据存储在 MySQL 8.0缓存用 Redis 7。三个服务之间通过网络互相通信MySQL 的数据要持久化Redis 的 AOF 文件要保存后端服务的日志要能导出查看。docker-compose.yml 文件如下version: 3.8 services: mysql: image: mysql:8.0 container_name: demo-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: Root123456 MYSQL_DATABASE: demo_app MYSQL_USER: demo_user MYSQL_PASSWORD: Demo123456 ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql - ./mysql-init:/docker-entrypoint-initdb.d:ro command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -uroot, -pRoot123456] interval: 10s timeout: 5s retries: 5 redis: image: redis:7-alpine container_name: demo-redis restart: unless-stopped command: [redis-server, --appendonly, yes, --requirepass, Redis123456] volumes: - redis_data:/data ports: - 6379:6379 healthcheck: test: [CMD, redis-cli, -a, Redis123456, ping] interval: 10s timeout: 5s retries: 5 app: build: ./app container_name: demo-app restart: unless-stopped depends_on: mysql: condition: service_healthy redis: condition: service_healthy environment: DB_HOST: mysql DB_PORT: 3306 DB_NAME: demo_app DB_USER: demo_user DB_PASSWORD: Demo123456 REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD: Redis123456 ports: - 8080:8080 volumes: - ./logs:/app/logs volumes: mysql_data: redis_data:这个文件信息量很大每一个字段背后都是生产实践总结出来的经验。4.2 Compose 文件的逐段拆解与参数选型逻辑先看版本号。现在最新版本的 Compose 已经不太强调 version 字段了但写上 3.8 这类版本号也不会报错兼容性更好。核心的三个小节是 services、volumes、networks。每个 service 下面的 image 决定你用什么镜像。这里我强烈建议显式写出版本号不要用 latest。原因很简单latest 是浮动的今天拉下来是 8.0.x过几个月再拉可能就成了 8.4而这种大版本升级往往有破坏性变更应用可能直接连不上数据库。锁定版本后如需升级走明确的变更流程。environment 是环境变量配置。MySQL 镜像官方支持 MYSQL_ROOT_PASSWORD、MYSQL_DATABASE、MYSQL_USER、MYSQL_PASSWORD 这些变量第一次启动时容器会自动完成数据库初始化和用户创建。这里有个安全问题以上密码直接写在文件里只适合本地开发生产环境建议通过 .env 文件或者 Docker secret 管理敏感信息。volumes 数据卷部分mysql_data 和 redis_data 是由 Compose 管理的命名卷数据保存在 /var/lib/docker/volumes/ 下面容器删掉重建数据也不会丢。./mysql-init 这个目录挂载值得单独说MySQL 官方镜像启动时会自动执行 /docker-entrypoint-initdb.d/ 目录下的所有 .sql 脚本。把初始化表结构、种子数据的 SQL 放进去首次启动即可自动导入非常方便。healthcheck 健康检查是生产环境必备的关键配置。MySQL 用 mysqladmin ping、Redis 用 redis-cli ping每隔 10 秒探测一次。这个配置的意义在于app 服务启动时可以使用 depends_on 配合 condition: service_healthy确保数据库和缓存完全就绪之后才启动应用从根本上解决了应用启动时数据库还没初始化完成导致连接失败的经典问题。restart: unless-stopped 设置的是 Docker 守护进程启动时自动拉起容器同时容器内部进程异常退出时自动重启。这个策略对大多数业务服务都适用比自己手写守护脚本强得多。4.3 启动编排栈的命令与踩坑优先级文件写好之后进入启动环节。我先说最常用的命令再说排查思路。在 docker-compose.yml 所在目录执行# 拉取镜像并启动服务 docker compose up -d # 查看服务状态 docker compose ps # 查看所有容器日志 docker compose logs -f # 仅查看 app 服务的日志 docker compose logs -f app # 停止服务但不删除数据卷 docker compose down # 停止服务并删除数据卷慎用 docker compose down -vup -d 会在后台启动所有服务首次执行会先拉取镜像如果有 build 配置还会执行镜像构建。ps 能看到每个服务的运行状态、端口映射和健康情况。logs -f 是实时跟踪日志调试时最常用。我第一次跑这个栈的时候遇到过 MySQL 初始化失败的情况。打开日志一看是 MYSQL_USER 变量指定的用户和 MYSQL_PASSWORD 密码不一致导致的权限错误修改密码配置后删除旧的数据卷重新初始化就好了。这里要注意MySQL 镜像的数据目录一旦初始化完成再修改环境变量里的密码也不会自动生效必须先 docker compose down -v 清理卷再重新 up。生产数据一定不能乱删但开发环境这个操作是安全的。还有一件事别把 docker compose down -v 当成普通停止命令用。我见过有人图省事每次更新代码都删卷重启结果把 MySQL 数据全清了。停止服务用 down 就行加 -v 只是针对确认数据不要了的清理场景。4.4 用 .env 文件管理环境差异在 4.1 的 yaml 文件里直接硬编码数据库密码这个写法在单机开发时无所谓但要部署到多套环境本地、测试、生产每次改密码都要改 yaml 文件不仅麻烦还容易出错。这里我用 .env 文件配合变量替换来解决。在 Compose 文件同目录创建一个 .env 文件MYSQL_ROOT_PASSWORDRoot123456 MYSQL_DATABASEdemo_app MYSQL_USERdemo_user MYSQL_PASSWORDDemo123456 REDIS_PASSWORDRedis123456然后把 docker-compose.yml 里的对应位置改成 ${变量名} 引用environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD}这样部署到不同环境时只需要替换 .env 文件即可Compose 在启动时会自动读取这个文件并用其中的值替换模板变量。我还会把 .env 加入 .gitignore避免数据库密码泄露到代码仓库。这个习惯可能在一个人开发时不觉得有什么但一旦项目交给别人或者开源出去就是很重要的安全边界。5. 生产环境部署的关键经验与注意事项5.1 安全加固最小权限与敏感信息保护从开发环境走向生产环境安全是绕不开的课题。我在这块总结了一些简单但极其重要的习惯。第一个是容器内尽量使用非 root 用户运行进程。默认情况下容器内进程就是 root 权限一旦容器被攻破攻击者就有可能通过内核漏洞或错误配置提升到宿主机权限。在 Dockerfile 里加两行就能解决的大部分问题RUN groupadd -r app useradd -r -g app app USER app这样容器内的主进程就以 app 用户身份运行权限范围大大缩小。对于官方镜像里的 MySQL、Redis它们默认已经做了用户降权不用额外处理。第二个是敏感信息不要直接写在 Dockerfile 或 Compose 文件里。我刚才提到了 .env 文件这算第一层防线。如果再严格一点可以用 Docker 原生的 docker secret 功能或者干脆用外部配置中心统一管理密码、API 密钥。我个人的原则是凡是能不放代码库的信息一律不放。第三个是网络端口的暴露范围。默认 0.0.0.0 绑定意味着宿主机所有网卡接口都会暴露这个端口包括公网网卡。如果 MySQL 只需要给内网应用访问就不要把 3306 端口映射到 0.0.0.0:3306而是映射到内网 IP 上比如 192.168.1.10:3306:3306。我见过有人在云服务器上把 Redis 端口直接暴露到公网且没设置密码几分钟内就被扫描爆破服务器直接成了矿机。这个教训足够深刻。5.2 资源限制与监控告警的配置生产环境的另一个关键问题是没有限制容器资源使用量。一个 Java 应用内存泄漏可以把宿主机内存全部吃光导致整个机器上所有服务崩溃。Docker 原生支持资源限制在 Compose 文件里配置非常简单services: app: deploy: resources: limits: cpus: 1.0 memory: 1G reservations: cpus: 0.25 memory: 256Mlimits 是硬限制应用最多只能使用 1 个 CPU 核心和 1GB 内存超过会被系统 OOM 杀掉或者被 throttling 限制 CPU。reservations 是软限制表示对这个服务预留的最小资源。配置之后即使某个容器出现异常影响范围也被控制在一个容器内不会拖垮整台机器。关于监控告警我建议至少做到以下几点宿主机的 CPU、内存、磁盘使用率要有基础监控Docker 引擎本身的状态要有检测每个容器是否处于运行状态、健康检查是否通过要有告警。如果不想引入复杂的 Prometheus 全家桶初期可以用 shell 脚本配合 crontab 定时检查 docker ps 的状态出现问题就发通知。先把最基础的告警跑起来再逐步升级到专业监控系统这个递进路线比较务实。5.3 数据备份与恢复的实操方案有状态服务是 Docker 部署中的关键部分。我在生产环境做数据备份时遵循一个原则备份一定在容器外部做不能只依赖容器内部的机制。以 MySQL 为例最简单的备份是在宿主机上执行 mysqldump 命令把数据导出到宿主机目录。我常用的脚本大致是这个样子#!/bin/bash BACKUP_DIR/backup/mysql DATE$(date %Y%m%d%H%M) docker exec demo-mysql mysqldump -uroot -pRoot123456 --single-transaction --routines --triggers demo_app $BACKUP_DIR/demo_app_$DATE.sql find $BACKUP_DIR -name *.sql -mtime 7 -delete这段脚本做了两件事把 demo_app 数据库完整导出到备份文件然后删除 7 天前的旧备份。配合 crontab 每天凌晨执行一次基础的数据安全就有保障了。恢复的操作也很直接。如果需要恢复某一天的备份把备份文件导入容器cat /backup/mysql/demo_app_202401011200.sql | docker exec -i demo-mysql mysql -uroot -pRoot123456 demo_app这里有一个容易犯的错误mysqldump 导出时如果不加 --single-transaction 参数备份过程中会把表锁住影响线上读写。加上之后使用 InnoDB 的事务一致性快照导出过程中业务不受影响。再有就是每天除了保留本地备份有条件的话尽量把备份文件同步到异地存储或者对象存储防止宿主机整个故障导致备份也一起丢失。5.4 镜像更新与平滑迁移的流程容器化部署带来的一大优势是更新方便但更新也有正确的姿势。当应用代码更新时步骤是先构建新镜像然后通过 Compose 重新创建容器。命令是docker compose build app docker compose up -d app这种方式的更新是停旧起新中间会有几秒钟的停机窗口。如果业务对连续性要求高就需要引入滚动更新或者蓝绿发布机制那超出了单机 Compose 的能力范围一般需要引入更上层编排工具这里不展开。更新镜像还要注意一点当你想要升级一个基础中间件的大版本比如 MySQL 从 8.0 升到 8.4操作前务必备份所有数据、先在一个测试环境跑通整个升级路径、确认应用兼容新版本再动生产。因为在数据库这类有状态服务上降级不是简单的换镜像就行数据文件的格式变化往往是不可逆的。5.5 日志管理与磁盘空间的长期维护前面在 daemon.json 里我提到了日志轮转配置这是防止磁盘被日志打满的第一道防线。除此之外我在项目层还有一个习惯把容器的日志按照天级导出到宿主机统一目录尤其对于 Nginx、应用服务这类访问日志有分析价值的服务。Compose 文件里的日志挂载可以这样写services: app: volumes: - ./logs:/app/logs logging: driver: json-file options: max-size: 20m max-file: 5容器内应用把日志写到 /app/logs 目录这个目录通过数据卷映射到宿主机的 ./logs 目录。宿主机的日志收集工具比如 Filebeat、Logstash可以直接采集这个目录形成完整的日志分析链路。既满足了文件日志的持久化需求又避免了 Docker 自带 json-file 日志膨胀的问题。磁盘空间的长期维护还要注意镜像和构建缓存的清理。长时间使用之后docker system df 查看会发现很多悬空镜像和 Build Cache。定期执行docker system prune -f可以把停止的容器、未使用的网络、悬空镜像和构建缓存全部清掉。加 -a 参数会把它变成 docker system prune -af连同所有未被容器引用的镜像一起删除这个操作谨慎使用因为会删除本地所有未运行的镜像下次需要时还得重新拉取。6. 常见问题与排查技巧实录6.1 高频报错速查表这一节我整理了一份我自己实际遇到过的、频率最高的问题速查表采用问题现象 原因 解决办法的结构。每个问题背后都有真实场景不是从文档里抄来的。问题现象根本原因解决办法docker: permission denied当前用户不在 docker 用户组sudo usermod -aG docker $USER 后重新登录Error response from daemon: Conflict. The container name xxx is already in use容器名字被占用docker rm -f 旧容器或用新名字无法解析镜像仓库地址Docker 配置了不可用的 registry-mirrors检查 /etc/docker/daemon.json 的镜像加速配置拉取镜像超时国内网络访问 Docker Hub 慢/不通配置可用的镜像加速器容器启动后立刻退出应用启动失败或前台进程退出docker logs 查看日志修复应用问题容器内 MySQL 无法远程连接只监听 localhost 或端口映射错误检查 command 参数、端口映射和云安全组容器时区不对显示 UTC容器内时区未设置为 CST挂载 /etc/localtime 或设置环境变量 TZAsia/Shanghaino space left on device/var/lib/docker 所在分区磁盘满了docker system prune 清理或调整>