visionA/docker/Dockerfile.stage
jim800121chen 700b7b08ba chore(stage): 新增 v2 deploy 流程(remote build via DOCKER_HOST)
v1 (deploy-stage.sh) 走 docker save | gzip | docker load 模式,需要把 81MB
tarball 一次性 POST 到 stage docker daemon /images/load API。5/4 / 5/9 兩次
驗證 VPN 下 docker daemon 對單一大 POST hang(卡 30+ 分鐘 / i/o timeout),
公司網段直連才可靠。

v2 仿 edge-ai-platform/scripts/deploy-docker.sh 改用 DOCKER_HOST=stage
docker build — multi-stage build 完全在 stage daemon 上執行:
- 跨網路只傳 build context(~44 MB streaming,VPN 友善)
- alpine base / nodejs / go mod / pnpm install 都由 stage daemon 自己 pull
- layer cache 留在 stage daemon,後續 incremental build 更快
- 5/9 VPN 下實測 work:first build ~3min、redeploy(layer cache) ~10s

連帶修:
- pnpm-workspace.yaml: 加 onlyBuiltDependencies (sharp / unrs-resolver /
  @tailwindcss/oxide / esbuild) — pnpm 10 預設拒跑依賴 build script、
  乾淨環境第一次 install 撞 ERR_PNPM_IGNORED_BUILDS
- package.json: 加 packageManager: pnpm@10.30.1 — 鎖 pnpm 版本,corepack
  在 stage daemon 第一次跑時不會拉到最新 pnpm 11(行為差異)
- Dockerfile.stage: COPY pnpm-workspace.yaml 進 builder context、否則
  容器內 install 看不到 trust list

v1 (deploy-stage.sh) 保留作為公司網段直連備援;v2 是 VPN / 預設模式。
2026-05-11 10:35:21 +08:00

151 lines
6.9 KiB
Docker
Raw Permalink 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.

