跳转到内容
123xiao | 无名键客

《Docker Compose 实战:为中型项目构建可复用的多环境开发与部署配置》

字数: 0 阅读时长: 1 分钟

Docker Compose 实战:为中型项目构建可复用的多环境开发与部署配置

中型项目往往最尴尬:规模还没大到值得上 Kubernetes,但服务已经不止一个,环境也不止一种。
这时候如果还靠“每个人本地手搓一份配置”“测试环境手工改端口”“生产环境复制粘贴 compose 文件”,后面一定会乱。

这篇文章我想带你做一套可复用、可扩展、能覆盖开发/测试/生产的 Docker Compose 配置方案。重点不是把 docker compose up 跑起来,而是让配置结构能撑住中型项目的真实复杂度。


背景与问题

一个典型的中型项目,通常会有这些组件:

  • web:前端静态资源或 SSR 服务
  • api:后端应用
  • worker:异步任务消费者
  • db:MySQL / PostgreSQL
  • redis:缓存和队列
  • nginx:统一入口代理
  • 若干环境差异:
    • 开发环境需要挂载源码、热更新、暴露调试端口
    • 测试环境需要独立数据库、固定镜像版本
    • 生产环境不希望挂载本地目录,也不希望暴露数据库端口

很多团队一开始会这样做:

  • docker-compose.yml
  • docker-compose-dev.yml
  • docker-compose-test.yml
  • docker-compose-prod.yml

文件越堆越多,最后会出现几个问题:

  1. 重复配置太多:服务名、网络、卷、健康检查写了三四遍
  2. 环境差异混在一起:到底哪个文件是“基线”,没人说得清
  3. 本地和线上行为不一致:本地能跑,上线就挂
  4. 环境变量管理混乱.env、shell 导出、CI 注入互相覆盖
  5. 排查困难:容器启动失败时,不知道是镜像、依赖、网络还是变量问题

我踩过一个很典型的坑:开发环境为了方便,把数据库端口映射成 3306:3306,到了测试环境也直接沿用,结果服务器上原本已有一个 MySQL,Compose 一启动就报端口冲突。这个问题本质上不是“端口配错了”,而是环境职责没有分层


核心原理

Compose 多环境配置,建议抓住三件事:

  1. 基线配置只描述共性
  2. 环境差异通过 override 文件或 profile 管理
  3. 敏感信息和可变参数交给环境变量,不写死

1. Compose 配置分层思路

推荐结构:

  • compose.yml:基础配置,定义所有服务的共性
  • compose.dev.yml:开发环境覆盖项
  • compose.test.yml:测试环境覆盖项
  • compose.prod.yml:生产环境覆盖项

执行时通过多个文件叠加:

docker compose -f compose.yml -f compose.dev.yml up -d
docker compose -f compose.yml -f compose.test.yml up -d
docker compose -f compose.yml -f compose.prod.yml up -d

覆盖规则可以简单理解为:

  • 同名服务会合并
  • 后面的文件优先级更高
  • 某些字段是追加,有些字段是覆盖

这意味着你要把稳定不变的部分放在基线里,把只属于某个环境的差异放在 override 文件里。

2. 用 profiles 管理“可选服务”

有些组件不是每个环境都要启动,比如:

  • 本地调试用的 MailHog
  • 开发环境专用的 Adminer
  • 压测时临时加的 mock 服务

这种很适合用 profiles

profiles:
  - debug

启动时指定:

docker compose --profile debug up -d

3. 健康检查 + 依赖关系,不只靠启动顺序

depends_on 只能保证“启动顺序”,不能保证“服务可用”。

例如数据库容器启动了,但数据库进程还没接受连接,这时 API 已经开始连接,结果报错退出。
更稳妥的方式是加 healthcheck,并让应用自己支持重试。

4. 网络、卷、环境变量是三大基础设施

  • 网络:服务之间用服务名互相访问,不要写死 IP
  • :数据库数据、缓存数据要持久化
  • 环境变量:镜像标签、端口、连接串、运行模式全部参数化

前置知识 / 环境准备

建议环境:

  • Docker Engine 24+
  • Docker Compose v2
  • Linux / macOS / WSL2
  • 基础目录结构:
