6a7d98b0fe
Run Tests / test (pull_request) Successful in 32s
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
187 lines
4.8 KiB
YAML
187 lines
4.8 KiB
YAML
|
||
services:
|
||
# PHP-FPM 服務
|
||
app:
|
||
# 構建配置
|
||
build:
|
||
context: . # 使用當前目錄作為構建上下文
|
||
dockerfile: Dockerfile # 指定 Dockerfile 路徑
|
||
target: app # 明確指定 build target,避免預設建到最後一個 stage(web/nginx)
|
||
image: cfdive-platform # 構建的鏡像名稱
|
||
container_name: cfdive-app # 容器名稱
|
||
restart: unless-stopped # 自動重啟策略:除非手動停止,否則自動重啟
|
||
working_dir: /var/www/ # 工作目錄
|
||
|
||
# 上傳檔案在 image 重建後仍保留(過渡方案,待正式切 S3 後可移除)
|
||
volumes:
|
||
- storage-app-public:/var/www/storage/app/public
|
||
|
||
# 環境變數:以 env_file 注入 .env 內容,不會在容器內生成實體 .env 檔案
|
||
env_file:
|
||
- .env
|
||
|
||
# 網絡配置
|
||
networks:
|
||
- cfdive-network # 連接到自定義網絡
|
||
- proxy_net
|
||
|
||
# 依賴關係:確保 db 和 redis 服務先啟動
|
||
depends_on:
|
||
db: # 依賴 db 服務
|
||
condition: service_healthy # 等待數據庫健康檢查通過
|
||
redis: # 依賴 redis 服務
|
||
condition: service_started # 等待服務啟動
|
||
|
||
# 健康檢查配置
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "nc -z 127.0.0.1 9000 || exit 1"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 3
|
||
start_period: 40s
|
||
|
||
nginx:
|
||
# 從 Dockerfile 的 web 階段本地建置,取得 public/ 靜態資源,
|
||
# 不再拉取官方 nginx:alpine image、也不依賴任何執行期共享機制取得程式碼
|
||
build:
|
||
context: .
|
||
dockerfile: Dockerfile
|
||
target: web
|
||
image: cfdive-nginx-web
|
||
container_name: cfdive-nginx
|
||
restart: unless-stopped
|
||
ports:
|
||
- "127.0.0.1:8080:80"
|
||
volumes:
|
||
- storage-app-public:/var/www/storage/app/public
|
||
networks:
|
||
- cfdive-network
|
||
- proxy_net
|
||
depends_on:
|
||
app:
|
||
condition: service_healthy
|
||
healthcheck:
|
||
# busybox wget 對 localhost 會先解析到 IPv6 ::1,但 nginx 只 listen IPv4,須用 127.0.0.1
|
||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1/"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 3
|
||
|
||
frontend:
|
||
build:
|
||
context: ./frontend
|
||
dockerfile: Dockerfile
|
||
args:
|
||
VITE_API_URL: ${VITE_API_URL:-https://api.hank-space.com}
|
||
VITE_REVERB_APP_KEY: ${VITE_REVERB_APP_KEY}
|
||
VITE_REVERB_HOST: ${VITE_REVERB_HOST:-localhost}
|
||
VITE_REVERB_PORT: ${VITE_REVERB_PORT:-8085}
|
||
VITE_REVERB_SCHEME: ${VITE_REVERB_SCHEME:-http}
|
||
image: cfdive-frontend
|
||
container_name: cfdive-frontend
|
||
restart: unless-stopped
|
||
ports:
|
||
- "127.0.0.1:5173:80"
|
||
networks:
|
||
- cfdive-network
|
||
- proxy_net
|
||
|
||
db:
|
||
image: mysql:8.0
|
||
container_name: cfdive-db
|
||
restart: unless-stopped
|
||
environment:
|
||
MYSQL_DATABASE: ${DB_DATABASE:-CFDivePlatform}
|
||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
||
MYSQL_USER: ${DB_USERNAME:-cfdiveuser}
|
||
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||
SERVICE_TAGS: dev
|
||
SERVICE_NAME: mysql
|
||
volumes:
|
||
- mysql-data:/var/lib/mysql
|
||
networks:
|
||
- cfdive-network
|
||
healthcheck:
|
||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 30s
|
||
ports:
|
||
- "127.0.0.1:3306:3306" # 只綁 localhost,不對外
|
||
|
||
|
||
scheduler:
|
||
image: cfdive-platform
|
||
container_name: cfdive-scheduler
|
||
restart: unless-stopped
|
||
working_dir: /var/www/
|
||
command: php artisan schedule:work
|
||
env_file:
|
||
- .env
|
||
networks:
|
||
- cfdive-network
|
||
depends_on:
|
||
app:
|
||
condition: service_healthy
|
||
|
||
queue-worker:
|
||
image: cfdive-platform
|
||
container_name: cfdive-queue
|
||
restart: unless-stopped
|
||
working_dir: /var/www/
|
||
command: php artisan queue:work --sleep=3 --tries=3 --timeout=60
|
||
env_file:
|
||
- .env
|
||
networks:
|
||
- cfdive-network
|
||
depends_on:
|
||
app:
|
||
condition: service_healthy
|
||
|
||
|
||
redis:
|
||
image: redis:alpine
|
||
container_name: cfdive-redis
|
||
restart: unless-stopped
|
||
networks:
|
||
- cfdive-network
|
||
volumes:
|
||
- redis-data:/data
|
||
healthcheck:
|
||
test: ["CMD", "redis-cli", "ping"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 3
|
||
|
||
reverb:
|
||
image: cfdive-platform
|
||
container_name: cfdive-reverb
|
||
restart: unless-stopped
|
||
working_dir: /var/www/
|
||
entrypoint: ["php", "artisan", "reverb:start", "--host=0.0.0.0", "--port=8080"]
|
||
ports:
|
||
- "127.0.0.1:8085:8080"
|
||
env_file:
|
||
- .env
|
||
networks:
|
||
- cfdive-network
|
||
- proxy_net
|
||
depends_on:
|
||
app:
|
||
condition: service_healthy
|
||
|
||
networks:
|
||
cfdive-network:
|
||
driver: bridge
|
||
proxy_net:
|
||
external: true
|
||
|
||
volumes:
|
||
mysql-data:
|
||
driver: local
|
||
redis-data:
|
||
driver: local
|
||
storage-app-public:
|
||
driver: local
|