# syntax=docker/dockerfile:1.6
#
# visionA — stage 部署映像(單 container 內含 nginx + node + 兩個 Go binary
#
# 設計原則:
# - 仿 edge-ai-platform/docker/Dockerfile 的「nginx as front + 多 process 後端」模式
# - 但 visionA-frontend 採 Next.js `output: "standalone"`(不是 static export
# 因此最終 image 必須帶 node runtime 跑 `node server.js`,不能像 edge-ai 那樣
# 只 COPY out/ 給 nginx serve。權衡見 docs/STAGE-DEPLOY.md §「為什麼帶 node」。
# - Multi-stage buildbuilder 帶完整 toolchainruntime 只留 nginx + node-slim + 兩個 Go static binary
# - 最終 image 預估 ~150 MBnginx-alpine ~50MB + node:lts-alpine ~120MB - overlap
#
# Container 內 process由 entrypoint.stage.sh 啟動):
# - nginx :80 公司 host nginx 反代到此 port
# - node server.js :3000 Next.js standalone server內部
# - api-server :3721 REST + storage
# - remote-proxy :3800/3801 tunnel WS + internal HTTP
#
# Build在 visionA repo 根目錄):
# docker buildx build --platform linux/amd64 \
# -f docker/Dockerfile.stage \
# -t 192.168.0.130:5000/visiona:stage \
# --push .
#
# 本機驗證(不 push
# docker buildx build --platform linux/amd64 \
# -f docker/Dockerfile.stage \
# -t test/visiona:stage --load .
# ============================================================
# Stage 1 — frontend builder
# ============================================================
# 用 Next.js 16 標準的 standalone build產出 .next/standalone/ 與 .next/static/。
FROM node:22-alpine AS frontend-builder
# 啟用 corepack 以使用 repo 鎖定的 pnpm 版本。
# pnpm 9+ 對 lockfile v9 有要求;--frozen-lockfile 確保 CI 與本機一致。
RUN corepack enable
WORKDIR /src
# 先 COPY lockfile 讓依賴 layer 可以被 cache。
# pnpm-workspace.yaml 含 onlyBuiltDependencies trust listpnpm 10 不執行依賴 build script
# 必須明確 allow否則乾淨環境第一次 install 撞 ERR_PNPM_IGNORED_BUILDS
COPY visionA-frontend/package.json visionA-frontend/pnpm-lock.yaml visionA-frontend/pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile
# 複製其餘原始碼後 build。
COPY visionA-frontend/ ./
# Next.js 16 偵測到 output: "standalone" 後會輸出:
# .next/standalone/ → 含 server.js、必要 node_modules
# .next/static/ → 靜態 chunk要單獨 COPY 進 standalone dir
# public/ → 公開靜態資源(要單獨 COPY
RUN pnpm build
# ============================================================
# Stage 2 — backend builderapi-server + remote-proxy
# ============================================================
# CGO_ENABLED=0 → 純 Go static binaryalpine runtime 可直接執行。
FROM golang:1.26-alpine AS backend-builder
RUN apk add --no-cache git ca-certificates
WORKDIR /src
# 先 COPY go.mod / go.sum讓依賴 layer 可被 cache。
COPY visionA-backend/go.mod visionA-backend/go.sum ./
RUN go mod download
COPY visionA-backend/ ./
ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
RUN go build -trimpath -ldflags="-s -w" -o /out/api-server ./cmd/api-server && \
go build -trimpath -ldflags="-s -w" -o /out/remote-proxy ./cmd/remote-proxy
# ============================================================
# Stage 3 — runtimenginx + node + 兩個 binary
# ============================================================
# 用 nginx:alpine 為基底,再裝 node + tiniPID 1 reaper
# tini 確保子 process 殘留 / signal forwarding 正確。
FROM nginx:alpine AS runtime
# nodejs — Next.js standalone server runtimealpine 主 repo 當前版本Next.js 16 支援 18-24
# tini — 正確 PID 1處理 signal + zombie reap多 process container 必備)
# bash — entrypoint 用 wait -nalpine 預設 sh 不支援
# curl — healthcheck 用
# ca-certificates / tzdata — 標準 hardening
#
# 注意:不固定 nodejs 版本alpine repo 會隨時間升),如需固定改用 node:lts COPY 過來
RUN apk add --no-cache \
nodejs \
npm \
tini \
bash \
curl \
ca-certificates \
tzdata
# 建立非 root usernginx 子 process 與 node 都用這個)
# nginx 主 process 必須是 root要 bind :80→ master 由 root 跑、worker fall back nginx user
RUN addgroup -S -g 1001 visiona && \
adduser -S -u 1001 -G visiona visiona
# ──────────── nginx 設定 ────────────
# 把預設站台清掉,換成 visionA 的 server block
RUN rm -f /etc/nginx/conf.d/default.conf
COPY docker/nginx.stage.conf /etc/nginx/conf.d/default.conf
# ──────────── frontendNext.js standalone ────────────
# Next.js 16 standalone 結構:
# /var/www/visiona/standalone/server.js
# /var/www/visiona/standalone/node_modules/ ← 必需
# /var/www/visiona/standalone/.next/ ← server 內部需要
# /var/www/visiona/static/ ← 對齊 server.js 預期:./。next/static
# /var/www/visiona/public/ ← 公開資源
#
# server.js 預設讀 ./.next/static 與 ./public 相對於自己的位置,
# 所以 standalone/ 下要再 mkdir .next/static 與 ./public 軟連結(或直接 COPY 進 standalone 內)。
WORKDIR /var/www/visiona
COPY --from=frontend-builder --chown=visiona:visiona /src/.next/standalone/ ./standalone/
COPY --from=frontend-builder --chown=visiona:visiona /src/.next/static/ ./standalone/.next/static/
COPY --from=frontend-builder --chown=visiona:visiona /src/public/ ./standalone/public/
# ──────────── backend binaries ────────────
COPY --from=backend-builder --chown=visiona:visiona /out/api-server /usr/local/bin/api-server
COPY --from=backend-builder --chown=visiona:visiona /out/remote-proxy /usr/local/bin/remote-proxy
RUN chmod +x /usr/local/bin/api-server /usr/local/bin/remote-proxy
# ──────────── 共享資料目錄local-fs storage 雛形用) ────────────
# api-server 預設 VISIONA_STORAGE_LOCALFS_ROOT=/data/storage
# 由 docker-compose volume 掛 host 路徑進來image 預建 dir + chown 確保權限
RUN mkdir -p /data/storage && chown -R visiona:visiona /data
# ──────────── entrypoint ────────────
COPY docker/entrypoint.stage.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# nginx 對外只 listen :80由公司 host nginx 反代 :9527 → container :80。
EXPOSE 80
# Healthcheck — 打 nginx 的 /healthz由 nginx.stage.conf 直接回 200不打到 backend
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD curl -fsS http://127.0.0.1:80/healthz || exit 1
# tini 當 PID 1把 signal + 子 process 收屍正確化
ENTRYPOINT ["/sbin/tini", "--", "/entrypoint.sh"]