myapp/
├── api/
│   ├── Dockerfile
│   ├── package.json
│   └── server.js
├── nginx/
│   └── default.conf
├── .env.dev
├── .env.test
├── .env.prod
├── compose.yml
├── compose.dev.yml
├── compose.test.yml
└── compose.prod.yml

本文示例用一个 Node.js API + Nginx + MySQL + Redis + Worker 的组合来演示。


整体架构图

flowchart LR
    User[浏览器/客户端] --> Nginx
    Nginx --> API[api 服务]
    API --> MySQL[(MySQL)]
    API --> Redis[(Redis)]
    Worker[worker 服务] --> Redis
    Worker --> MySQL

这套结构有几个特点:

  • nginx 作为统一入口
  • apiworker 共用同一份应用镜像
  • dbredis 通过内部网络暴露给应用,不直接暴露给外部
  • 环境差异主要体现在:
    • 是否挂载源码
    • 是否暴露端口
    • 镜像构建还是直接拉取
    • 副本数和资源限制

配置分层设计

基线配置的职责

基线文件只做这些事:

  • 定义服务名称
  • 定义镜像或构建方式
  • 定义网络和卷
  • 定义默认环境变量
  • 定义健康检查
  • 定义共用依赖

环境覆盖文件的职责

覆盖文件只描述差异:

  • 开发环境:
    • 挂载源码
    • 暴露调试端口
    • 使用开发命令
  • 测试环境:
    • 固定标签镜像
    • 注入测试专用变量
  • 生产环境:
    • 不挂载源码
    • 不暴露数据库端口
    • 更严格的重启策略和资源限制

实战代码(可运行)

下面直接给出一套可跑的示例。

1. Node.js API 示例

api/package.json

{
  "name": "compose-medium-project-demo",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "node server.js",
    "worker": "node worker.js"
  },
  "dependencies": {
    "express": "^4.19.2",
    "mysql2": "^3.11.0",
    "redis": "^4.7.0"
  }
}

api/server.js

const express = require("express");
const mysql = require("mysql2/promise");
const { createClient } = require("redis");

const app = express();
const port = process.env.APP_PORT || 3000;

const dbConfig = {
  host: process.env.DB_HOST || "db",
  port: Number(process.env.DB_PORT || 3306),
  user: process.env.DB_USER || "app",
  password: process.env.DB_PASSWORD || "app123",
  database: process.env.DB_NAME || "app_db"
};

const redisUrl = `redis://${process.env.REDIS_HOST || "redis"}:${process.env.REDIS_PORT || 6379}`;

async function waitForMysql(retries = 20, delay = 3000) {
  for (let i = 1; i <= retries; i++) {
    try {
      const conn = await mysql.createConnection(dbConfig);
      await conn.query(`
        CREATE TABLE IF NOT EXISTS visits (
          id INT AUTO_INCREMENT PRIMARY KEY,
          created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
      `);
      await conn.end();
      console.log("MySQL ready");
      return;
    } catch (err) {
      console.log(`MySQL not ready, retry ${i}/${retries}: ${err.message}`);
      await new Promise((r) => setTimeout(r, delay));
    }
  }
  throw new Error("MySQL connection failed after retries");
}

async function createRedisClient() {
  const client = createClient({ url: redisUrl });
  client.on("error", (err) => console.error("Redis error:", err.message));
  await client.connect();
  return client;
}

async function bootstrap() {
  await waitForMysql();
  const redis = await createRedisClient();

  app.get("/health", async (req, res) => {
    try {
      const conn = await mysql.createConnection(dbConfig);
      await conn.query("SELECT 1");
      await conn.end();
      await redis.ping();
      res.json({ ok: true });
    } catch (err) {
      res.status(500).json({ ok: false, error: err.message });
    }
  });

  app.get("/", async (req, res) => {
    const conn = await mysql.createConnection(dbConfig);
    await conn.query("INSERT INTO visits () VALUES ()");
    const [rows] = await conn.query("SELECT COUNT(*) AS count FROM visits");
    await conn.end();

    const count = rows[0].count;
    await redis.set("last_visit_count", String(count));

    res.json({
      message: "Hello from Docker Compose multi-env demo",
      visits: count,
      env: process.env.APP_ENV || "unknown"
    });
  });

  app.listen(port, () => {
    console.log(`API listening on ${port}`);
  });
}

