Share Notes

chundev

View the Project on GitHub latteouka/share-notes

Docker Compose 多 Repo 部署架構

日期:2026-03-16 場景:一個主應用(Node.js)+ 一個 Worker(Python)共用 DB,部署在同一台 server


TL;DR

兩個不同語言、不同 repo 的服務,用同一個 docker-compose.yml 部署。關鍵技巧:build.context 指向隔壁目錄,共用 Docker network 讓服務互打內部 API。


問題

你有兩個獨立 repo:

~/projects/
├── app/              # 主應用(Node.js, Next.js)
│   ├── Dockerfile
│   ├── docker-compose.prod.yml  ← compose 放這裡
│   └── .env.production
└── worker/           # Worker(Python)
    └── Dockerfile

需求:


解法:docker-compose 的 build context

docker-compose.ymlbuild.context 可以指向任意路徑,不限於當前目錄:

services:
  postgres:
    image: postgres:17-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready"]
      interval: 10s

  app:
    build: .                    # 當前 repo
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "127.0.0.1:3050:3050"
    environment:
      DATABASE_URL: postgresql://user:pass@postgres:5432/mydb

  worker:
    build:
      context: ../worker        # ← 指向隔壁 repo
      dockerfile: Dockerfile
    depends_on:
      app:
        condition: service_healthy
    environment:
      API_URL: http://app:3050/api   # ← Docker 內部網路,用 service name

volumes:
  pgdata:

關鍵點

概念 說明
build.context: ../worker compose 會把 ../worker 整個目錄送給 Docker daemon 當作 build context
http://app:3050 同一個 compose 的 services 自動在同一個 network,用 service name 當 hostname
depends_on + condition 確保 app 健康後 worker 才啟動

Server 上的目錄結構

/home/deploy/
├── app/
│   ├── docker-compose.prod.yml
│   ├── .env.production          # 所有環境變數(兩個服務共用)
│   └── ...(app 原始碼)
├── worker/
│   ├── Dockerfile
│   └── ...(worker 原始碼)
└── certs/                       # 敏感憑證(volume 掛載)

部署指令:

cd /home/deploy/app
docker compose -f docker-compose.prod.yml --env-file .env.production up -d --build

--build 會根據 build.context 自動 build 兩個 image。


三種 Worker 排程策略

Worker 通常不是常駐服務,而是定時執行一次。有三種方式:

策略 A:Host Crontab(推薦)

Server 的 crontab 定時用 docker compose run 觸發:

# /etc/crontab 或 crontab -e
30 8 * * 1-5 cd /home/deploy/app && docker compose -f docker-compose.prod.yml --env-file .env.production run --rm worker

策略 B:Container 內建 Cron

Worker Dockerfile 裝 cron,自己排程:

FROM python:3.12-slim
RUN apt-get update && apt-get install -y cron
COPY crontab /etc/cron.d/worker-cron
RUN chmod 0644 /etc/cron.d/worker-cron && crontab /etc/cron.d/worker-cron
CMD ["cron", "-f"]

策略 C:App 內觸發(PM2 / 主應用 call)

主應用的排程器在指定時間 docker exec 或呼叫 API 觸發 worker:

// PM2 ecosystem.config.cjs
{
  name: 'trigger-worker',
  script: 'bash',
  args: '-c "docker compose run --rm worker"',
  cron_restart: '30 8 * * 1-5',
}

比較

策略 複雜度 常駐? 依賴 host?
A. Host Crontab 低 ⭐
B. Container Cron
C. App 觸發 ⚠️ 需 docker socket

服務間通訊

Docker 內部(推薦)

同一個 docker-compose.yml 的 services 自動加入同一個 bridge network:

app ←→ postgres     # app 用 postgres:5432 連 DB
worker ←→ app       # worker 用 app:3050 打 API
worker ←→ postgres  # worker 也能直接連 DB

不需要 expose port 到 host。ports: "127.0.0.1:3050:3050" 是給外部(Nginx)用的,內部直接用 service name。

API Token 保護

Worker 打 App 的 API 時,用 Token 驗證避免外部濫用:

# .env.production
EXECUTOR_API_TOKEN=random-secret-token

# app 檢查 Authorization header
# worker 帶上 Bearer token

敏感檔案處理

絕對不要 把憑證、密鑰打包進 Docker image。用 volume 掛載:

services:
  worker:
    volumes:
      - /home/deploy/certs:/app/certs:ro   # :ro = 唯讀
    environment:
      CA_CERT_PATH: /app/certs/cert.pfx

這樣 image 裡沒有敏感檔案,可以安全推到 registry。


部署流程

首次部署

# 1. Clone 兩個 repo
ssh deploy@server
cd /home/deploy
git clone git@github.com:you/app.git
git clone git@github.com:you/worker.git

# 2. 設定環境變數
cp app/.env.example app/.env.production
vim app/.env.production  # 填入所有變數

# 3. 放置憑證
mkdir -p certs
scp local/cert.pfx deploy@server:/home/deploy/certs/

# 4. Build + 啟動
cd app
docker compose -f docker-compose.prod.yml --env-file .env.production up -d --build

# 5. 設定 crontab
crontab -e
# 加入:30 8 * * 1-5 cd /home/deploy/app && docker compose -f docker-compose.prod.yml --env-file .env.production run --rm worker >> /home/deploy/worker/logs/cron.log 2>&1

日常更新

# 只更新 app
cd /home/deploy/app && git pull
docker compose -f docker-compose.prod.yml --env-file .env.production up -d --build app

# 只更新 worker
cd /home/deploy/worker && git pull
docker compose -f docker-compose.prod.yml --env-file .env.production build worker
# 下次 cron 觸發時自動用新 image

# 兩個都更新
cd /home/deploy/app && git pull
cd /home/deploy/worker && git pull
cd /home/deploy/app
docker compose -f docker-compose.prod.yml --env-file .env.production up -d --build

常見問題

Q: worker build 時找不到 ../worker 目錄?

確保目錄結構正確:

ls /home/deploy/
# 應該看到 app/ 和 worker/ 在同一層

Q: worker 連不上 http://app:3050

  1. 確認 app 的 healthcheck 通過:docker compose ps
  2. 確認在同一個 network:docker network ls + docker network inspect
  3. 確認 app 監聽 0.0.0.0:3050 而非 127.0.0.1:3050

Q: docker compose run vs docker compose up

指令 用途
up -d 啟動常駐服務(app, postgres)
run --rm 執行一次性任務(worker),完成後自動清除 container
exec 在已運行的 container 中執行指令

Q: 如何看 worker 的 log?

# 如果用 crontab,log 在指定的檔案
tail -f /home/deploy/worker/logs/cron.log

# 如果用 docker compose run,即時看到 stdout
docker compose -f docker-compose.prod.yml run --rm worker

# 查看歷史 container 的 log
docker logs tsm-executor

參考架構圖

Server
┌─────────────────────────────────────────────┐
│  Docker Network (bridge)                     │
│                                              │
│  ┌──────────┐  ┌──────────┐  ┌───────────┐ │
│  │ postgres │←─│   app    │←─│  worker   │ │
│  │ :5432    │  │ :3050    │  │ (cron)    │ │
│  └──────────┘  └────┬─────┘  └───────────┘ │
│                     │                        │
└─────────────────────┼────────────────────────┘
                      │ 127.0.0.1:3050
                ┌─────┴─────┐
                │   Nginx   │ ← HTTPS
                └───────────┘
                      ↕
                  Internet