ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

【Web全栈进阶】Docker Compose:一键起全栈

【Web全栈进阶】Docker Compose:一键起全栈

早报站要真正“可交付”。现在起一套全栈要5条命令、关注启动顺序、操心环境变量——本篇之后:一条docker compose up,PG + Redis + 后端 + 前端全部就位。
从“我机器上能跑”到“任何机器一条命令能跑”,这是部署的起点,也是开篇词“能上线”四件事的最后一坎。

🎯本篇产出:后端/前端Dockerfile + docker-compose.yml(健康检查、依赖顺序、卷持久化)+ 常用编排命令。含代码约130行。


📌 太长不看版(给想快速上手的你)

项目信息一句话说明
本篇目标用Docker Compose一键拉起全栈
代码行数~130行(Dockerfile + compose)
依赖Docker Desktop / Docker Engine + Compose v2+
核心功能四服务编排 + 健康检查 + 依赖顺序 + 卷持久化
跑起来的命令docker compose up -d --build
核心知识点服务名即主机名、depends_on + healthcheck、volume
做完你能得到任何机器一条命令能跑的全栈应用

一、概念课:Compose在解决什么

手工起全栈的痛点(现在的你正在经历):起PostgreSQL → 起Redis → 装依赖 → 跑迁移 → 起uvicorn → 起npm dev——6条命令,顺序不能错,环境变量散在三个终端里,换个机器全部重来。

Compose的做法:把“有哪些服务、谁依赖谁、环境变量是什么、数据存哪里”写进一个compose.yaml(声明式配置),一条命令按依赖顺序全部拉起。

这就是**服务编排(orchestration)**的入门——所有容器编排工具(K8s是重型版)解决的都是同一件事。

📋 四个术语一次认清

术语含义类比
image(镜像)只读模板,含运行时与依赖光盘
container(容器)镜像的运行实例用光盘开的机
service(服务)compose里定义的容器+配置一个机组
volume(卷)容器外挂的持久化目录外接硬盘

🆕 关于version字段(重要)

🚨version:字段已废弃。
Compose v2+不再需要它,写了反而会收到警告:the attribute version is obsolete, it will be ignored。新写法直接以services:开头,本篇的compose.yaml就是正确姿势。


二、第0步:两个Dockerfile

🔧 Dockerfile.backend

FROM python:3.13-slim WORKDIR /app # pip 走国内镜像源加速(非国内网络可删除这行) ENV PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple # 依赖先行单独一层:代码改动不用重装依赖 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 后端运行所需的目录逐个拷(见 .dockerignore 排除大目录) COPY alembic.ini ./ COPY app ./app COPY core ./core COPY collector ./collector COPY migrations ./migrations COPY main.py . EXPOSE 8000 CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

🔧 frontend/Dockerfile(开发模式,生产Nginx版后续篇章)

FROM node:24-alpine WORKDIR /app COPY package*.json . RUN npm install COPY . . CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]

📌 三个细节

#细节说明
①WORKDIR固定工作目录
②依赖先COPY再装利用Docker层缓存——改代码不用重装依赖
③--host 0.0.0.0让容器外可访问(默认只听localhost)

💡镜像版本选择:python:3.13-slim是当前稳定Python线,node:24-alpine是Active LTS(Node 26将在2026年10月28日进入Active LTS,届时可升级)。


三、第1步:compose.yaml——核心编排文件

compose.yaml(项目根,注意没有version:字段):

services:db:image:postgres:18-alpineenvironment:POSTGRES_USER:postgresPOSTGRES_PASSWORD:postgresPOSTGRES_DB:dailyvolumes:-pgdata:/var/lib/postgresql# 数据放卷里,容器删了数据还在healthcheck:# 健康检查:pg_isready才算"就绪"test:["CMD-SHELL","pg_isready -U postgres -d daily"]interval:5stimeout:5sretries:10start_period:10s# 给PG启动留缓冲,避免初期误判redis:image:redis:8-alpinehealthcheck:test:["CMD","redis-cli","ping"]interval:5stimeout:3sretries:5backend:build:./backendenvironment:DATABASE_URL:postgresql+psycopg://postgres:postgres@db:5432/dailyREDIS_URL:redis://redis:6379/0# 主机名就是服务名!SECRET_KEY:${SECRET_KEY:-dev-secret}DEEPSEEK_API_KEY:${DEEPSEEK_API_KEY}depends_on:db:condition:service_healthy# 等db健康再启动redis:condition:service_healthy# 等redis健康再启动ports:["8000:8000"]volumes:-uploads:/app/uploads# 上传的封面图持久化frontend:build:./frontendports:["5173:5173"]volumes:pgdata:

🎯 四个关键设计(全是新手必踩的坑)

#设计为什么重要
①服务名即主机名容器网络里backend访问db用db,不是localhost——同一网络内的服务通过名字互相访问
②depends_on + healthcheck双保险只写depends_on不够(它只管启动顺序,不管“就绪”)——db的健康检查通过后backend才启动,否则后端一启动就报“连接被拒”
③volume持久化pgdata卷挂在PG数据目录——docker compose down后数据还在;down -v才会删(第八节⑤)
④环境变量注入DATABASE_URL从本地开发的localhost换成db——同一份配置,宿主机视角和容器视角的“地址”不一样(第八节③)

