小杰的小窝
  • 关于我
  • 作品集
  • 技术笔记
  • 生活随笔
  • 找我聊聊
小杰的小窝
© 2026 Liu Jie·蜀ICP备2026002183号-1
返回博客

一套企业级的前后端 Docker 分离部署方案

2026-01-135分钟
架构

这篇文章不是“Docker 怎么用”的教程,而是一次真实工程问题的拆解: 如何在单台服务器上,构建一套前后端完全解耦、可 CI/CD、可长期维护的 Docker 部署方案。


一、问题背景:为什么「前后端写在一起」会出问题?

很多项目在早期,都会采用类似这样的方式:

  • 一个 docker-compose.yml
  • 同时包含:
    • frontend
    • backend
    • postgres
    • redis
  • 前端发布 = docker compose up -d
  • 后端发布 = docker compose up -d

看起来简单,但问题很快就会出现:

典型问题

  1. 前端发布时触发后端依赖
    • 缺少后端环境变量
    • 数据库未启动
  2. 后端发布影响前端
    • compose 解析 frontend 的变量
    • frontend image/tag 不存在
  3. CI/CD 难以拆分
    • 前端 pipeline 却要配置后端变量
    • 后端 pipeline 却被 frontend 阻塞

👉 本质问题只有一句话:

部署单元(deployment unit)划分错误


二、核心设计原则(这套方案的灵魂)

在进入具体方案前,先明确几条工程级原则:

1️⃣ 前后端必须是「独立发布单元」

  • 前端发布 ≠ 后端发布
  • 任意一方发布 不应该感知另一方

2️⃣ 基础设施(Infra)必须与业务解耦

  • 数据库
  • Redis
  • Network
  • Volume

这些都不属于业务发布范畴。


3️⃣ 环境变量只能有一个入口

  • 本地:.env
  • CI/CD:GitLab Variables
  • ❌ compose 里不写 env_file

4️⃣ Compose 是「声明」,脚本是「入口」

  • compose 描述 是什么
  • shell 决定 何时、如何跑

三、最终架构总览(逻辑结构)

┌──────────────┐
│   Infra      │
│              │
│  postgres    │
│  redis       │
│  networks    │
└──────┬───────┘
       │
┌──────▼───────┐
│   Backend    │
│              │
│  API / RPC   │
└──────┬───────┘
       │
┌──────▼───────┐
│  Frontend    │
│              │
│  Next.js     │
└──────────────┘

关键点:

  • Infra 只部署一次
  • Backend / Frontend 完全独立
  • 通过 Docker Network 通信

四、Infra:企业级基础设施 compose

infra.compose.yml

services:
  postgres:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_DB: neo_blog_db
      POSTGRES_USER: blog
      POSTGRES_PASSWORD: secret
    volumes:
      - neo-blog-postgres-data:/var/lib/postgresql/data
    networks:
      - database-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U blog -d neo_blog_db"]
      interval: 5s
      retries: 10

  redis:
    image: redis:latest
    restart: unless-stopped
    volumes:
      - neo-blog-redis-data:/data
    networks:
      - database-network

volumes:
  neo-blog-postgres-data:
  neo-blog-redis-data:

networks:
  database-network:
    name: database-network
  app-network:
    name: app-network

设计思考

  • infra 负责创建 network
  • backend / frontend 只使用 external: true
  • infra 永远不进 CI/CD

deploy-infra.sh

#!/usr/bin/env bash
set -euo pipefail

docker compose \
  -p neo-blog-infra \
  -f infra.compose.yml \
  up -d

📌 企业共识:infra 由运维/SRE 执行,不由 CI 触发


五、Backend:独立、可 CI/CD 的服务部署

backend.compose.yml

services:
  backend:
    image: ${CI_BACKEND_IMAGE}:${BACKEND_IMAGE_TAG}
    container_name: neo-blog-backend
    restart: unless-stopped
    networks:
      - database-network
      - app-network

  migrate:
    image: ${BACKEND_MIGRATE_IMAGE}:${MIGRATE_IMAGE_TAG}
    container_name: neo-blog-migrate
    command: ["bun", "run", "prisma:deploy"]
    environment:
      - xxxx1=${xxxx1}
      - xxxx2=${xxxx2}
    networks:
      - database-network

networks:
  database-network:
    external: true
  app-network:
    external: true

deploy-backend.sh(本地 & CI 通用)

#!/usr/bin/env bash
set -euo pipefail

set -a
source "$ENV_FILE"
set +a

: "${BACKEND_IMAGE:?required}"
: "${DATABASE_URL:?required}"

docker compose -f backend.compose.yml pull
docker compose -f backend.compose.yml run --rm migrate
docker compose -f backend.compose.yml up -d backend

关键设计点

  • 👀 加载本地环境变量(如果走gitlab部署 那么可以全部由Environment variables等方式注入环境变量)
  • ❌ 不感知 frontend
  • ✅ CI 环境变量 = 唯一真相源

六、Frontend:完全独立的发布单元

frontend.compose.yml

services:
  frontend:
    image: ${CI_REGISTRY_FRONTEND_IMAGE}:${FRONTEND_IMAGE_TAG}
    container_name: neo-blog-frontend
    restart: unless-stopped
    ports:
      - "3001:3000"
    networks:
      - app-network

networks:
  app-network:
    external: true

deploy-frontend.sh

#!/usr/bin/env bash
set -euo pipefail

: "${FRONTEND_IMAGE:?required}"

docker compose -f frontend.compose.yml pull
docker compose -f frontend.compose.yml up -d frontend

七、环境变量策略(核心思想)

本地开发 / 模拟部署

  • 使用对应文件夹中的 .env.deploy
  • 使用对应文件夹中的 .env.deploy
  • shell 中 source 或 --env-file

GitLab CI/CD

  • 不使用任何 .env 文件
  • 全部变量托管在:
    • Project variables
    • Group variables
    • Environment variables
docker compose up -d

即可正常工作。

📌 Docker Compose 优先读取 shell 环境变量


八、为什么不再使用 env_file:?

原因只有一个:

环境变量只能有一个入口

env_file: 会导致:

  • 本地 / CI 行为不一致
  • 变量来源不明确
  • CI 中文件可能根本不存在

👉 在企业部署中,这是反模式。


九、真实企业中这套方案是谁在用?

角色 负责内容
运维 / SRE infra.compose.yml
CI/CD backend / frontend
开发 镜像构建
Docker Compose 运行时编排

📌 这是 Docker → Kubernetes 的自然过渡路径。


十、总结:这套方案解决了什么?

✅ 前后端彻底解耦 ✅ 本地与 CI 行为一致 ✅ infra 与业务隔离 ✅ 不依赖隐式文件 ✅ 可无限次重跑 ✅ 易迁移至 k8s


觉得有用的话,分享给朋友吧