前段时间帮同事看一个 docker compose up -d 起不来的项目前后折腾了大半天最后发现是 YAML 里混了一个 Tab。借这个机会把这两年用 Compose 踩过的坑整理一下顺便把常用的模板存个档下次直接抄。先说一个前提本文用的是Compose v2命令是 docker compose中间空格v1 的 docker-compose连字符已经在 2023 年停止维护网上老教程里那种 version: 3.8 开头的写法在新版会打警告不再推荐。环境项目版本Docker Engine24.0.7Docker Composev2.24.5OSUbuntu 22.04 / Windows 11 Docker Desktop 4.29查看自己版本docker compose version # Docker Compose version v2.24.5先讲一个万能习惯写完先 config不管写多短的 compose.yml启动前先跑一次docker compose config这条命令做两件事校验 YAML 语法缩进、Tab、引号错乱都会在这里报出来还会指出具体行号展开所有变量插值和 extends 继承输出的是 Compose 实际会使用的最终配置。比如你写了 ${DB_PASSWORD}config 会告诉你这个变量最终被替换成了什么或者是不是空字符串——很多配置看着没问题但服务起不来的情况跑一次 config 就露馅了。如果报错输出大概长这样yaml: line 12: found character that cannot start any tokenline 12 就是出错行号直接跳过去改。坑 1YAML 缩进这是新手 90% 的第一个坑。YAML 有几个硬规则只能用空格不能用 Tab——用 Tab 直接 found character that cannot start any token同一层级缩进数必须一致惯例是2 空格冒号后面必须有空格image: nginx ✅image:nginx ❌列表项 - 后面也要空格错误示范services: nginx: image: nginx ports: # ← 这一行是 Tab - 8080:80正确写法services: nginx: image: nginx ports: - 8080:80VSCode 装个 YAML 插件Red Hat 出的那个底部状态栏能切空格/Tab缩进错误也会实时高亮比事后 config 校验省事。坑 2字段名拼错Compose 对未知字段是静默忽略的除非开了严格模式拼错了不报错但配置不生效。最常见的错写正确volumevolumesenviromentenvironmentportportscontainer_namecontainer_name这个反而经常被写成 containernamedepend_ondepends_on排查方法还是 docker compose config展开后的输出会告诉你 Compose实际读到了什么。如果你写了 enviroment展开的输出里就找不到对应的环境变量一眼看穿。坑 3挂载路径绑定挂载bind mount路径踩坑分平台Linux / Mac相对路径以 compose.yml所在目录为基准不是你执行命令的目录。services: nginx: volumes: - ./conf/nginx.conf:/etc/nginx/nginx.conf:ro - ./logs:/var/log/nginx:ro 表示只读挂载配置文件建议都加防止容器内进程误改宿主机文件。Windows路径分隔符推荐用正斜杠 /反斜杠在 YAML 里要转义很麻烦。volumes: - D:/projects/myapp/conf:/etc/nginx/conf:ro # ✅ # - D:\projects\myapp\conf:/etc/nginx/conf # ❌ 反斜杠容易被吃掉Docker Desktop 还要注意挂载的盘符必须在 Settings → Resources → File Sharing 里共享过否则报 Mount denied。Windows 11 上 C 盘默认共享D/E 盘要手动加。排查挂载失败docker compose logs nginx # 或者进容器看 docker compose exec nginx ls -la /etc/nginx/如果宿主机文件没挂进来容器里看到的是镜像自带的默认文件不是你的配置。坑 4depends_on 只等启动不等就绪这个是原文提到但没给解法的坑展开讲。depends_on 的默认行为是等被依赖的容器进入 running 状态就返回不管服务本身有没有准备好接受连接。MySQL 容器启动到能接受 TCP 连接中间有 10~30 秒的初始化时间这段时间 Web 服务连过去就是 Connection refused。错误示范services: web: image: myapp depends_on: - mysql mysql: image: mysql:8启动顺序是 mysql 先起但 web 起来的时候 mysql 还没监听 3306应用初始化连接池直接崩。正确写法推荐用 healthcheck condition: service_healthyservices: mysql: image: mysql:8 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: app healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -uroot, -p${MYSQL_ROOT_PASSWORD}] interval: 5s timeout: 3s retries: 20 start_period: 30s volumes: - mysql_data:/var/lib/mysql web: image: myapp depends_on: mysql: condition: service_healthystart_period: 30s 是给 MySQL 初始化的宽限期这段时间内 healthcheck 失败不算 unhealthy。retries: 20 interval: 5s 意味着最多等 100 秒够用。注意condition: service_healthy 是 Compose v2.1 支持的语法v1 的 docker-compose 老版本可能不认报 depends_on contains an invalid type。坑 5环境变量 ${VAR} 与 shell 变量混淆Compose 里 ${VAR} 有两层解析很多人分不清第一层Compose 插值——发生在读取 yml 时Compose 从宿主机 shell 环境或同目录下的 .env 文件里取值替换到 yml 里。services: mysql: environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PW} # ← Compose 会从宿主机/.env 取 DB_ROOT_PW第二层容器内 shell 解析——如果值里带了 $$两个美元符Compose 会转义成单个 $最终传给容器里的 shell 去解析。services: app: command: bash -c echo $$HOSTNAME # ← 打印容器内的 HOSTNAME不是宿主机的写一个 $ 就是 Compose 插值写两个 $$ 就是留给容器内 shell——这个区别踩过一次就记一辈子。推荐做法敏感配置全放 .env同目录Compose 自动读并且 .env 加进 .gitignore# .env DB_ROOT_PWs3cret DB_USERapp DB_NAMEapp # .gitignore .env提交一个 .env.example 到仓库写明需要哪些变量别人 clone 下来复制成 .env 自己填。坑 6匿名卷导致重启丢数据services: mysql: image: mysql:8 volumes: - /var/lib/mysql # ← 匿名卷这种写法叫匿名卷Compose 会创建一个随机名字的 Docker volume。问题在于docker compose down不会删匿名卷默认行为docker compose down -v会删所有卷包括匿名的你换台机器部署或者 down -v 一次数据全没正确做法显式声明命名卷services: mysql: image: mysql:8 volumes: - mysql_data:/var/lib/mysql # ← 命名卷 volumes: mysql_data: # ← 顶层声明命名卷有固定名字down 不会删docker volume ls 能看到方便备份和迁移。完整可运行模板nginx mysql redis下面这份是我常用的模板直接 docker compose up -d 就能起包含了上面所有的最佳实践# compose.yml Compose v2不需要 version 字段 services: nginx: image: nginx:1.25-alpine container_name: nginx ports: - 80:80 - 443:443 volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./nginx/logs:/var/log/nginx - ./nginx/html:/usr/share/nginx/html:ro depends_on: - app restart: unless-stopped networks: - frontend - backend app: image: myapp:latest container_name: app environment: DB_HOST: mysql DB_PORT: 3306 DB_USER: ${DB_USER} DB_PASSWORD: ${DB_PASSWORD} DB_NAME: ${DB_NAME} REDIS_HOST: redis REDIS_PORT: 6379 depends_on: mysql: condition: service_healthy redis: condition: service_healthy restart: unless-stopped networks: - backend mysql: image: mysql:8.0 container_name: mysql environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MYSQL_DATABASE: ${DB_NAME} MYSQL_USER: ${DB_USER} MYSQL_PASSWORD: ${DB_PASSWORD} TZ: Asia/Shanghai command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci - --default-authentication-pluginmysql_native_password volumes: - mysql_data:/var/lib/mysql - ./mysql/init:/docker-entrypoint-initdb.d:ro # 首次启动执行的 SQL healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -uroot, -p${DB_ROOT_PASSWORD}] interval: 5s timeout: 3s retries: 20 start_period: 30s restart: unless-stopped networks: - backend redis: image: redis:7-alpine container_name: redis command: [redis-server, --requirepass, ${REDIS_PASSWORD}, --appendonly, yes] volumes: - redis_data:/data healthcheck: test: [CMD, redis-cli, -a, ${REDIS_PASSWORD}, ping] interval: 5s timeout: 3s retries: 10 restart: unless-stopped networks: - backend volumes: mysql_data: redis_data: networks: frontend: driver: bridge backend: driver: bridge internal: true # backend 网络不通外网只有 nginx 能进出配套的 .envDB_ROOT_PASSWORDChangeMe_Root_2026 DB_USERapp DB_PASSWORDChangeMe_App_2026 DB_NAMEapp REDIS_PASSWORDChangeMe_Redis_2026这份模板里几个细节值得单独说两个网络分离frontend 让 nginx 对外backend 设了 internal: truemysql/redis 只在内部网络里宿主机端口不暴露安全性直接提升一档restart: unless-stopped宿主机重启后容器自动拉起除非你手动 stop 过。比 always 更符合直觉TZ: Asia/Shanghai不设时区容器内默认 UTC日志时间戳会差 8 小时查问题很痛苦MySQL 初始化脚本目录/docker-entrypoint-initdb.d 里的 .sql、.sh 会在数据卷为空时首次启动执行用来建表灌初始数据很方便。注意如果 mysql_data 卷已经有数据脚本不会重跑排查工具速查现象命令看某个服务日志docker compose logs -f --tail200 mysql进容器排查docker compose exec mysql bash看容器状态docker compose ps看容器详细信息docker inspect container_id重启单个服务docker compose restart app重建单个服务docker compose up -d --build app停止并清理保留数据卷docker compose down停止并清理含数据卷docker compose down -v ⚠️ 慎用down -v 那个 -v 一按命名卷全删生产环境执行前三思。小结Compose 出问题基本就三类YAML 语法缩进、拼写、路径/变量解析挂载、${}、.env、服务时序depends_on 不等于就绪。养成三个习惯能规避 80% 的坑写完先 docker compose config 校验敏感值放 .env 不入库有状态服务必写 healthcheck依赖方用 condition: service_healthy上面那份 nginxmysqlredis 模板我自己用了两年改改环境变量和挂载路径就能套到新项目上需要的直接抄。
企业数字化 ERP 产品动态
相关推荐
证据感知信用分配:sprix-sage-router如何从执行结果中学习并越用越聪明 证据感知信用分配:sprix-sage-router如何从执行结果中学习并越用越聪明 【免费下载链接】sprix-sage-router Sprix AI at 屿智同行 — state-aware SELF/COLLABORATE/HANDOFF routing for A2A agent networks. 项目地址: https://gitcode.com/gh_mirrors/sp/sprix… · 2026/9/25 21:07:49
STM32硬件SPI驱动W25Q64:跨页写入拆分、WEL自动清零陷阱与擦写时序实测验证 文章目录 摘要 前言 一、NOR Flash 的物理规则:所有问题的源头 1.1 存储结构与操作单位 1.2 位只能从 1 写成 0 1.3 WEL 写使能锁存器:每条指令只生效一次 1.4 一次页编程的完整时序 1.5 方案级决策:为什么选 SPI Flash 而不是别的 二、SPI 模式与片选:两个必须想清楚的配置… · 2026/9/25 21:07:49
C+java排序 主要核心有7个排序一、直接插入排序(时间复杂度O(N^2) 空间复杂度O(1) 稳定排序)
核心是将数组分为两个区间,有序区间和待排序区间,每次拿待排序区间第一个元素跟已排序区间从后往前比较大于才进行后移操作(保证稳定性)
Java实现&… · 2026/9/25 21:07:43
UE5 GAS框架实战:构建可扩展ARPG战斗系统的核心方法 1. 先别急着写代码:ARPG战斗框架到底在解决什么问题如果你在UE里做过ARPG,一定体会过战斗系统写到后期的那种窒息感。普攻连段、闪避无敌帧、受击硬直、怪物AI、伤害数字、BUFF叠层、技能打断、镜头冲击……表面上每个功能都不难,但一旦它们互… · 2026/9/26 0:52:20
贵州正规的除尘器加工厂哪家靠谱?株洲菲尔特生产厂靠谱商家测评排名 株洲菲尔特科技有限公司简介株洲菲尔特科技有限公司是一家专注工业干式除尘成套设备及非标成套装备研发、制造、安装全流程服务的企业,业务覆盖方案设计、粉尘收集系统搭建、风管配套、设备生产、安装调试到第三方检测的全链条服务,可针对工矿、矿山、建… · 2026/9/26 0:52:08
免费金融数据接口选型与Python接入实操:A股、美股、期货、外汇、数字货币全覆盖 1. 选型:免费数据源全景对比与选择逻辑1.1 为什么说免费金融数据接口是刚需先说个现实问题:很多人一接触量化交易或者个人投资分析,第一反应就是去找Wind、Bloomberg这类专业终端。Wind的Python接口确实好用,但一个账号一年几万块… · 2026/9/26 0:51:43
磁力搜索与下载工具全解析:从DHT原理到下载器调优实战 1. 磁力搜索与下载工具的核心逻辑拆解1.1 磁力链接到底是什么,为什么它比传统下载更抗封很多人第一次接触磁力搜索,脑子里冒出来的问题是:这东西跟普通下载链接有啥区别?我用一句话说清楚——磁力链接不存文件本身,它存… · 2026/9/26 0:51:37
MySQL图书管理系统实战:从表设计到事务、索引与主从复制 简介:这份 MySQL 图书管理系统资源面向高校数据库课程期末大作业场景,适合正在学习 MySQL 表设计、权限管理与存储过程/触发器的学生参考。包内共 19 个文件,以 12 个 frm 表结构文件、2 个 trn 触发器文件、1 个 trg 触发器定义、1 个 opt 配… · 2026/9/26 0:51:37
日本地铁为什么准点?信号、供电与调度系统的协同设计 如果问你,世界上哪个城市的地铁最能让人形成“准点”肌肉记忆,很多人会脱口而出东京。但真正值得追问的是:东京地铁早高峰的发车间隔已经被压到3分钟以内,有些拥挤区段甚至接近2分钟,列车和信号设备还是一套不断叠加了… · 2026/9/26 0:51:37
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46