bootstrap().catch((err) => {
  console.error(err);
  process.exit(1);
});

api/worker.js

const mysql = require("mysql2/promise");
const { createClient } = require("redis");

const dbConfig = {
  host: process.env.DB_HOST || "db",
  port: Number(process.env.DB_PORT || 3306),
  user: process.env.DB_USER || "app",
  password: process.env.DB_PASSWORD || "app123",
  database: process.env.DB_NAME || "app_db"
};

async function run() {
  const redis = createClient({
    url: `redis://${process.env.REDIS_HOST || "redis"}:${process.env.REDIS_PORT || 6379}`
  });

  redis.on("error", (err) => console.error("Redis error:", err.message));
  await redis.connect();

  while (true) {
    try {
      const conn = await mysql.createConnection(dbConfig);
      const [rows] = await conn.query("SELECT COUNT(*) AS count FROM visits");
      await conn.end();

      const count = rows[0].count;
      await redis.set("stats:visit_count", String(count));
      console.log("Updated stats:visit_count =", count);
    } catch (err) {
      console.error("Worker loop error:", err.message);
    }

    await new Promise((r) => setTimeout(r, 5000));
  }
}

run().catch((err) => {
  console.error(err);
  process.exit(1);
});

api/Dockerfile

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

EXPOSE 3000

CMD ["npm", "start"]

2. Nginx 配置

nginx/default.conf

server {
    listen 80;
    server_name _;

    location / {
        proxy_pass http://api:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

3. 基线 Compose 文件

compose.yml

name: medium-project-demo

services:
  nginx:
    image: nginx:1.27-alpine
    depends_on:
      api:
        condition: service_started
    ports:
      - "${NGINX_PORT:-8080}:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    networks:
      - app_net
    restart: unless-stopped

  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    image: medium-project-demo-api:${APP_IMAGE_TAG:-latest}
    environment:
      APP_ENV: ${APP_ENV:-dev}
      APP_PORT: 3000
      DB_HOST: db
      DB_PORT: 3306
      DB_NAME: ${DB_NAME:-app_db}
      DB_USER: ${DB_USER:-app}
      DB_PASSWORD: ${DB_PASSWORD:-app123}
      REDIS_HOST: redis
      REDIS_PORT: 6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - app_net
    restart: unless-stopped

  worker:
    image: medium-project-demo-api:${APP_IMAGE_TAG:-latest}
    command: ["npm", "run", "worker"]
    environment:
      APP_ENV: ${APP_ENV:-dev}
      DB_HOST: db
      DB_PORT: 3306
      DB_NAME: ${DB_NAME:-app_db}
      DB_USER: ${DB_USER:-app}
      DB_PASSWORD: ${DB_PASSWORD:-app123}
      REDIS_HOST: redis
      REDIS_PORT: 6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - app_net
    restart: unless-stopped

  db:
    image: mysql:8.4
    environment:
      MYSQL_DATABASE: ${DB_NAME:-app_db}
      MYSQL_USER: ${DB_USER:-app}
      MYSQL_PASSWORD: ${DB_PASSWORD:-app123}
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root123}
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h localhost -uroot -p$$MYSQL_ROOT_PASSWORD"]
      interval: 5s
      timeout: 3s
      retries: 20
    networks:
      - app_net
    restart: unless-stopped

  redis:
    image: redis:7.4-alpine
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    networks:
      - app_net
    restart: unless-stopped

networks:
  app_net:

volumes:
  db_data:
  redis_data:

4. 开发环境覆盖文件

开发环境通常强调:

  • 源码热更新
  • 开放更多端口
  • 允许调试
  • 可以附加辅助工具

compose.dev.yml

services:
  api:
    env_file:
      - .env.dev
    volumes:
      - ./api:/app
      - /app/node_modules
    command: ["npm", "run", "dev"]
    ports:
      - "3000:3000"

  worker:
    env_file:
      - .env.dev
    volumes:
      - ./api:/app
      - /app/node_modules

  db:
    env_file:
      - .env.dev
    ports:
      - "3307:3306"

  redis:
    ports:
      - "6379:6379"

  adminer:
    image: adminer:4
    profiles:
      - debug
    ports:
      - "8081:8080"
    networks:
      - app_net
    depends_on:
      db:
        condition: service_started

.env.dev

APP_ENV=development
APP_IMAGE_TAG=dev
DB_NAME=app_db_dev
DB_USER=app
DB_PASSWORD=app123
DB_ROOT_PASSWORD=root123
NGINX_PORT=8080

5. 测试环境覆盖文件

测试环境更像“轻量部署环境”,重点是:

  • 不挂载源码
  • 使用构建产物或固定镜像
  • 端口尽量明确
  • 数据隔离

compose.test.yml

services:
  api:
    env_file:
      - .env.test

  worker:
    env_file:
      - .env.test

  db:
    env_file:
      - .env.test
    ports:
      - "3308:3306"

  nginx:
    env_file:
      - .env.test

.env.test

APP_ENV=test
APP_IMAGE_TAG=test
DB_NAME=app_db_test
DB_USER=app
DB_PASSWORD=test123
DB_ROOT_PASSWORD=testroot123
NGINX_PORT=8088

6. 生产环境覆盖文件

生产环境要尽量减少“开发式便利”。

compose.prod.yml

services:
  nginx:
    env_file:
      - .env.prod
    ports:
      - "${NGINX_PORT:-80}:80"

  api:
    env_file:
      - .env.prod
    build: null
    image: ${API_IMAGE:?API_IMAGE is required}

  worker:
    env_file:
      - .env.prod
    image: ${API_IMAGE:?API_IMAGE is required}

  db:
    env_file:
      - .env.prod
    ports: []
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h localhost -uroot -p$$MYSQL_ROOT_PASSWORD"]
      interval: 10s
      timeout: 5s
      retries: 12

  redis:
    ports: []
    command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD:?REDIS_PASSWORD is required}"]

