Files
CFDivePlatform/openspec/changes/bake-image-remove-bind-mount/proposal.md
T
a620906209 6a7d98b0fe
Run Tests / test (pull_request) Successful in 32s
feat(docker): 移除 bind mount,程式碼與 vendor 烘進 image
Dockerfile 改 multi-stage(app + web),app/scheduler/queue-worker/
reverb/nginx 五個服務都不再依賴 ./:/var/www bind mount 取得程式碼;
nginx 改本地 build 從 app 階段 COPY --from 取得 public/,public/storage
symlink 在兩階段各自於 build 時建立。docker-compose.yml 新增具名 volume
storage-app-public 讓上傳檔案跨 image 重建保留;env_file: 取代原本
bind mount 帶入 .env 的方式,只給四個 Laravel runtime 服務。
compose.override.yml 補上開發用 bind mount 維持改 code 立即生效;
docker-entrypoint.sh 收斂到只剩目錄權限設定,migration/cache/swagger
移到 deploy.yml 統一負責,避免職責重疊。

實作驗證時發現並修復三個規劃階段沒預見的問題:app 服務的 build: 缺
target: app 導致預設建到最後一個 stage(image 實際變成 nginx);
.dockerignore 需排除本機既有的 public/storage 符號連結(Windows 上
會讓 build context 打包失敗);env_file: 讓 .env 全部變數變成真實
環境變數、蓋掉 phpunit.xml 的測試隔離設定,已擴大 tests/bootstrap.php
的強制覆蓋清單修復(239 tests 全綠)。

本機 dev 模式與 production 模式(無 override)皆完整驗證:healthcheck、
migration/cache/swagger、上傳檔案持久性、nginx 獨立於 app 服務靜態檔
與 storage 檔案、.env 熱更新、nginx 無 .env 存取權。

openspec change: bake-image-remove-bind-mount

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0142LC1kQb8EyV59TjHDkhWS
2026-08-04 16:59:18 +08:00