💡start_period的作用:给PG启动留缓冲时间,在这段时间内健康检查失败不计数,避免PG还没起来就被判定为“不健康”。


四、第2步:迁移与种子数据自动化

容器启动后要跑Alembic迁移,再灌种子数据。backend服务的command覆盖:

backend:...command:>sh -c "alembic upgrade head && python seed.py && uvicorn app.main:app --host 0.0.0.0 --port 8000"

启动顺序变成:建表 → 种子 → 起服务。Alembic的env.py从settings读DATABASE_URL,容器里注入的环境变量自动生效——迁移配置复用,零改动。

🆕Compose v5.3+的新选择:如果你用的是较新的Compose(v5.3.0,2026年7月发布),可以用**pre_start生命周期**替代“sh -c串联”——它原生支持“主容器启动前先跑一个短命容器”,比用depends_on service_completed_successfully更干净。入门阶段先用sh -c串联足够,知道有这个方向即可。


五、第3步:一键起 + 常用命令

dockercompose up-d--build# 构建并后台启动(-d不占终端)dockercomposeps# 看四个服务的状态dockercompose logs-fbackend# 跟后端日志dockercompose down# 停止(保留数据卷)dockercompose down-v# 停止并删除数据卷(⚠️ 数据没了)

🌐 浏览器访问

端地址说明
前端http://127.0.0.1:5173页面正常加载
后端APIhttp://127.0.0.1:8000/api前端调用的地址

⚠️注意5173页面的API请求依然是127.0.0.1:8000——因为浏览器在宿主机上,它看到的“地址”是宿主机视角;只有容器内互相访问时才用服务名。两个视角别混(第七节小结)。


六、验收清单

1. 一条命令:docker compose up-d--build→ 四个服务全部Running2.dockercompose ps显示db / redis / backend / frontend都在,且db/redis为healthy3. 浏览器打开http://127.0.0.1:5173 → 首页正常加载、登录、收藏全通4.dockercompose down → 再docker compose up -d(不带--build)→ 数据还在(pgdata卷生效)5. 把本地已装的PG/Redis停掉,全栈依然工作(全部在容器里)6. 换台“干净机器”(或删除所有旧容器)重跑第1步 → 依然全绿7. 全程无报错后提交Git
gitadd.gitcommit-m"Docker Compose:一键起全栈(PG+Redis+后端+前端)"

七、常见报错:这6个,Compose上手的标配(重点!)

①service backend depends on db but db has no healthcheck

🔍 原因:depends_on里写了condition: service_healthy,但db服务没配healthcheck,或格式写错。

✅ 解法:给db补上healthcheck(第3节写法),test用pg_isready最稳。


② backend一启动就报could not connect to server: Connection refused

🔍 原因:两个可能——

  • ①DATABASE_URL里写的是localhost(容器内没有PG!);
  • ②depends_on没配condition,backend比db先起。

✅ 解法:地址用服务名db;depends_on加condition: service_healthy。


③port is already allocated(5432已被占用)

🔍 原因:宿主机本地也装了PostgreSQL,端口冲突。

✅ 解法:映射改宿主侧端口5433:5432(容器内不变)——宿主端口冲突改冒号左边,服务名不用动。


④ 前端页面能开,但所有API请求失败

🔍 原因:前端代码里API地址写死了localhost且端口没映射 / 或vite proxy指向了容器内才存在的地址。

✅ 解法:浏览器视角用127.0.0.1:8000(映射过即可);容器视角才用服务名——两个视角的地址表,写代码时贴在显示器上。


⑤docker compose down -v之后数据全没了

🔍 原因:-v会删除声明过的卷——PG数据、上传的图片,全没。

✅ 解法:把down -v当“格式化硬盘”看待,确认要重建才用;日常一律down(不带-v)。


⑥ 改了代码但容器里还是旧版本

🔍 原因:镜像没重新构建(up -d不重建);或后端没挂载代码卷(改了不生效)。

✅ 解法:代码变更 →docker compose up -d --build(重新构建镜像);或开发期用volume挂载源码(bind mount)。


八、课后练习

#练习难度提示
1补全健康检查:给backend也加healthcheck⭐⭐curl -f localhost:8000/docs
2.env化敏感配置:把SECRET_KEY等接入.env文件⭐⭐compose自动读取,且.env进gitignore
3Makefile化命令:写一个Makefile封装make up/make down/make logs⭐⭐⭐团队里“连docker命令都不用记”
4(选做)bind mount开发:把backend源码挂载进容器(- ./backend:/app)⭐⭐⭐体验“改代码即热更新”的开发模式

📦 配套代码

两个Dockerfile + compose.yaml + 种子脚本已上传Git(python_daily/):【gitee仓库地址】

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进