.env.prod

APP_ENV=production
DB_NAME=app_db_prod
DB_USER=app
DB_PASSWORD=prod_app_password
DB_ROOT_PASSWORD=prod_root_password
NGINX_PORT=80
API_IMAGE=registry.example.com/myteam/medium-project-demo-api:1.0.0
REDIS_PASSWORD=prod_redis_password

提醒一下:真正生产环境里,.env.prod 最好不要直接放仓库,应该通过 CI/CD、密钥管理系统或服务器安全注入。


启动流程图

sequenceDiagram
    participant U as 运维/开发者
    participant C as Docker Compose
    participant D as db
    participant R as redis
    participant A as api
    participant W as worker
    participant N as nginx

    U->>C: docker compose -f compose.yml -f compose.dev.yml up -d
    C->>D: 启动 MySQL
    C->>R: 启动 Redis
    D-->>C: healthcheck 通过
    C->>A: 启动 API
    C->>W: 启动 Worker
    C->>N: 启动 Nginx
    U->>N: 访问 8080
    N->>A: 反向代理请求
    A->>D: 读写数据
    A->>R: 写缓存/状态

7. 运行与验证

开发环境启动

docker compose -f compose.yml -f compose.dev.yml up -d --build

如果想带上 Adminer:

docker compose -f compose.yml -f compose.dev.yml --profile debug up -d --build

测试环境启动

docker compose -f compose.yml -f compose.test.yml up -d --build

生产环境启动

docker compose -f compose.yml -f compose.prod.yml up -d

验证 API

curl http://localhost:8080/

预期返回:

{
  "message": "Hello from Docker Compose multi-env demo",
  "visits": 1,
  "env": "development"
}

验证健康检查

curl http://localhost:8080/health

查看日志

docker compose -f compose.yml -f compose.dev.yml logs -f api
docker compose -f compose.yml -f compose.dev.yml logs -f worker
docker compose -f compose.yml -f compose.dev.yml logs -f db

停止并清理

docker compose -f compose.yml -f compose.dev.yml down

如果要连同数据卷一起删:

docker compose -f compose.yml -f compose.dev.yml down -v

逐步验证清单

建议你不要一上来就全启动,按下面顺序验证,出问题更容易定位:

  1. docker compose config 看最终合并结果
  2. 单独启动 dbredis
  3. 检查 db healthcheck 是否通过
  4. 启动 api,观察连接数据库是否成功
  5. 启动 worker
  6. 最后启动 nginx
  7. curl 验证 //health
  8. 重启一遍确认数据卷是否保留

