Docker 模式
適用於容器化開發的 Docker 與 Docker Compose 最佳實踐。
本地開發的 Docker Compose
標準網頁應用程式堆疊
# docker-compose.yml
services:
app:
build:
context: .
target: dev # 使用多階段 Dockerfile 的 dev 階段
ports:
- "3000:3000"
volumes:
- .:/app # 繫結掛載,支援熱重載
- /app/node_modules # 匿名磁碟區,保留容器內的依賴
environment:
- DATABASE_URL=postgres://postgres:postgres@db:5432/app_dev
- REDIS_URL=redis://redis:6379/0
- NODE_ENV=development
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
command: npm run dev
db:
image: postgres:16-alpine
ports:
- "5432:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: app_dev
volumes:
- pgdata:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redisdata:/data
mailpit: # 本地郵件測試
image: axllent/mailpit
ports:
- "8025:8025" # 網頁介面
- "1025:1025" # SMTP
volumes:
pgdata:
redisdata:
開發 vs 生產環境的 Dockerfile
# Stage: dependencies
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Stage: dev (熱重載、除錯工具)
FROM node:22-alpine AS dev
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
# Stage: build
FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build && npm prune --production
# Stage: production (最小映像)
FROM node:22-alpine AS production
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && adduser -S appuser -u 1001
USER appuser
COPY --from=build --chown=appuser:appgroup /app/dist ./dist
COPY --from=build --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=build --chown=appuser:appgroup /app/package.json ./
ENV NODE_ENV=production
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
覆蓋檔案
# docker-compose.override.yml (自動載入,僅開發模式設定)
services:
app:
environment:
- DEBUG=app:*
- LOG_LEVEL=debug
ports:
- "9229:9229" # Node.js 除錯器
# docker-compose.prod.yml (明確指定生產環境)
services:
app:
build:
target: production
restart: always
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
# 開發環境 (自動載入覆蓋)
docker compose up
# 生產環境
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
網路
服務發現
相同 Compose 網路中的服務可透過服務名稱解析:
# 從 "app" 容器:
postgres://postgres:postgres@db:5432/app_dev # "db" 解析到 db 容器
redis://redis:6379/0 # "redis" 解析到 redis 容器
自訂網路
services:
frontend:
networks:
- frontend-net
api:
networks:
- frontend-net
- backend-net
db:
networks:
- backend-net # 僅 api 可連線,frontend 無法
networks:
frontend-net:
backend-net:
僅暴露需要的埠
services:
db:
ports:
- "127.0.0.1:5432:5432" # 僅主機能存取,對外網路不可見
# 正式環境省略 ports 設定——僅能在 Docker 網路內存取
磁碟區策略
volumes:
# 命名磁碟區:跨容器重啟持續存在,由 Docker 管理
pgdata:
# 繫結掛載:將主機目錄對應到容器內 (用於開發)
# - ./src:/app/src
# 匿名磁碟區:保留容器產生的內容,避免被繫結掛載覆蓋
# - /app/node_modules
常見模式
services:
app:
volumes:
- .:/app # 原始碼 (繫結掛載,支援熱重載)
- /app/node_modules # 保護容器的 node_modules 不受主機影響
- /app/.next # 保護建置快取
db:
volumes:
- pgdata:/var/lib/postgresql/data # 持久化資料
- ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql # 初始化腳本
容器安全
Dockerfile 強化
# 1. 使用明確標籤 (永遠不要用 :latest)
FROM node:22.12-alpine3.20
# 2. 以非 root 執行
RUN addgroup -g 1001 -S app && adduser -S app -u 1001
USER app
# 3. 移除權限 (在 compose 中設定)
# 4. 盡可能使用唯讀根檔案系統
# 5. 映像層中不包含機密
Compose 安全設定
services:
app:
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
- /app/.cache
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # 僅在綁定低於 1024 的埠時才需要
機密管理
# 良好:使用環境變數 (執行階段注入)
services:
app:
env_file:
- .env # 絕對不要將 .env 提交到 git
environment:
- API_KEY # 繼承自主機環境
# 良好:Docker 機密 (Swarm 模式)
secrets:
db_password:
file: ./secrets/db_password.txt
services:
db:
secrets:
- db_password
# 不良:在映像中寫死
# ENV API_KEY=sk-proj-xxxxx # 絕對不要這樣做
強化 CLI 安裝器測試框架
使用容器在不影響原始專案的情況下,於可複製的專案拷貝中測試安裝器的行為。
尊重平台邊界
- 對 Linux 發行版(如 Debian 和 Ubuntu)執行真實容器。
- macOS 無法作為 Docker 容器執行,因為 Docker 共用 Linux 核心。請原生地在 macOS 上執行相同的無 shell 測試入口點。
- Windows 容器需要 Windows Docker 引擎。在原生 Windows CI 執行器上執行與平台無關的邏輯,並僅為 Windows 主機保留 Windows 容器。
- 保留原生 Ubuntu/macOS/Windows CI 矩陣,以測試主機特定路徑、命令別名、引號和檔案系統行為。
不要聲稱 Linux 容器可以驗證 macOS 或 Windows 的行為。
執行隔離合約
- 以不可變摘要標籤鎖定基底映像,並鎖定安裝的 CLI 版本。
- 當發行版的使用者帳戶名稱不同時,以非 root 的數值 UID/GID 執行。
- 唯讀掛載存放庫和原始專案。
- 在進行任何變更之前,將原始專案複製到可寫入的
tmpfs工作區。 - 以
noexec、UID/GID 1000 和mode=0700掛載/workspace,確保只有容器使用者可以檢查專案資料。 - 將 npm 和 npx 的可執行快取設定在
NPM_CONFIG_CACHE=/tmp/npm-cache的可執行/tmp掛載點。其預設大小為 2 GiB,可透過ECC_TMPFS_SIZE調整;ECC_WORKSPACE_SIZE則單獨控制私有工作區掛載。 - 設定
read_only: true、no-new-privileges:true、cap_drop: [ALL],以及有限的pids_limit。 - 將預設的真實 CLI 服務置於
network_mode: none。僅透過一個明顯命名的選擇性服務,為已驗證的提供者工作階段新增網路存取;永遠不要讓它成為意外依賴環境變數的預設行為。 - 僅建立工具所需的可寫入暫存路徑。
- 預設不要將主機憑證傳入容器。
- 預設為試運行,並僅允許明確的
dry-run、install、plugin和shell模式。 - 對跨平台執行器使用引數陣列或
spawnSync(..., { shell: false })。永遠不要將專案路徑內插到 shell 指令中。
演練 ECC 外掛設定框架
使用 docker/plugin-setup/compose.yaml 作為參考實作。它提供了:
fixture-tests用於聚焦的安裝清單、目標和執行器套件。real-cli用於鎖定的基於 Debian 的通用 Linux 映像。real-cli-ubuntu用於鎖定的 Ubuntu 映像。
在建置之前驗證 Compose 模型:
docker compose -f docker/plugin-setup/compose.yaml config --quiet
建置兩個真實的 Linux 映像:
docker compose -f docker/plugin-setup/compose.yaml \
build real-cli real-cli-ubuntu
在每個映像中執行安全的預設流程:
docker compose -p ecc-plugin-debian-test \
-f docker/plugin-setup/compose.yaml \
run --rm -T real-cli dry-run
docker compose -p ecc-plugin-ubuntu-test \
-f docker/plugin-setup/compose.yaml \
run --rm -T real-cli-ubuntu dry-run
試運行執行目前的公開指令合約:
ecc install --profile core --target claude-project --dry-run --json
在執行該指令之前,容器會使用 npm pack --ignore-scripts 從唯讀的檢出中建立本地打包的 npm 構件。它將自建的 tarball 解壓縮到 /tmp 下,驗證 ecc-universal 套件名稱、必要的安裝清單和限制的 package.json bin.ecc 對應,然後呼叫解壓縮後的 ecc 可執行檔。執行環境保持在 network_mode: none,不執行套件生命週期腳本,也不依賴主機的 node_modules;其鎖定的確切生產依賴項已存在於映像中。
該框架拒絕空白的計劃、非 claude-project 的目標、在 /workspace/project/.claude 之外的任何操作,或任何會建立目標目錄的試運行。install 會執行兩次隔離的應用,檢查其管理的安裝狀態,列出已安裝的目標,並執行 doctor。
啟動、打開、重新連接和清理命名的工作階段
啟動一個分離的容器(不使用 --rm),以便離開終端時不會移除工作階段:
docker compose -p ecc-plugin-session \
-f docker/plugin-setup/compose.yaml \
run --detach --name ecc-plugin-shell real-cli shell
容器將唯讀的固定資料複製到穩定的私有目錄 /workspace/project。確認它正在執行,然後輸出 Docker 端的終端打開器 v1 資料合約:
docker inspect --format '{{.State.Running}}' ecc-plugin-shell
node docker/plugin-setup/interactive-plan.js \
--container ecc-plugin-shell \
--workdir /workspace/project \
--json \
-- bash
JSON 結果包含一個 executable 和 argv 邊界(加上 contractVersion: 1):可執行檔是 docker,而 argv 以 exec、-it 和 -w 開頭。將這些資料傳遞給已安裝的獨立終端打開器技能。此 Docker 框架故意不匯入終端轉接器、不內插 shell 指令,也不管理主機 GUI 程序。在此之前,直接在同一個主機終端中打開相同的 PTY:
docker exec -it -w /workspace/project ecc-plugin-shell bash
退出 shell 而不停止分離的容器。使用相同的 docker exec -it 指令重新連接。完成後,移除精確命名的容器及其 Compose 專案資源:
docker rm --force ecc-plugin-shell
docker compose -p ecc-plugin-session \
-f docker/plugin-setup/compose.yaml \
down --remove-orphans
主機憑證預設不存在,且憑證目錄也從不掛載。預設服務也沒有網路存取。當已驗證的提供者工作階段確實需要網路時,先建置 real-cli,然後透過 docker compose --profile networked run real-cli-networked shell 明確選擇加入。更傾向在該可拋棄的工作階段內進行驗證。如果 CI 執行必須繼承主機環境憑證,請在呼叫時使用明確的 Compose --env NAME 標誌選擇加入,並了解該值是可檢查的,且在容器生命週期內可能被竊取,並在完成後立即移除精確命名的容器。
在主機上原生執行相同的集中套件:
npm run test:plugin-setup-platform
在信任映像之前檢查其產生的身分和環境:
docker image inspect ecc-plugin-setup:debian ecc-plugin-setup:ubuntu
清理每個命名的測試專案,而不刪除不相關的磁碟區或映像:
docker compose -p ecc-plugin-debian-test \
-f docker/plugin-setup/compose.yaml down --remove-orphans
docker compose -p ecc-plugin-ubuntu-test \
-f docker/plugin-setup/compose.yaml down --remove-orphans
.dockerignore
node_modules
.git
.env
.env.*
dist
coverage
*.log
.next
.cache
docker-compose*.yml
Dockerfile*
README.md
tests/
除錯
常用指令
# 檢視日誌
docker compose logs -f app # 跟隨 app 日誌
docker compose logs --tail=50 db # 顯示 db 最近 50 行
# 在執行中的容器執行指令
docker compose exec app sh # 進入 app 的 shell
docker compose exec db psql -U postgres # 連線到 postgres
# 檢查
docker compose ps # 執行中的服務
docker compose top # 每個容器內的行程
docker stats # 資源使用情況
# 重建
docker compose up --build # 重建映像
docker compose build --no-cache app # 強制完整重建
# 清理
docker compose down # 停止並移除容器
docker compose down -v # 同時移除磁碟區 (破壞性操作)
docker system prune # 移除未使用的映像/容器
除錯網路問題
# 檢查容器內的 DNS 解析
docker compose exec app nslookup db
# 檢查連線能力
docker compose exec app wget -qO- http://api:3000/health
# 檢查網路
docker network ls
docker network inspect <project>_default
反模式
# 不良:使…
[為翻譯而截斷]
注意:原始內容在反模式部分被截斷。請參考完整來源以獲得更多資訊。