资讯动态

Docker + Compose 实战:从环境准备到生产级部署的完整指南

发布时间:2026/9/16 6:17:46 来源:尧图企业网站定制
说真的Docker 相关的教程我在网上看过无数篇也照着部署过不少服务踩过的坑基本能绕地球一圈。这套 “Docker Docker Compose 部署” 的流程是我在真实项目里反复使用、亲测有效的一套方案不是单纯的文档搬运。这篇内容适合这几类人看刚接触 Docker 想搭一套自己的服务环境的同学、在 Windows 上被 Docker Desktop 各种报错折磨到头秃的朋友、以及想把 MySQL、Redis、GitLab 这类常用服务快速跑起来但不想被安装文档淹没的开发者。我会把所有前期准备、核心概念、实操步骤和排坑经验都放在一起尽量让你照着做就能成功。1. 环境准备Docker 装不好后面全是坑1.1 Windows 用户别跳过 WSL2 这一步很多人装 Docker Desktop 失败十有八九是卡在虚拟化或者 WSL2 上。Docker Desktop 在 Windows 上现在默认依赖 WSL2 后端这是它运行容器的底层支撑。网上搜到的 “virtualization support not detected” 或者 “Docker Desktop failed to start because...” 这类报错基本都是 WSL2 没启用或者 BIOS 里的虚拟化开关没打开。装之前先在 PowerShell管理员里跑两条命令dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /restart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /restart跑完重启再去 BIOS 里确认虚拟化Intel VT-x / AMD-V已经开启。然后下载 WSL2 内核更新包安装最后执行wsl --set-default-version 2这时候再装 Docker Desktop基本不会再报虚拟化相关的错误。顺手说过一句踩过的坑Windows 上装完 Docker Desktop 后第一次启动如果提示需要更新 WSL 内核不要点“忽略”老老实实去下载安装不然之后拉镜像很容易莫名其妙失败。1.2 Ubuntu / CentOS 服务器官方脚本不是唯一选择如果是 Linux 服务器很多人喜欢直接执行 Docker 官方脚本一行安装curl -fsSL https://get.docker.com | bash这个脚本确实快但有个问题它会把 Docker 的 apt 源指向国外服务器在国内网络环境下经常出现下载超时。我的习惯是先配置好国内的软件源再装。以 Ubuntu 22.04 为例先装依赖sudo apt update sudo apt install -y ca-certificates curl gnupg lsb-release添加官方 GPG key 和仓库然后把源地址替换成可用的镜像源最后sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin注意这里直接安装了docker-compose-plugin这是 Docker Compose 的官方插件版本装完系统里就有docker compose命令注意是docker compose中间带空格不是旧版的docker-compose。CentOS 那边类似用 yum 装docker-ce docker-ce-cli containerd.io然后单独装 compose 插件包步骤差不太多。装完先验证一下sudo systemctl enable --now docker docker version docker compose version如果两条命令都有输出环境基本就绪了。还有一步很重要把当前用户加进 docker 组免掉每次敲 sudosudo usermod -aG docker $USER加完重新登录终端才生效。1.3 镜像加速配不好部署失败一大半Docker 装好了第一件事不是急着跑容器而是配置镜像加速。纯新手最容易在这栽跟头明明docker pull nginx:latest输进去了结果卡在 “Get https://registry-1.docker.io/v2/” 或者连 pull 到一半就断了重试心态直接崩掉。Docker 的镜像仓库默认在国外网络经常不稳定所以需要给 Docker 配置镜像加速地址。做法是在/etc/docker/daemon.jsonWindows 上是 Docker Desktop 的 Settings - Docker Engine里写{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com ] }改完重启 Dockersudo systemctl restart docker配好之后docker pull的速度和成功率都会好很多。这个文件不止配镜像加速后面很多全局参数都在这里设置比如日志大小限制后面会提到。注意不同加速源的有效期和稳定性不一样最好挑一个备选一个不行就换另一个有备无患。2. Docker Compose 到底解决什么问题2.1 为什么不用 docker run 硬扛有人觉得我直接用docker run不也能跑容器吗为什么要学 Compose拿一个最简单的场景说你要部署 MySQL用一段 docker run 命令确实能跑起来docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -v mysql-data:/var/lib/mysql \ mysql:8.0一条命令可以接受但如果你要部署 Redis、RabbitMQ、Nginx 三个服务呢每个服务都得找对应的启动参数端口、密码、数据卷、网络统统要写对这些命令还特别长一旦写错就得删掉重来。更糟糕的是这些容器默认都在各自的网络里想互相通信还得手动docker network create去对接。Compose 做的事情就是把这一堆手动操作写进一个compose.yaml文件里用声明式的方式定义“我这个项目需要哪些容器、各自怎么配置、怎么互相通信”。之后只需要一个命令docker compose up -d所有容器一次性启动干净利落。我遇到过一个自带十多个服务的开源项目官方文档给了五页部署步骤实际上就是一张 compose 文件的事。这也解释了为什么现在大量开源项目Dify、GitLab、RabbitMQ 等都会提供现成的 compose.yaml拉下来直接就能跑。2.2 compose.yaml 核心字段是哪些Compose 文件的语法不复杂但有些字段如果不理解后面排错会很痛苦。services 是顶层核心字段下面每个子项代表一个容器服务。image 指定镜像ports 做端口映射environment 传环境变量volumes 挂载数据卷networks 指定网络。下面这个例子是单服务的极简 composeservices: web: image: nginx:1.27-alpine container_name: my-web ports: - 8080:80 volumes: - ./html:/usr/share/nginx/html restart: unless-stopped几个值得深入理解的点ports的8080:80表示宿主机 8080 端口映射到容器内 80 端口。容器 80 端口一般不能被外部直接访问容器网络隔离所以必须映射。但注意映射也意味着宿主机端口被占用同一个端口不能被两个容器同时映射。volumes是数据持久化的关键。容器是无状态的只要容器被删容器内部写的数据全会丢。把宿主机的目录或具名卷挂载到容器内目录数据就能留宿主机上。restart: unless-stopped让容器在意外退出时自动重启生产环境几乎是标配不然服务器一重启服务就全部挂掉很头大。2.3 版本语义和兼容性用 Compose 时要注意版本概念。Docker Compose 现在分两代旧版使用 Python 写的docker-compose带横杠新版是 Docker 官方用 Go 重写的docker compose插件带空格。新版兼容旧的docker-compose.yml文件格式但命令参数略有不同。如果照着网上老教程用docker-compose up -d而系统只装了新插件会提示 “docker-compose: command not found”。现在最省事的方案直接用docker compose语法上区别不大只是注意搜索引擎里的教程有“时代差异”老教程的命令要手动转换一下。version字段在新版里已经标记为废弃compose.yaml 文件顶部不用写version: 3.8这类内容写了也不影响但没必要。默认就是最新格式。3. 三个高频场景的 Compose 实战3.1 MySQL 8.0持久化和时区一个都不能少MySQL 是容器化部署时最容易踩坑的数据库之一。最经典的错误容器跑了很久数据都存进去了突然容器被删或者重建所有数据全没了。原因就是没用数据卷挂载。所以 MySQL 的 compose 里volumes必须配置。我常用的 MySQL 8.0 配置services: mysql8: image: mysql:8.0 container_name: mysql8 restart: unless-stopped environment: TZ: Asia/Shanghai MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: app_db MYSQL_USER: app_user MYSQL_PASSWORD: app_pass command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci - --default-authentication-pluginmysql_native_password ports: - 3306:3306 volumes: - mysql-data:/var/lib/mysql - ./mysql/conf:/etc/mysql/conf.d - ./mysql/init:/docker-entrypoint-initdb.d healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -uroot, -proot123456] interval: 10s timeout: 5s retries: 5 volumes: mysql-data:细节拆解TZ: Asia/Shanghai解决容器内时间比宿主机慢 8 小时的问题。不用 TZ 环境变量的话也可以挂载/etc/localtime:/etc/localtime:ro两个方案选一个。建议优先用环境变量兼容性更好。command里的参数是 MySQL 服务端的启动参数。utf8mb4字符集必须显式声明否则默认的 latin1 会让你写进去的中文全部变成乱码。mysql_native_password是为了兼容老客户端新版 MySQL 默认用 caching_sha2_password个别老版本客户端连不上这个参数可以按需添加。./mysql/init:/docker-entrypoint-initdb.d这个目录会自动执行里面的 SQL 脚本。比如把建库建表语句放进去容器首次启动时自动完成初始化非常方便。但注意只有首次启动且数据目录为空时会执行数据卷已经有内容就不会再执行了。healthcheck是健康检查配合后面的depends_on很有用下面会详细说。启动命令docker compose up -d docker compose ps看到 STATUS 显示 healthy说明 MySQL 已经就绪可以用客户端连接了。3.2 Redis 日常和生产环境部署Redis 在 Compose 里最常见的是单节点和主从两种玩法。先说日常开发用的单节点services: redis: image: redis:7-alpine container_name: redis-dev restart: unless-stopped ports: - 6379:6379 command: redis-server --requirepass redispass --appendonly yes volumes: - redis-data:/data volumes: redis-data:--appendonly yes开启 AOF 持久化数据安全性更好。开发环境这样够了但如果做生产级部署就得考虑主从复制甚至哨兵。主从结构我实际部署过一套两个 Redis 实例一个主一个从配置思路如下services: redis-master: image: redis:7-alpine container_name: redis-master restart: unless-stopped ports: - 6379:6379 command: redis-server --requirepass masterpass --appendonly yes redis-slave: image: redis:7-alpine container_name: redis-slave restart: unless-stopped depends_on: - redis-master ports: - 6380:6379 command: redis-server --slaveof redis-master 6379 --masterauth masterpass --requirepass slavepass --appendonly yes volumes: - redis-slave-data:/data volumes: redis-slave-data:注意 slave 里使用了服务名redis-master作为主机地址这是 Compose 默认网络内的 DNS 解析能力。容器之间直接通过服务名互相访问不需要关心具体 IP 是多少这也是 Compose 相比裸 docker run 的另一个便捷之处。Redis 7 之后的趋势是使用 Redis Stack带 JSON、Search 等模块或者 valkey 之类的替代分支。不过本教程目标是用稳定方案redis:7-alpine 镜像体积小、内存占用低已经能满足绝大多数场景。3.3 GitLab 和 RabbitMQ重服务的资源规划GitLab 是容器化部署里出了名的“吃内存大户”默认配置下 8G 内存都可能不够用。但好消息是 Compose 里可以限制资源。GitLab 的官方推荐是通过环境变量覆盖配置。我的实践配置如下services: gitlab: image: gitlab/gitlab-ce:latest container_name: gitlab restart: unless-stopped hostname: gitlab.example.com environment: GITLAB_OMNIBUS_CONFIG: | external_url http://gitlab.example.com unicorn[worker_processes] 2 puma[worker_processes] 0 sidekiq[max_concurrency] 5 prometheus_monitoring[enable] false grafana[enable] false ports: - 80:80 - 443:443 - 2222:22 volumes: - gitlab-config:/etc/gitlab - gitlab-logs:/var/log/gitlab - gitlab-data:/var/opt/gitlab shm_size: 256m volumes: gitlab-config: gitlab-logs: gitlab-data:这里有几个关键点GITLAB_OMNIBUS_CONFIG是 GitLab 容器用来生成配置的环境变量不在这里写配置后面每次修改都要进容器改/etc/gitlab/gitlab.rb再重启麻烦。关闭了 Prometheus 和 Grafana因为单机部署用不上能省一大截内存。2222:22映射 SSH 端口外部连接用 2222不然和宿主机 SSH 的 22 端口冲突。shm_size设大一点GitLab 对 /dev/shm 要求很高默认 64M 容易触发 “SIGBUS” 报错。RabbitMQ 部署相对简单主要是注意管理插件services: rabbitmq: image: rabbitmq:3.13-management container_name: rabbitmq restart: unless-stopped environment: RABBITMQ_DEFAULT_USER: admin RABBITMQ_DEFAULT_PASS: admin123 ports: - 5672:5672 - 15672:15672 volumes: - rabbitmq-data:/var/lib/rabbitmq volumes: rabbitmq-data:rabbitmq:management前缀的镜像自带 Web 管理界面15672 就是管理后台的端口。5672 是 AMQP 协议端口给客户端连接用。4. 生产环境部署的进阶配置4.1 网络规划端口冲突与内网隔离初学者最容易犯的错就是把所有端口一律暴露到宿主机。比如部署一个由 Nginx、后端 API、MySQL 组成的应用看到 MySQL 3306 就映射3306:3306看到 Redis 6379 就映射6379:6379。这样做的风险在于数据库和缓存直接暴露在外网等于把家门钥匙挂在门口。生产环境的思路是分层外部能访问的只有入口服务比如 Nginx 的 80/443MySQL、Redis 这些内部组件只在内网互相访问不映射端口。Compose 文件天然支持这种网络隔离services: nginx: image: nginx:1.27-alpine ports: - 80:80 networks: - frontend - backend app: image: myapp:latest networks: - backend mysql: image: mysql:8.0 networks: - backend # 不写 ports外部无法访问 networks: frontend: backend:这样app和mysql都在 backend 网络内通过服务名互相访问只有nginx同时连了 frontend 和 backend它既能接收外部流量又能通过http://app:8080这样的地址访问后端服务。而 MySQL 因为没写 ports宿主机外网根本访问不到大大降低了风险。4.2 数据安全备份、恢复与升级容器化部署的一大优势是方便重建但也有一个陷阱数据全靠数据卷存着一旦数据卷误删神仙难救。所以备份必须纳入日常运维计划。用 Docker 自带的命令备份具名卷一条命令搞定docker run --rm -v mysql-data:/data -v $(pwd):/backup alpine tar czf /backup/mysql-data-$(date %F).tar.gz -C /data .这个命令的意思是临时用 alpine 镜像起一个容器把mysql-data卷挂载到/data把当前目录挂载到/backup然后在容器里把/data打包成 tar.gz。--rm保证运行完自动删除临时容器。恢复稍微复杂点得先把新卷建出来再解压docker volume create mysql-data-restore docker run --rm -v mysql-data-restore:/data -v $(pwd):/backup alpine tar xzf /backup/mysql-data-2025-01-01.tar.gz -C /data然后在 compose 文件里把mysql-data-restore临时替换成实际卷名重启即可。生产环境强烈建议写个 cron 脚本定时执行备份任务别等到数据没了再后悔。升级容器镜像也需要一点技巧。不要直接在旧容器上改而是docker compose pull docker compose up -dpull只会拉新镜像不会动正在运行的容器up -d时 Compose 发现镜像有更新会先创建新容器再切换流量旧容器自动停掉删除。不过数据库类服务升级前一定要先备份MySQL 大版本升级尤其要谨慎跨版本数据迁移不是简单换镜像就能搞定的。4.3 日志与健康检查别等崩溃才发现问题容器内应用打印的日志如果不加限制会无限膨胀最后把磁盘塞满。Docker 默认的 json-file 日志驱动不做轮转这是我踩过最痛的坑之一某天发现磁盘 100%排查半天才发现是 Nginx 容器积累了上百 GB 的访问日志。全局限制日志大小在/etc/docker/daemon.json里加{ log-driver: json-file, log-opts: { max-size: 50m, max-file: 5 } }这样单个日志文件最大 50MB最多保留 5 个文件超过自动清理。改完重启 Docker 后新建的容器才生效已经运行的容器需要重建。健康检查也是生产环境的重要一环。前面 MySQL 配置里写过了healthcheck这里说说它的用法Compose 里可以用depends_on控制容器启动顺序但它只管“先启动”不管“可用不可用”。比如应用依赖数据库如果数据库还没初始化完就被应用连应用就会报错崩溃。正确姿势是配合condition: service_healthyservices: app: image: myapp:latest depends_on: mysql: condition: service_healthy这样 Compose 会等 MySQL 通过健康检查后才启动 app。这个功能在高版本 Compose 里是开箱即用的别再用sleep 10这种笨办法了。5. 常见问题排查与实践感悟5.1 高频报错速查表报错信息可能原因解决方案Cannot connect to the Docker daemon at unix:///var/run/docker.sock当前用户不在 docker 组或 Docker 服务没启动sudo usermod -aG docker $USER后重新登录sudo systemctl start dockerport is already allocated宿主机端口被占用sudo lsof -i:3306查看占用进程换端口或停掉冲突进程Get https://registry-1.docker.io/v2/: net/http: request canceled镜像拉取被网络阻断配置镜像加速重试docker pullno matching manifest for linux/arm64 in the manifest list entries镜像不支持当前 CPU 架构换 multi-arch 镜像或改用 Raspberry Pi 专用镜像Virtualization support not detectedBIOS 未开启虚拟化或 WSL2 未启用进入 BIOS 开启 VT-x/AMD-V启用 Windows 功能安装 WSL2 内核OCI runtime exec failed: exec failed: unable to start container process容器内命令路径不对检查 healthcheck 或 exec 里的命令是否为容器内存在的绝对路径docker compose命令找不到没有安装 compose 插件Ubuntu 安装docker-compose-plugin或用官方二进制安装有一类比较隐蔽的问题compose.yaml 语法没问题但环境变量没传进去。我遇到过 MySQL 密码一直是默认值的问题排查半天发现是.env文件里的变量名写错Compose 并不会提示这种错误只会默默用空值。建议启动后先进容器验证docker compose exec mysql env docker compose exec redis redis-cli ping容器内环境变量对不对跑一下就知道了。5.2 一些值得记住的小技巧最后分享几个实际项目里攒下来的小技巧。第一.dockerignore文件一定要写。构建 Docker 镜像时Docker 会把构建目录里的所有文件发给守护进程。如果不排除 node_modules、.git、dist 这类大目录构建会慢得想砸电脑更严重的是可能把敏感信息也打进去。在项目根目录建一个node_modules .git .gitignore *.log .env dist第二.env文件管理敏感信息。compose.yaml 里可以直接写MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}然后在同一目录的.env文件里写具体的值。这样 compose.yaml 可以进代码库.env塞进.gitignore密码就不会泄露。第三先用docker compose config做静态校验。这个命令会渲染出最终生效的完整配置任何语法错误、变量缺失都会在这里暴露出来。我习惯在up之前必跑一下这个命令能省掉一半的启动失败。第四清理环境时别直接docker volume prune -f。这个命令会删除所有未被使用的数据卷如果你有不再运行的容器但数据卷里还存着重要数据一起删掉就追悔莫及了。稳妥做法是手动确认再删或者先列出卷列表看清楚再处理。5.3 个人实操中的几个体会这套 Docker Compose 方案我前后用了两三年从最早单个服务手敲 docker run到后来一个中等规模项目用 Compose 管理十几个服务整体的感受是学习曲线其实比想象中平缓真正的门槛不在命令而在理解容器和宿主机之间的边界在哪里。边界理解到位了很多问题自己就能推出来。比如容器为什么重启后数据没了因为数据写在容器可写层里而可写层跟着容器生命周期走。容器为什么时不时连不上数据库因为数据库容器的启动完成时间和外部认为的“容器已启动”不是一回事。这些逻辑想通之后Compose 里的每个字段为什么这么写就有了答案。如果你现在正准备把一个新服务容器化我的建议是别一上来就追求高可用、负载均衡、Kubernetes 那套。先用一个服务、一个 compose 文件把基础链路跑通数据卷、健康检查、日志轮转这些基本功练扎实再逐步往上加东西。这套基础的部署方案已经能覆盖绝大多数中小项目的需求而且后期迁移到 Docker Swarm 或 Kubernetes 时之前写的 compose 配置也能平滑过渡不会白费功夫。最后再分享一个小技巧每套部署写成独立目录里面放 compose.yaml、.env、README.md 和必要的配置目录。这样即使三个月后回来看也能快速回忆起来当时怎么部署的新机器上一拉一跑就能重建整个环境。这个习惯救过我不少次也推荐你试试。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价