☰
一条命令起全套开发环境:Docker Compose 从零到能用的完整教程
2026/10/1 20:04:40 网站建设 项目流程

MySQL 版本不对、Redis 端口被占、Node 版本和 package-lock 打架,中间还重装了一次 Python。

这种事我经历过太多次了。后来我们团队的做法是:不给文档,给一份compose.yaml。新人git clone完,敲一条命令,环境就有了。

这篇就把这套东西从零搭一遍。用的是 Docker Compose v2 的最新写法,包含几个大多数人没用过但确实省事的功能。

环境准备

只需要装 Docker Desktop(或者 Docker Engine + Compose v2 插件),别的什么都不用。

docker--version# 24.x 以上dockercompose version# v2.x,注意是 "docker compose",中间是空格

有个小细节:docker-compose(带横杠)是 v1,早就停止维护了。如果你敲出来是这个,说明装的是老的 Python 版,建议换掉。

第一步:写 compose.yaml

先说一个很多人不知道的变化——version这个顶层字段已经废弃了。新的标准文件名是compose.yaml(其次是compose.yml),docker-compose.yml属于旧命名。

# compose.yamlservices:api:build:context:.target:develop# 多阶段构建的 dev 阶段ports:-"8000:8000"environment:DATABASE_URL:postgresql+asyncpg://app:secret@db:5432/appdbREDIS_URL:redis://cache:6379/0depends_on:db:condition:service_healthy# 等健康检查通过再启动,不是等容器起来cache:condition:service_starteddevelop:watch:-action:syncpath:./apptarget:/app/app-action:rebuildpath:./pyproject.tomldb:image:postgres:17-alpineenvironment:POSTGRES_USER:appPOSTGRES_PASSWORD:secretPOSTGRES_DB:appdbvolumes:-db_data:/var/lib/postgresql/dataports:-"5432:5432"# 暴露出来,本地 IDE 能直连healthcheck:test:["CMD-SHELL","pg_isready -U app -d appdb"]interval:5stimeout:3sretries:5start_period:10scache:image:redis:8-alpinevolumes:-cache_data:/datavolumes:db_data:cache_data:

depends_on里的condition: service_healthy是重点。默认的depends_on只保证容器启动顺序,不保证里面的服务真的能用了。Postgres 容器起来后要几秒才接受连接,如果 api 抢跑,就会报连接拒绝——很多人第一次用 Compose 都被这个坑过。

第二步:写多阶段 Dockerfile

开发和生产用同一个 Dockerfile,靠target区分:

FROM python:3.13-slim AS base ENV PYTHONUNBUFFERED=1 PIP_NO_CACHE_DIR=1 WORKDIR /app COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv # ---- 开发阶段 ---- FROM base AS develop COPY pyproject.toml uv.lock ./ RUN uv sync --frozen COPY . . CMD ["uv", "run", "uvicorn", "app.main:app", "--reload", "--host", "0.0.0.0", "--port", "8000"] # ---- 生产阶段 ---- FROM base AS prod COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-dev COPY . . USER 1001 CMD ["uv", "run", "gunicorn", "app.main:app", "-k", "uvicorn.workers.UvicornWorker", "-w", "4", "-b", "0.0.0.0:8000"]

开发阶段带--reload和不带--no-dev,生产阶段反过来。这两个阶段分开写,比在 compose 里用command覆盖更清楚。

第三步:用 watch 告别手动 build

Compose v2.22 起有了develop.watch,v2.23 加了sync+restart,v2.32 又加了restart和sync+exec。它有三种动作:

action行为什么时候用
sync只同步文件,不重启应用自己有热重载(uvicorn --reload、nodemon)
sync+restart同步后重启容器改配置需要重启才生效,比如 nginx.conf
rebuild重新构建镜像并替换容器依赖清单变了,pyproject.toml/package.json
dockercomposewatch# 或者直接dockercompose up--watch

它会监控你配的路径,改代码就同步进去,uvicorn 自己热重载;改了依赖清单就自动 rebuild。日常用下来,基本告别了手动build那三分钟。

有个坑要注意:watch 的 sync 需要镜像里有stat、mkdir、rmdir三个命令,而且容器里的用户必须对目标路径有写权限。如果用非 root 用户,Dockerfile 里COPY记得加--chown,否则同步会静默失败:

COPY --chown=app:app . /app

第四步:profiles 区分环境

单机开发时你可能还想开个 pgAdmin、看看 Prometheus 面板,但这些不该是所有人默认启动的。用 profiles:

pgadmin:image:dpage/pgadmin4:latestprofiles:["debug"]ports:-"5050:80"prometheus:image:prom/prometheus:latestprofiles:["monitoring"]ports:-"9090:9090"
dockercompose up-d# 只有基础服务dockercompose--profiledebug up-d# 加 pgAdmindockercompose--profiledebug--profilemonitoring up-d

没写profiles的服务永远启动,写了 profile 的只在激活时才起。比维护三四份 compose 文件清爽得多。

第五步:别再明文写密码

见过太多人的 compose 文件里直接写MYSQL_ROOT_PASSWORD: 123456,然后连文件一起提交到 Git。

Compose 支持 secrets:

services:db:image:postgres:17-alpineenvironment:POSTGRES_PASSWORD_FILE:/run/secrets/db_password# 注意是 _FILE 后缀secrets:-db_passwordsecrets:db_password:file:./secrets/db_password.txt

注意 Postgres 用的是POSTGRES_PASSWORD_FILE这种带_FILE后缀的变量,不是直接把值传进去。这是镜像本身的约定,不是 Compose 的语法,换别的镜像要查一下它支持不支持。

第六步:日志和信号也别漏

本地开发环境跑久了,最常见的诡异问题不是服务挂了,是磁盘满了。Docker 默认的json-file日志驱动不带轮转,一个疯狂打日志的服务,几天就能写掉几十 GB。

给服务加上轮转配置:

services:api:logging:driver:json-fileoptions:max-size:"10m"max-file:"3"

另外两个值得加的小配置:

api:stop_grace_period:30s# docker compose down 时给它 30 秒优雅退出init:true# 用 tini 做 1 号进程,正确回收僵尸子进程

init: true在跑 shell 脚本、或者应用会 fork 子进程时很有用。容器里的 1 号进程如果不会wait()回收子进程,跑久了会攒一堆僵尸进程占着 PID。加这一行就够了,Compose 会自动注入一个轻量的 init。

常用命令

dockercompose up-d# 起dockercomposeps# 看状态dockercompose logs-fapi# 跟日志dockercomposeexecapibash# 进容器dockercompose up-d--build# 重建dockercompose config# 校验并打印合并后的配置dockercompose down-v# 停掉并删数据卷

最后那条down -v慎用,-v会连数据卷一起删掉,数据库就清空了。想重置环境时很好用,但手滑一次就得重来。

docker compose config这条值得养成习惯:它会把你写的文件、.env、以及include进来的所有内容合并展开,打印最终生效的配置。遇到"我明明改了配置怎么没生效",先跑这条看一眼。

几个我实际踩过的坑

1. 容器里的 localhost 不是你的电脑。在容器里连localhost:5432一定失败,因为那是容器自己。要连宿主机的服务,用host.docker.internal(Mac/Windows);Linux 上这个域名默认不存在,得加extra_hosts: - "host.docker.internal:host-gateway"。

2. bind mount 在 Mac/Windows 上很慢。挂一个几万文件的node_modules进去,文件 IO 能慢到怀疑人生。解决方案就是用develop.watch的 sync 代替 bind mount,或者把依赖目录用匿名卷屏蔽掉:

volumes:-./app:/app-/app/node_modules# 匿名卷,让容器内自己维护

3. 改了配置忘了重载。Compose 里改compose.yaml后,部分字段(比如 ports、environment)需要docker compose up -d重新创建容器才生效,光restart不行。改完记得up -d,或者up -d --force-recreate。

最后

这套东西的价值不在于"用了 Docker",在于环境变成了代码。新人来了不用问任何人,docker compose up -d就完事;换台电脑不用重新配一遍;线上出问题排查时,本地能和线上跑同一套依赖版本。

我们团队现在的标准是:README 里超过三行的环境配置说明,一律改成一份 compose 文件。环境配置是文档最容易过期的地方,写成代码就不会了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询