查看合并后的最终配置

docker compose -f compose.yml -f compose.dev.yml config

这个命令特别有用。很多“我明明写了怎么没生效”的问题,跑一次它基本就能看出问题。


核心原理再落地:如何避免配置失控

中型项目里,我最推荐下面这套规则:

规则 1:基线文件不要写“开发便利项”

比如:

  • 不要在基线里直接暴露数据库端口
  • 不要在基线里挂载源码目录
  • 不要在基线里写测试专用环境变量

因为这些都不是“共性”。

规则 2:应用镜像尽量复用

上面的 apiworker 共用一个镜像,只通过 command 区分入口。
这能减少:

  • Dockerfile 重复
  • 依赖版本漂移
  • CI 构建时间

规则 3:服务间访问统一用服务名

比如 API 连数据库写:

  • db:3306
  • redis:6379

不要写:

  • 127.0.0.1
  • 宿主机 IP
  • 手动分配的容器 IP

规则 4:环境变量分“默认值”和“必填值”

Compose 里很适合这样写:

image: ${API_IMAGE:?API_IMAGE is required}

这样生产环境少传变量时会直接失败,而不是等服务启动后才发现拉不到镜像。


常见坑与排查

这部分很重要。很多 Compose 问题不是不会写,而是“不知道为什么没按预期工作”。

1. depends_on 不等于服务真正可用

现象

api 已启动,但日志里报:

ECONNREFUSED

原因

db 容器进程启动了,但数据库还没完成初始化。

处理方法

  • dbhealthcheck
  • 应用侧实现重试逻辑
  • 不要迷信单纯的 depends_on

2. 覆盖文件没生效

现象

你明明在 compose.dev.yml 里写了端口映射,但启动后没看到。

排查

先执行:

docker compose -f compose.yml -f compose.dev.yml config

看最终配置里有没有你期望的字段。

常见原因

  • 文件顺序反了
  • 服务名写错
  • 实际使用的是 docker-compose 老命令或旧版本行为不同
  • .envenv_file 变量来源搞混了

3. 宿主机端口冲突

现象

启动时报:

Bind for 0.0.0.0:3306 failed: port is already allocated

处理

查看占用:

lsof -i :3306

或者干脆不要对外暴露数据库端口。
我一般建议:

  • 开发环境映射成 3307:3306
  • 测试环境映射成 3308:3306
  • 生产环境不映射

4. 挂载源码后依赖丢失

现象

开发环境启动时报:

Error: Cannot find module 'express'

原因

宿主机目录挂载把镜像内 /app 覆盖了,连同容器里的 node_modules 一起“遮住”了。

处理

像这样保留匿名卷:

volumes:
  - ./api:/app
  - /app/node_modules

这个写法在 Node.js 开发环境里非常常见。


5. 环境变量优先级理解错误

Compose 里变量来源不少,容易混:

  • shell 环境变量
  • 项目目录下 .env
  • env_file
  • environment

一个经验结论:

  • 插值主要看 Compose 解析阶段拿到的变量
  • 容器内环境变量最终看 environment / env_file

如果你发现 ${VAR} 没替换,先检查是不是 Compose 启动时那个变量根本不存在。


6. 生产环境仍然在本机构建镜像

现象

服务器启动很慢,还依赖本地源码目录。

原因

生产覆盖文件没把 build 清掉,或者仍然使用本地构建逻辑。

处理

像本文这样在生产覆盖里显式指定:

build: null
image: ${API_IMAGE:?API_IMAGE is required}

安全最佳实践

中型项目不一定有专职平台团队,所以更要避免“方便但危险”的配置。

1. 不把敏感信息写死进仓库

尤其是:

  • DB_ROOT_PASSWORD
  • REDIS_PASSWORD
  • 第三方 API Key
  • JWT Secret

更推荐:

  • CI/CD 注入
  • Docker secrets(如果运行环境支持)
  • 服务器环境变量
  • 专门的密钥管理系统

2. 生产环境不要随意暴露内部服务端口

常见的安全错误是:

  • MySQL 对公网开放
  • Redis 对公网开放且没密码
  • Adminer / phpMyAdmin 长期开着

