Docker Compose 本地开发环境清理:容器能启动,不代表环境是干净的
问题通常不是“起不来”
很多 Docker Compose 问题看起来都很玄。明明 docker compose up -d 没报错,容器也都是 running,应用却连不上 MySQL;改了初始化 SQL,重启十次还是旧表结构;Redis 密码换了,程序日志里还在报旧密码认证失败。
我刚开始用 Compose 搭本地环境时,也把“容器启动成功”当成“环境启动成功”。后来才发现,这是两个概念。容器能跑,只说明进程没立刻退出;服务是否可用、配置是否生效、数据卷里有没有历史包袱,还是要另外检查。
这篇不是 Docker 命令大全,只整理我在本地开发里最常遇到的几类脏环境问题,以及我现在会怎么处理。
先看一个常见 compose 文件
假设项目依赖 MySQL 和 Redis:
services:
mysql:
image: mysql:8.0
container_name: demo-mysql
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: demo
MYSQL_USER: demo
MYSQL_PASSWORD: demo123
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-proot123"]
interval: 5s
timeout: 3s
retries: 20
redis:
image: redis:7
container_name: demo-redis
command: ["redis-server", "--requirepass", "redis123"]
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "-a", "redis123", "ping"]
interval: 5s
timeout: 3s
retries: 20
volumes:
mysql_data:
这个文件不复杂,但已经埋了几个容易误判的点。
初始化 SQL 为什么没重新执行
MySQL 官方镜像只会在数据目录为空时执行 /docker-entrypoint-initdb.d/ 下的脚本。也就是说,第一次启动之后,mysql_data 这个 volume 里已经有数据了。你再改 init.sql,然后重启容器,MySQL 不会重新初始化。
所以改初始化脚本后,我会明确选择一种处理方式。
如果只是想保留数据,手动执行迁移脚本:
docker exec -i demo-mysql mysql -uroot -proot123 demo < sql/patch.sql
如果本地数据可以丢,直接删掉 volume:
docker compose down -v
docker compose up -d
这里的 -v 很关键。只执行 docker compose down 会删容器和网络,但不会删命名 volume。旧数据还在,旧问题也还在。
我一般不把这条命令写进一键启动脚本里,因为它会清数据。更稳妥的做法是单独写一个 reset-local-env.ps1 或 reset-local-env.sh,名字里把 reset 写清楚。
服务互联别写 localhost
容器里的 localhost 指的是容器自己,不是你的宿主机,也不是另一个容器。这个坑太常见了。
如果应用也跑在 Compose 网络里,连接 MySQL 应该写服务名:
spring:
datasource:
url: jdbc:mysql://mysql:3306/demo?useSSL=false&allowPublicKeyRetrieval=true
username: demo
password: demo123
连接 Redis 也一样:
spring:
data:
redis:
host: redis
port: 6379
password: redis123
如果应用跑在宿主机,才用 localhost:3306、localhost:6379。这两个场景要分清楚。很多“我本地能连,容器里不能连”的问题,最后都落在这里。
depends_on 不是健康检查
不少人会这样写:
services:
app:
depends_on:
- mysql
- redis
这只能保证 mysql、redis 容器先启动,不保证服务已经准备好。MySQL 第一次初始化时可能要十几秒,应用抢先连接,启动失败并不奇怪。
Compose 新版本可以配合 healthcheck 使用:
services:
app:
build: .
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
但我还是建议应用自己也做重试。基础设施脚本只能减少问题,不能替应用兜底。尤其是 Java、Go 这类长期运行服务,数据库短暂不可用时直接退出,体验很差。
端口冲突别靠猜
本地经常已经装了 MySQL,Compose 又映射 3306:3306,启动时可能直接失败,也可能你连到的根本不是容器里的库。
我现在更喜欢给项目环境换一个不常用端口:
ports:
- "13306:3306"
宿主机连接就用:
mysql -h 127.0.0.1 -P 13306 -udemo -pdemo123 demo
容器内服务互联仍然用 mysql:3306。宿主机端口和容器内端口不是一回事,写清楚能少很多误会。
我常用的排查顺序
容器启动后,我不会只看 docker ps。一般按这个顺序走:
docker compose ps
docker compose logs mysql --tail=80
docker compose logs redis --tail=80
docker inspect demo-mysql --format='{{json .State.Health}}'
docker volume ls | grep mysql
如果怀疑应用连错地址,再进容器里测网络:
docker compose exec app sh
nc -vz mysql 3306
nc -vz redis 6379
没有 nc 的镜像可以临时起一个调试容器:
docker run --rm -it --network demo_default nicolaka/netshoot
网络名可以通过 docker network ls 看。别凭感觉改配置,先确认请求到底有没有打到目标服务。
给团队留一份说明
本地环境最怕每个人靠记忆操作。我的做法是在仓库里放一个很短的 docs/local-env.md,只写四件事:
• 第一次启动命令;
• 如何查看 MySQL、Redis 连接信息;
• 如何重置本地数据;
• 常见错误对应的排查命令。
不需要写成教程,能让新同事少问两次就够了。
小结
Compose 的价值是把环境变得可复制,但它不会自动让环境变干净。命名 volume、服务名、healthcheck、端口映射,这几个点只要混在一起,问题就会变得很像“玄学”。
我的经验是:先承认本地环境会脏,再给它设计清理方式。能重置、能检查、能解释,比单纯“能启动”更重要。
更多推荐




所有评论(0)