visionA/scripts/deploy-stage-v2.sh
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

214 lines
8.1 KiB
Bash
Executable File
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.

#!/usr/bin/env bash
#
# visionA — Stage Deploy v2remote build 模式,仿 edge-ai-platform/scripts/deploy-docker.sh
#
# 為什麼有 v2
# v1deploy-stage.sh走 docker save | gzip | docker load 模式,需要把 81MB
# tarball 一次性 POST 到 stage docker daemon `/images/load`。經驗證 VPN 下
# docker daemon 對單一大 POST 有 read timeout / hang 問題5/4 卡 30+ 分、
# 5/9 i/o timeout / 也卡 30 分),公司網段直連才可靠。
#
# v2 改用 `DOCKER_HOST=stage docker buildx build .` — multi-stage build
# 完全在 stage daemon 上執行:
# - 跨網路只傳 build context~44 MBstreaming 上傳VPN 友善)
# - alpine base / nodejs apk / go mod download / pnpm install 都是 stage
# daemon 自己 pull / 抓(公司內網 → docker hub / npm registry
# - layer cache 留在 stage daemon、後續 deploy 只重 build 改動的 layer
#
# 流程(仿 edge-ai-platform deploy-docker.sh
# 0. Pre-flightdocker buildx、git status
# 1. DOCKER_HOST=stage docker buildx buildmulti-stage 全在 stage 跑)
# 2. DOCKER_HOST=stage docker compose up -d同 v1
# 3. Verifycontainer status + healthz
#
# 跟 v1 (deploy-stage.sh) 的差別:
# - v1本機 buildx --load → save | gzip | load → compose up
# - v2直接 DOCKER_HOST=stage buildx build → compose up沒 save/load 步驟)
#
# 用法:
# bash scripts/deploy-stage-v2.sh # full deploy
# bash scripts/deploy-stage-v2.sh --skip-build # 不重 build只 compose up
# bash scripts/deploy-stage-v2.sh --no-deploy # build 完不 compose up驗證 build
# bash scripts/deploy-stage-v2.sh --help
set -euo pipefail
# ──────────── 路徑 ────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
DOCKERFILE="$PROJECT_ROOT/docker/Dockerfile.stage"
COMPOSE_FILE="$PROJECT_ROOT/docker-compose.stage.yml"
# ──────────── stage 設定 ────────────
DOCKER_REMOTE="${DOCKER_HOST:-tcp://192.168.0.130:2375}"
IMAGE_NAME="visiona"
IMAGE_TAG="stage"
IMAGE_REF="${IMAGE_NAME}:${IMAGE_TAG}"
STAGE_DOMAIN="stage-9527.innovedus.com"
STAGE_PORT="9527"
HEALTHZ_URL="https://${STAGE_DOMAIN}:${STAGE_PORT}/healthz"
# ──────────── colors ────────────
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
BLUE='\033[0;34m'
NC='\033[0m'
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
step() { echo -e "\n${CYAN}=== $* ===${NC}\n"; }
hint() { echo -e "${BLUE}[HINT]${NC} $*"; }
show_help() {
cat <<'HELP'
visionA — Stage Deploy v2 (remote build 模式)
Usage:
bash scripts/deploy-stage-v2.sh [OPTIONS]
Options:
--skip-build Skip build, just compose upimage 已 build 過)
--no-deploy Build only, do not compose up
--help Show this help
Environment:
DOCKER_HOST Remote Docker daemon (default: tcp://192.168.0.130:2375)
Examples:
bash scripts/deploy-stage-v2.sh
bash scripts/deploy-stage-v2.sh --skip-build
bash scripts/deploy-stage-v2.sh --no-deploy
After deployment:
https://stage-9527.innovedus.com:9527/
HELP
}
# ──────────── parse args ────────────
SKIP_BUILD=false
NO_DEPLOY=false
while [ $# -gt 0 ]; do
case "$1" in
--skip-build) SKIP_BUILD=true; shift ;;
--no-deploy) NO_DEPLOY=true; shift ;;
--help|-h) show_help; exit 0 ;;
*) error "Unknown option: $1 (use --help)" ;;
esac
done
# ──────────── pre-flight ────────────
step "0/3 Pre-flight checks"
command -v docker >/dev/null 2>&1 || error "docker 未安裝"
[ -f "$DOCKERFILE" ] || error "找不到 Dockerfile.stage$DOCKERFILE"
[ -f "$COMPOSE_FILE" ] || error "找不到 docker-compose.stage.yml$COMPOSE_FILE"
# 確認 stage daemon 連得上v2 必須能連、v1 也是)
info "確認 stage docker daemon 連線:$DOCKER_REMOTE"
if ! DOCKER_HOST="$DOCKER_REMOTE" docker version --format '{{.Server.Version}}' >/dev/null 2>&1; then
error "無法連到 stage docker daemon @ $DOCKER_REMOTE
提示:
- 公司內網需直連 192.168.0.130:2375
- VPN 下小流量 OKversion / images但大流量會卡
- v2 模式跨網路傳 ~44 MB build context比 v1 友善但仍非 100% 保證"
fi
STAGE_VERSION=$(DOCKER_HOST="$DOCKER_REMOTE" docker version --format '{{.Server.Version}}' 2>/dev/null || echo unknown)
info "stage daemon 連線 OKserver version: $STAGE_VERSION"
# git 狀態warn only
cd "$PROJECT_ROOT"
GIT_SHA="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
TIMESTAMPED_TAG="${IMAGE_TAG}-${TIMESTAMP}-${GIT_SHA}"
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
warn "git working tree 有未 commit 的變更stage build 仍會繼續)"
fi
info "Project root : $PROJECT_ROOT"
info "Docker remote : $DOCKER_REMOTE"
info "Image : $IMAGE_REF"
info "Timestamp tag : ${IMAGE_NAME}:${TIMESTAMPED_TAG}"
info "Git SHA : $GIT_SHA"
# ──────────── build (在 stage daemon 上) ────────────
if [ "$SKIP_BUILD" = false ]; then
step "1/3 Remote build (DOCKER_HOST=stage docker buildx build)"
info "build 完全在 stage daemon 上執行;只跨網路傳 build context (~44 MB streaming)"
# 用 buildxbuildx 預設 builder 是 default = remote daemon driver
# 不需要 --load / --pushbuild 完 image 直接留在 stage daemon
# 不需要 --platformDOCKER_HOST 指向 stage daemon、stage 是 linux/amd64、native build
cd "$PROJECT_ROOT"
DOCKER_HOST="$DOCKER_REMOTE" docker build \
-f "$DOCKERFILE" \
-t "${IMAGE_NAME}:${IMAGE_TAG}" \
-t "${IMAGE_NAME}:${TIMESTAMPED_TAG}" \
.
info "build 完成 — stage daemon 上已有 ${IMAGE_NAME}:${IMAGE_TAG} + ${IMAGE_NAME}:${TIMESTAMPED_TAG}"
else
info "skip 1/3--skip-build— 假設 ${IMAGE_NAME}:${IMAGE_TAG} 已存在於 stage daemon"
fi
# ──────────── deploy ────────────
if [ "$NO_DEPLOY" = true ]; then
info "skip 2/3, 3/3--no-deploy"
info "下一步:執行 bash scripts/deploy-stage-v2.sh --skip-build 完成 deploy"
exit 0
fi
step "2/3 Deploy via docker compose"
DOCKER_HOST="$DOCKER_REMOTE" docker compose \
-f "$COMPOSE_FILE" \
--project-directory "$PROJECT_ROOT" \
-p visiona-stage \
up -d --remove-orphans
info "container 已啟動,等 healthcheck最多 60s..."
# ──────────── verify ────────────
step "3/3 Verify"
DOCKER_HOST="$DOCKER_REMOTE" docker ps --filter name=visiona --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
deadline=$(( $(date +%s) + 60 ))
healthy=false
status=unknown
while [ "$(date +%s)" -lt "$deadline" ]; do
status=$(DOCKER_HOST="$DOCKER_REMOTE" docker inspect --format='{{.State.Health.Status}}' visiona 2>/dev/null || echo "unknown")
if [ "$status" = "healthy" ]; then
healthy=true
break
fi
sleep 3
done
if [ "$healthy" = true ]; then
info "container healthy ✓"
else
warn "container healthcheck 超時status=$status)— 注意 5/1 已知 healthcheck 從 container 內被 nginx server_name 擋成 444 是 false negative外部 healthz 才是真實狀態"
fi
if curl -fsS --max-time 10 "$HEALTHZ_URL" >/dev/null 2>&1; then
info "對外 healthz ✓ ($HEALTHZ_URL)"
else
warn "對外 healthz 失敗:$HEALTHZ_URL"
warn "查看 log: DOCKER_HOST=$DOCKER_REMOTE docker logs visiona --tail 100"
fi
echo ""
echo -e "${GREEN}=== Deploy v2 完成 ===${NC}"
echo ""
info "URL : https://${STAGE_DOMAIN}:${STAGE_PORT}/"
info "Logs: DOCKER_HOST=$DOCKER_REMOTE docker logs -f visiona"
echo ""
hint "Rollback hintDOCKER_HOST=$DOCKER_REMOTE docker tag visiona:stage-<old-tag> visiona:stage && bash scripts/deploy-stage-v2.sh --skip-build"
echo ""