建议:

  • dbredis 只放内网
  • 调试服务用 profiles
  • 仅在临时维护时启用

3. 用非 root 用户运行应用

示例为了简洁没有展开,但实际项目里建议在 Dockerfile 里切换非 root 用户。

例如:

FROM node:20-alpine

WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .

RUN addgroup -S app && adduser -S app -G app
USER app

EXPOSE 3000
CMD ["npm", "start"]

4. 固定镜像版本,不要长期用 latest

开发环境可以灵活一点,但测试、生产环境最好固定版本:

image: mysql:8.4
image: redis:7.4-alpine

应用镜像也要打版本标签,比如:

registry.example.com/myteam/medium-project-demo-api:1.0.0

性能最佳实践

Compose 虽然不是大规模编排工具,但也不是只能“凑合用”。做好几个点,稳定性会明显提升。

1. 给数据库和缓存挂持久化卷

这不是性能优化的唯一手段,但绝对是稳定性的底线。
没有卷的话,容器一删数据就没了,后面的排查全是噪音。

2. 不要把所有流量都打到应用直端口

用 Nginx 做入口有几个好处:

  • 统一暴露端口
  • 更容易加 gzip、缓存、限流
  • 后续切 HTTPS 更顺

3. 尽量减少开发环境和生产环境的镜像差异

理想状态是:

  • 同一份 Dockerfile
  • 相同依赖安装方式
  • 不同环境只改少量变量和挂载策略

这样“本地没问题、线上不行”的概率会小很多。

4. 合理设置重启策略

restart: unless-stopped

对多数中型项目已经够用。
但要注意,重启策略不是兜底修复工具。如果应用启动逻辑有 bug,它只会帮你无限重启。

5. 做好健康检查和观测

至少要能做到:

  • docker compose ps 看状态
  • docker compose logs -f 看日志
  • HTTP /health 可探活
  • 数据库/缓存连接失败时日志可读

环境配置关系图

flowchart TD
    A[compose.yml 基线] --> B[compose.dev.yml]
    A --> C[compose.test.yml]
    A --> D[compose.prod.yml]

    B --> E[开发环境<br/>挂载源码/开放调试端口]
    C --> F[测试环境<br/>固定配置/数据隔离]
    D --> G[生产环境<br/>仅镜像部署/最小暴露面]

什么时候 Compose 够用,什么时候该升级方案?

这篇文章讲的是中型项目,所以也要说清边界。

Compose 很适合的场景

  • 服务数量在个位数到十来个
  • 团队主要需求是统一开发环境和简单部署
  • 单机或少量服务器部署
  • 还不需要复杂的自动扩缩容和服务治理

Compose 不太适合的场景

  • 多机集群高可用
  • 动态扩缩容频繁
  • 强依赖服务发现、灰度发布、复杂滚动升级
  • 需要细粒度资源编排和大规模可观测性

如果项目已经进入这些阶段,就该考虑:

  • Kubernetes
  • Nomad
  • 或更成熟的 PaaS / GitOps 体系

所以别把 Compose 神化,它很强,但也有边界。


总结

如果你想为中型项目构建一套可复用的多环境 Docker Compose 配置,我建议直接落实这几条:

  1. 一份基线文件 + 多份环境覆盖文件
  2. 共性放基线,差异放 override
  3. 服务间通信统一用服务名
  4. 数据库加 healthcheck,应用侧加重试
  5. 开发环境允许便利,生产环境坚持最小暴露
  6. 生产部署优先拉镜像,不在服务器现构建
  7. 先用 docker compose config 看最终结果,再排障

如果你今天就准备改造现有项目,我建议从最小一步开始:

  • 先把现有 docker-compose.yml 拆成 compose.yml + compose.dev.yml
  • 再把数据库、缓存、应用的环境变量整理出来
  • 最后补上测试和生产覆盖文件

这样成本不高,但收益很快能看到:
配置更清晰,环境更一致,排查也更省心。

对中型项目来说,这往往就是从“能跑”走向“可维护”的分水岭。


分享到:

上一篇
《Java Web 开发中基于 Spring Boot + Redis 实现接口限流与防刷的实战指南》
下一篇
《Java Web 开发中基于 Spring Boot + MyBatis 的高并发订单系统接口幂等与防重复提交实战》