
如何理解 docker-compose.yml可以把 Docker Compose 想成一份「一键启动清单」。不用分别docker run三次写在一个 YAML 里一条命令就能同时拉起 PostgreSQL、Redis 和 Node API。dockercompose up-d# 启动dockercompose down# 停止并删除容器pgdata 卷里的数据还在dockercomposeps# 看状态含 healthydockercompose logs postgres# 看某个服务的日志整体结构docker-compose.yml ├── services ← 要跑哪些容器 │ ├── postgres ← 数据库 │ ├── redis ← 缓存 │ └── api ← Node API └── volumes ← 声明持久化存储给 postgres 用Compose 会自动给三个容器建一张默认虚拟网络容器之间用服务名互访如postgres:5432、redis:6379。api在容器内连 PG/Redis 用服务名在宿主机上执行 migrate 或本地npm run dev时仍用localhost 下面配置的映射端口。服务 1postgresservices:postgres:image:postgres:18container_name:nodejs-postgresenvironment:POSTGRES_USER:postgresPOSTGRES_PASSWORD:你的密码# 与 .env 里 DATABASE_URL 密码一致POSTGRES_DB:nodejs_studyvolumes:-pgdata:/var/lib/postgresql# Volume容器删了数据还在ports:-5433:5432# 宿主机可连方便 migratehealthcheck:test:[CMD-SHELL,pg_isready -U postgres -d nodejs_study]interval:5stimeout:5sretries:5字段值含义imagepostgres:18官方 PostgreSQL 18不用自己写 Dockerfilecontainer_namenodejs-postgres固定容器名Docker Desktop 里好认environment用户名/密码/库名仅首次启动初始化库密码需与.env里DATABASE_URL一致volumespgdata:/var/lib/postgresql/数据存命名卷删容器不丢ports5433:5432宿主机 5433 → 容器 5432本机连localhost:5433healthcheckpg_isready …定期探测数据库是否就绪连接串Node 在宿主机postgresql://postgres:你的密码localhost:5433/nodejs_study端口用 5433 而非 5432是为避免与本机已安装的 PostgreSQL 冲突。healthcheck 命令拆解test:[CMD-SHELL,pg_isready -U postgres -d nodejs_study]部分含义CMD-SHELL用 shell 执行后面字符串pg_isreadyPostgreSQL 自带工具检查能否连接-U postgres用用户 postgres-d nodejs_study检查库 nodejs_study 是否就绪探测成功 → healthy失败 → 继续重试多次失败后 → unhealthy。healthcheck 时间参数Redis 段配置相同不再重复参数含义interval: 5s每 5 秒测一次timeout: 5s单次命令最多等 5 秒retries: 5连续多次失败后标记为 unhealthyhealthcheck 不是必填不写也能跑。api服务已配置depends_on: condition: service_healthy会等 postgres、redis 变为 healthy 后再启动——避免 API 连上尚未就绪的数据库。服务 2redisredis:image:redis:7container_name:nodejs-redisports:-6379:6379healthcheck:test:[CMD,redis-cli,ping]interval:5stimeout:5sretries:5字段含义image: redis:7官方 Redis 7ports: 6379:6379宿主机 6379 直连容器 6379无 volumes开发够用删容器后缓存丢失Redis 多为临时数据healthcheckredis-cli ping确认 Redis 已响应连接串redis://localhost:6379healthcheck 命令拆解test:[CMD,redis-cli,ping]部分含义CMD直接执行程序不经过 shellredis-cliRedis 官方客户端ping发 PING正常应返回 PONG时间参数含义见 Postgres 一节。Redis 启动快healthcheck 这里主要是方便docker compose ps看状态Postgres 的 healthcheck 对「等库就绪再 migrate」更关键。服务 3apiapi:build:.container_name:nodejs-apiports:-8080:8080environment:DATABASE_URL:postgresql://postgres:你的密码postgres:5432/nodejs_study?schemapublicREDIS_URL:redis://redis:6379JWT_SECRET:${JWT_SECRET}depends_on:postgres:condition:service_healthyredis:condition:service_healthy字段作用build: .用项目根目录 Dockerfile 构建镜像postgres:5432Compose 内部 DNS解析到 postgres 容器redis:6379同上解析到 redis 容器${JWT_SECRET}从项目根 .env 读取Compose 自动做变量替换depends_on healthy等 PG、Redis 就绪后再启动 API密码与 postgres 服务的 POSTGRES_PASSWORD 保持一致。服务 4workerworker:build:.container_name:nodejs-workercommand:npm run worker:emailenvironment:REDIS_URL:redis://redis:6379depends_on:redis:condition:service_healthy字段作用build: .与 api 同一 Dockerfilecommand: …覆盖 Dockerfile 的 CMD跑 Worker 而不是 APIREDIS_URL用服务名 redis不是 localhost无 portsWorker 不对外暴露端口卷声明volumesvolumes:pgdata:# 声明命名卷这里只是声明命名卷pgdata真正挂载在 postgres 的volumes里。容器删了数据还在 Docker 管理的存储里。首次up会初始化空库pgdata已有数据时再次启动不会重置。和 docker run 的对应关系这份 Compose 文件大致等价于# PostgreSQLdockerrun-d--namenodejs-postgres\-ePOSTGRES_USERpostgres\-ePOSTGRES_PASSWORD...\-ePOSTGRES_DBnodejs_study\-vpgdata:/var/lib/postgresql/data\-p5433:5432\postgres:18# Redisdockerrun-d--namenodejs-redis\-p6379:6379\redis:7# API需 postgres、redis 已就绪且与它们在同一个 Docker 网络dockerbuild-tnodejs-api.dockerrun-d--namenodejs-api\--network与 postgres/redis 相同的网络\-p8080:8080\-eDATABASE_URLpostgresql://postgres:你的密码postgres:5432/nodejs_study?schemapublic\-eREDIS_URLredis://redis:6379\-eJWT_SECRET...\nodejs-apiCompose 的好处配置集中、可版本管理、一条命令启停多个服务网络、depends_on与健康检查也会自动编排。运行时是什么样子你的电脑 (Host) 浏览器 / curl │ └── localhost:8080 ──► nodejs-api (Node API 容器) │ ├── postgres:5432 ──► nodejs-postgres (PostgreSQL) └── redis:6379 ──► nodejs-redis (Redis) 宿主机上单独操作如 migrate、npm run dev仍用映射端口 localhost:5433 ──► nodejs-postgres localhost:6379 ──► nodejs-redis环境变量说明.env文件在宿主机上dockerignore中设置了不会进入 Docker 镜像/容器里。environment是容器内的正式环境变量优先级高于镜像里的.env/ dotenv就算.env文件存在 compose 里DATABASE_URL、REDIS_URL直接覆盖了本地.env的对应值。JWT_SECRET: ${JWT_SECRET}的读取发生在 Docker Compose 在宿主机上解析配置文件这一步不是容器里的 Node 去读.env。端口映射8080:8080前面是外面访问端口改成8081:8080后从本机访问 API 要用 8081容器内仍是 8080。Worker 为什么需要容器worker 需要容器是因为它是独立进程 — 和 API 分开跑API 挂了 worker 还能继续处理队列反过来也一样。需要同一套代码和依赖 — ioredis、emailWorker.ts、emailQueue.ts 都在项目里必须进容器才能跑。build: .只是声明「用这个 Dockerfile 造运行环境」 — Compose 通常只 build 一次两个 service 复用同一张镜像worker 用command覆盖默认的 CMD。command 覆盖默认启动命令command就是覆盖 Dockerfile 的默认启动命令但有个重要前提同一份镜像 → 可以起多个容器 → 每个容器可以有不同的启动命令关系可以这样理解Dockerfile build → 镜像含 Node、代码、依赖默认 CMD npm start │ ┌───────────────┴───────────────┐ ▼ ▼ api 容器 worker 容器 没写 command command: npm run worker:email 用镜像默认 CMD 覆盖 CMD → npm start → npm run worker:email → tsx ./index.ts → tsx src/workers/emailWorker.tsWorker 连接 Redis 逻辑emailWorker.ts被 tsx 当作入口执行import { redis } from ../redis.ts会立刻执行redis.ts里的代码redis.ts用process.env.REDIS_URL创建 ioredis 连接在 Docker Compose 里worker 容器的环境变量是REDIS_URLredis://redis:6379ioredis 通过 TCP 连到内部网络里名为redis的服务Redis 容器dotenv/configdotenv/config的作用是进程启动时把.env文件里的变量读进process.env。它应该放在入口文件程序最先跑起来的那个文件而不是放在被 import 的工具模块里。