结论先行:每次启动新项目都从空白手写 compose.yaml 是出海独立开发中最隐蔽的时间黑洞。把通用架构沉淀为标准化模块:通过清晰的目录规范(base/、stacks/、env/)、变量继承机制与生产基线(健康检查、日志限额、重启策略、固定镜像小版本),新项目直接复制改动几行变量即可 5 分钟起跑。生产铁律:对外只暴露反代网关端口、数据库缓存统一走内部自定义隔离网络、严禁在 Compose 暴露未经认证的 Redis/Postgres 端口到公网。
0、写在前面
在连续启动两三个海外项目后,很多开发者会陷入重复劳动的循环:
- 每次新开仓库,都要从旧项目里复制一份
docker-compose.yml,然后手动搜索替换端口、密码和挂载路径;
- 常常因为复制漏掉某一行环境变量,或者打错了一个 YAML 缩进,导致数据库连接超时;
- 生产环境忘记配置容器日志上限,几个月后服务器磁盘被撑爆。
解决这个问题的方案是工程化搭建一套可复用的 Compose 模板库。把常用的服务组件(Nginx 反代、PostgreSQL、MySQL、Redis、PHP/Node/Python 运行时)沉淀为经过生产验证的标杆配置。新项目上线时,只需按需拼装,修改 .env 即可上线。
1、期望目标
阅读并实操本文后,你将达成以下目标:
- [ ] 掌握标准化 Compose 模板库的目录组织规范与多环境分离逻辑;
- [ ] 掌握健康检查、日志轮转(50MB/3份)与内存限额三大生产配置基线;
- [ ] 掌握 LNMP(Nginx + PHP-FPM + MySQL)经典建站栈模板;
- [ ] 掌握现代全栈 Web 架构(Nginx + Node/Python + PostgreSQL + Redis)模板;
- [ ] 掌握开发环境专用辅助组件(Adminer 数据库管理工具、Mailpit 本地邮件捕捉器)的组合编排;
- [ ] 熟练运用
docker compose -f 多文件继承与环境变量注入。
2、模板库架构体系设计
目录结构规划
推荐将模板库统一维护在独立代码仓库中,例如 docker-templates/:
1 2 3 4 5 6 7 8 9 10
| docker-templates/ ├── common/ # 通用配置片段 │ ├── nginx/conf.d/ # 反代模板 │ └── logging.yml # 日志策略片段 ├── stacks/ # 开箱即用的应用堆栈 │ ├── 01-lnmp/ # Nginx + PHP + MySQL │ ├── 02-modern-web/ # Nginx + App + Postgres + Redis │ ├── 03-dev-tools/ # 开发调试增强栈 │ └── 04-production-hardened/# 高可靠生产加固模板 └── scripts/ # 一键初始化与校验脚本
|
生产级容器四大黄金基线
一套合格的生产级 Compose 模板,必须在服务中内置以下属性:
- 固定镜像精确版本 (Tag):严禁使用
latest,必须精确到次版本号(如 postgres:16.2-alpine);
- 日志体积上限控制 (Logging):单容器日志上限 50MB,最多 3 个滚动备份;
- 健康检查与就绪依赖 (Healthcheck):数据库等基础设施必须配置探测命令,应用通过
condition: service_healthy 建立依赖;
- 内部网络隔离 (Bridge Network):数据库和缓存绝不映射到宿主机公网端口。
3、四大经典基础架构模板
模板 1:标准 LNMP 建站栈(WordPress / Typecho / Laravel 通用)
适用于 PHP 内容站点,采用 Nginx + PHP 8.2 FPM + MySQL 8.0 组合,动静分离:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| services: web: image: nginx:1.25-alpine container_name: lnmp-nginx restart: unless-stopped ports: - "${WEB_PORT:-80}:80" volumes: - ./html:/var/www/html:ro - ./nginx/conf.d:/etc/nginx/conf.d:ro depends_on: - php networks: - lnmp-net logging: driver: "json-file" options: max-size: "50m" max-file: "3"
php: image: php:8.2-fpm-alpine container_name: lnmp-php restart: unless-stopped volumes: - ./html:/var/www/html networks: - lnmp-net depends_on: db: condition: service_healthy
db: image: mysql:8.0.36 container_name: lnmp-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD} volumes: - mysql_data:/var/lib/mysql networks: - lnmp-net healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"] interval: 5s timeout: 5s retries: 5 start_period: 15s
volumes: mysql_data:
networks: lnmp-net: driver: bridge
|
模板 2:现代 Web 全栈应用栈(Nginx + Node/Python + PostgreSQL + Redis)
海外 SaaS 原型最常用的黄金组合,兼顾 API 高性能与缓存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| services: proxy: image: nginx:1.25-alpine container_name: app-proxy restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./certs:/etc/nginx/certs:ro depends_on: - backend networks: - app-net
backend: image: node:20-alpine container_name: app-backend restart: unless-stopped working_dir: /app volumes: - ./app:/app command: npm run start environment: - NODE_ENV=production - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0 depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - app-net
postgres: image: postgres:16.2-alpine container_name: app-postgres restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - pg_data:/var/lib/postgresql/data networks: - app-net healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 5s timeout: 5s retries: 5 start_period: 10s
redis: image: redis:7.2-alpine container_name: app-redis restart: unless-stopped command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes volumes: - redis_data:/data networks: - app-net healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 5s timeout: 3s retries: 5
volumes: pg_data: redis_data:
networks: app-net: driver: bridge
|
模板 3:开发测试环境增强套件(含数据库图形工具与邮件陷阱)
本地开发时,往往需要可视化管理数据库,并拦截应用发送的找回密码或验证码邮件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| services: adminer: image: adminer:4.8-standalone container_name: dev-adminer restart: unless-stopped ports: - "127.0.0.1:8088:8080" networks: - app-net
mailpit: image: axllent/mailpit:v1.15 container_name: dev-mailpit restart: unless-stopped ports: - "127.0.0.1:8025:8025" - "127.0.0.1:1025:1025" networks: - app-net
|
4、模板复用规范与环境切换
4.1 .env.example 规范
每个模板根目录必须附带一份无敏感数据的 .env.example,新项目只需拷贝改名:
1 2
| cp .env.example .env chmod 600 .env
|
1 2 3 4 5 6
| POSTGRES_DB=myapp_production POSTGRES_USER=appuser POSTGRES_PASSWORD=RandomGeneratedSecurePassword2026! REDIS_PASSWORD=AnotherStrongPassword2026! WEB_PORT=80
|
4.2 多文件继承合并机制
Docker Compose 原生支持通过 -f 合并参数实现环境分离,避免复制两套重复的 YAML:
1 2 3 4 5
| docker compose -f compose.yaml -f compose.prod.yaml up -d
docker compose -f compose.yaml -f compose.dev.yaml up -d
|
5、生产避坑清单
避坑 1:Redis 容器未配置密码且端口对外映射
- 危害:互联网扫描器会在数秒内发现空密码的 6379 端口,并利用 Redis 写入 SSH 公钥或植入挖矿木马。
- 规范:
- 模板中严禁写
ports: - "6379:6379",仅保留容器网络互联;
- 启动命令必须强制带上
--requirepass 参数。
避坑 2:不同项目模板中的 container_name 命名冲突
- 现象:在同一台服务器拉起第二个项目时,报错
The container name "/app-nginx" is already in use。
- 解法:在通用模板中,尽量移除静态的
container_name 属性,让 Docker Compose 自动根据 项目目录名_服务名_序号 自动生成唯一名称。
6、FAQ
Q1:既然有模板,为什么不直接打包成单一超级镜像?
单一镜像(All-in-One 镜像把 Nginx、Node、MySQL 塞在一起)违反了容器的单一进程职责原则,导致无法针对单一数据库或前端单独扩容和升级。多容器编排解耦了生命周期,各服务独立更新互不干扰。
Q2:下一篇有哪些模板?
下一篇《Docker Compose 模板库实战(下)》将给出 5 个生产级独立应用的完整模板:LNMP WordPress、Ghost 内容创作平台、Nextcloud 私有云盘、Vaultwarden 密码管理器与 Plausible 统计服务。