发布时间:2026-09-15 12:31 更新时间:2026-09-15 12:31 阅读量:0
单容器跑个 Nginx 或 MySQL 不难,难的是把 Web 应用、数据库、缓存这几类服务组织成一套能一键起停、重启后数据还在、配置不写死在镜像里的组合。Docker Compose 就是干这件事的工具:用一个 YAML 文件描述服务、网络和存储,然后 docker compose up -d 一把拉起。本文按实际部署顺序,把环境变量、数据卷、重启策略这三处最容易踩坑的地方讲透,代码可以直接改成自己的项目。
下面这份示例包含三个服务:Nginx 作为 Web 入口、MySQL 作为数据库、Redis 作为缓存。它同时演示了本文要讲的三个要点,请对照后面的小节逐项理解。
services:
web:
image: nginx:1.27-alpine
ports:
- "8080:80"
environment:
TZ: Asia/Shanghai
APP_ENV: ${APP_ENV}
env_file:
- .env
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./html:/usr/share/nginx/html:ro
depends_on:
- db
- cache
restart: unless-stopped
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
TZ: Asia/Shanghai
volumes:
- db_data:/var/lib/mysql
- ./mysql/init:/docker-entrypoint-initdb.d:ro
restart: unless-stopped
cache:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes", "--save", ""]
volumes:
- redis_data:/data
restart: unless-stopped
volumes:
db_data:
redis_data:
注意两个细节:一是 web 服务没有把配置文件 COPY 进镜像,而是用挂载的方式映射进去,改配置不用重新构建;二是顶层单独声明了 volumes,命名卷必须在这里登记,否则 Compose 会报错。具体字段含义以 Docker 官方 Compose 文件规范为准,不同 Compose 版本关键字略有差异。
环境变量在 Compose 里有三个来源,优先级从低到高大致是:Compose 文件同级目录下自动加载的 .env 文件、env_file 指定的文件、environment 里显式写的键值。理解这个顺序,才能解释「我明明改了 .env 怎么没生效」这类问题。
推荐的做法是:敏感值(数据库密码、API Key)放独立文件,代码只引用变量名。Compose 文件里写 ${MYSQL_ROOT_PASSWORD},同目录建一个 .env:
# .env(不要提交到 git,记得写进 .gitignore)
MYSQL_ROOT_PASSWORD=换成你自己的强密码
MYSQL_DATABASE=appdb
APP_ENV=production
启动前先验证变量是否被正确替换,不要等容器起来才发现连不上数据库:
# 打印 Compose 最终解析出的配置,检查 environment 与 volumes 是否符合预期
docker compose config
只看某个服务的环境变量
docker compose config --format json | grep -A5 MYSQL
几个容易忽略的点:.env 里等号两边不要加空格,值含空格或特殊字符时用引号包起来;变量未定义又没给默认值时,Compose 会给出告警并替换为空字符串,可以用 ${VAR:-默认值} 兜底。另外,容器内的环境变量在 docker inspect 里是明文可见的,真正的密钥管理建议配合 Docker secrets 或外部配置中心,别用环境变量存放长期高敏感凭据。
容器是无状态的,删掉重建是常态,所以数据库、上传目录这类需要留存的数据必须落到卷上。上例用的是命名卷 db_data,Docker 会把它放在宿主机的数据目录下(默认在 /var/lib/docker/volumes/,具体路径以实际安装方式为准)。相比直接绑宿主机目录,命名卷的好处是权限由 Docker 管理,跨平台一致,也不容易被误删。
选择哪种卷,可以按这个思路:配置文件、网站源码这类需要直接用编辑器改的,用绑定挂载(./html:/usr/share/nginx/html);数据库数据、Redis 持久化文件这类由程序自己读写的,用命名卷。MySQL 的 /var/lib/mysql 如果绑到宿主机空目录,初始化会因权限失败,新手优先用命名卷。
重启策略决定容器退出后 Docker 是否自动拉起,常用的几种:no(默认,不重启)、on-failure(非零退出码才重启)、always、unless-stopped(除手动 stop 外都重启)。生产环境一般推荐 unless-stopped:服务器重启后容器自动恢复,而你自己 docker compose stop 掉的不会莫名其妙又起来。
部署完成后,用这几条命令确认状态:
# 启动并后台运行
docker compose up -d
查看服务状态与重启次数(RestartCount 持续增长说明进程在反复崩溃)
docker compose ps
docker inspect --format '{{.Name}} {{.RestartCount}}' $(docker compose ps -q)
查看某个服务日志
docker compose logs -f --tail=100 db
restart 只能让容器反复重启,解决不了启动即失败的问题。如果发现某个容器重启次数不断上涨,先看日志里的报错,常见原因有:数据卷权限不对、端口被占用、依赖的数据库还没就绪。depends_on 只保证启动顺序,不代表被依赖服务已经可用,应用侧要有重试或健康检查。需要更严格的就绪判断,可以给数据库加 healthcheck,再让 web 服务通过 depends_on 的条件写法等待其健康。
最后提醒一句:docker compose down 默认会删除容器和网络但保留命名卷,而 docker compose down -v 会连卷一起删。执行带 -v 的命令前,务必确认数据已经备份,这个参数是新手最容易误操作导致数据全丢的地方。
把这三个点配置到位,一套多容器应用基本就具备了「重启能自愈、数据能留存、配置可切换」的运维基础。下一步可以在此基础上补充健康检查、资源限制(deploy.resources 或 mem_limit)以及日志轮转配置,让整套服务更贴近生产要求。
| 📑 | 📅 |
|---|---|
| Nginx 日志按天切割与过期清理:logrotate 实战 | 2026-09-15 |
| Let's Encrypt 证书自动续期失败排查:日志、80端口校验与 reload 钩子 | 2026-09-13 |
| Docker 磁盘占用过高清理实战:overlay2、容器日志与悬空镜像 | 2026-09-12 |
| Nginx 502/504 排查实战:从 upstream 超时到 php-fpm 进程池 | 2026-09-12 |
| MySQL 出现 Too many connections 怎么办:定位、连接池与超时调优 | 2026-09-11 |
| MySQL慢查询日志开启与优化入门 | 2026-09-15 |
| 服务器定时任务实战:crontab 语法与不生效排查 | 2026-09-15 |
| Nginx 499 状态码排查实战:客户端断连与 upstream 超时的区别 | 2026-09-16 |
| 容器内存超限被 OOM Kill 排查:指标、cgroup 与堆参数对应 | 2026-09-16 |
| MySQL 主从延迟排查:Seconds_Behind_Master 忽高忽低怎么定位 | 2026-09-16 |