28 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Why
目前 `cfdive-platform` image 是空殼——`Dockerfile` 沒有 `COPY . /var/www`,程式碼完全靠 `docker-compose.yml` 對 app、nginx、scheduler、queue-worker、reverb 五個服務的 `./:/var/www` bind mount 從主機借過去。這代表 image 本身不含可執行的程式碼,無法搬到另一台機器獨立運作,也不是可版本化、可回溯的部署單位。P1(log 改 stderr、session 改 Redis)與 `file-storage-s3`(上傳檔案可切到 S3)已經拆掉了「container 必須綁死同一台主機磁碟」的兩個主要理由,現在是把程式碼真正烘進 image、讓 container 變成自足部署單位的時機,也是未來水平擴容的前提。
## What Changes
- **Dockerfile 改為 multi-stage**`app` 階段(`php:8.2-fpm`)統一策略為「先 COPY composer manifests → `composer install --no-scripts` → COPY 全專案 → 補跑 `composer dump-autoload --optimize`(不重複加 `--no-dev`,該旗標已在 install 階段決定 vendor 內容)與 `php artisan package:discover --ansi``--no-scripts` 跳過的 composer hook 手動補上)→ 執行 `ln -sfn ../storage/app/public public/storage` 建立符號連結」;新增 `web` 階段(`nginx:alpine`)以 `COPY --from=app /var/www/public /var/www/public` 取得 Laravel 的 `public/` 目錄,並**再次獨立執行**同一條 `ln -sfn` 指令建立自己的符號連結(不依賴 `COPY --from` 隱性帶過去,兩階段各自明確宣告)。新增 `.dockerignore` 排除 `vendor/``node_modules/``.env``storage/app/public/*`,避免機敏資訊或本機殘留檔案被烘進 image——這是本次的正確性要求,不是可選優化。
- **docker-compose.yml**:移除 app、nginx、scheduler、queue-worker、reverb 五個服務的 `./:/var/www` bind mount`nginx` 服務改從 `image: nginx:alpine`(直接拉取)改為本地 build`target: web`image 命名 `cfdive-nginx-web`)取得程式碼,不再依賴任何執行期共享機制。**BREAKING**:改 code 不再瞬間生效,必須重新 build image 才能部署新版本。
- **上傳檔案(storage)處理方式**:本次變更**不強制**先切換到 S3——決定改用具名 volume `storage-app-public`(已定名),掛載至 `app``nginx` 服務容器內的絕對路徑 `/var/www/storage/app/public`,讓上傳檔案在 image 重建後仍保留;待日後正式切到 `FILESYSTEM_DISK=s3` 後,此 volume 可再移除(留給後續變更處理,不在本次範圍)。
- **`.gitea/workflows/deploy.yml`**:部署流程從「`git reset --hard` 改主機檔案 → `docker compose exec app composer install`」改為「`git reset --hard``docker compose build``docker compose up -d`」,並成為 migration`config:cache``route:cache``view:cache``event:cache``l5-swagger:generate` 這些「部署一次新版本」動作的**唯一**執行位置。**BREAKING**:部署時間變長(每次都要重建 image),不再有「改 code 幾乎瞬間生效」的能力。
- **`.env` 傳遞方式**:拿掉 bind mount 後 `.env` 不能再依賴主機檔案直接出現在容器裡,**只在 `app``scheduler``queue-worker``reverb` 四個 Laravel runtime 服務**改用 YAML list 格式的 `env_file:``env_file:\n - .env`)指向主機上的 `.env` 路徑——`env_file:` 是把檔案內容以環境變數形式注入容器程序,**不會**在容器內生成一份實體 `.env` 檔案。`nginx` 服務**不**加 `env_file:`,它不需要讀取任何 `.env` 內容,避免不必要的機敏資訊暴露。
- **`docker-entrypoint.sh`**:移除「composer.lock 內容比對後才 composer install」「.env 不存在則從 .env.example 複製 + key:generate」「強制改寫 DB_HOST」三段假設容器內是 bind mount 主機目錄的邏輯;同時**移除 migrationcache clearswagger 生成**(改由 deploy.yml 統一負責,避免與 deploy.yml 職責重疊、也避免 app/scheduler/queue-worker 三個服務各自啟動時重複跑)與**移除 `storage:link`**symlink 已在 image build 階段建立,執行期不需要)。entrypoint 最終只保留目錄與權限設定這項啟動必要工作。
## Capabilities
### New Capabilities
- `image-baked-deployment`:涵蓋 image 建置時內嵌程式碼與 vendor、compose 移除 bind mount、部署流程改為 build-then-up、`.env` 改用 env_file 傳遞、entrypoint 職責調整後的完整行為規格。
### Modified Capabilities
- `scheduler-container`:現有 requirement「Scheduler 以獨立 container 執行」隱含透過 bind mount 取得程式碼,需更新為透過 image 內建程式碼執行,不再掛 `./:/var/www`
## Impact
- **受影響檔案**`Dockerfile`(改為 multi-stage`app` + `web`)、`.dockerignore`(新增,含 `public/storage` 排除本機既有符號連結)、`docker-compose.yml``app` 服務需明確 `target: app`,否則預設建到最後一個 stage)、`.gitea/workflows/deploy.yml``docker/php/docker-entrypoint.sh``compose.override.yml`;另因 `env_file:``.env` 全部變數成為真實環境變數而牽動 `tests/bootstrap.php``phpunit.xml`(擴大既有測試環境隔離的強制覆蓋範圍,非新增機制,詳見 design.md 實作驗證後追加)
- **受影響服務**app、nginx(改為本地 build 而非拉取 `nginx:alpine`)、scheduler、queue-worker、reverb(全部五個掛 bind mount 的服務)
- **受影響流程**VPS 部署流程(Gitea Actions self-hosted runner)、本機開發流程(開發者需改用 `docker compose build` 才能讓程式碼變動生效,或另外規劃開發環境仍用 bind mount 的 override 方案)
- **不受影響**`file-storage-s3`(本次不強制切換到 S3,上傳檔案改走具名 volume 過渡)、`compose-cloud-baseline`healthcheck/開發工具隔離邏輯不變)