KB-348B

P10D-2D Deploy Discovery — Phase A only (STOP for decision)

9 min read Revision 1
p10ddeploydiscoverystop

P10D-2D — Deploy Discovery Report (Phase A only — STOP)

Date: 2026-04-30 Agent: Claude (Opus 4.7) via SSH contabo Verdict: DISCOVERY_ONLY_STOP — multiple mechanisms identified; awaiting user decision before activating.


Gate 0

Item Value
Control host Nguyens-MacBook-Air.local (nmhuyen)
VPS vmi3080463.contaboserver.net
Directus DB directus/directus (PostgreSQL)
Web repo /opt/incomex/docker/nuxt-repo
Git HEAD 5ce3437bf78db9989eafec14216fb5d2f2a26801 (target — already checked out)
HEAD commit "P10D: add parallel TAC official laws screen" — 2026-04-30 11:15 +0200
Working tree clean

Phase A — Cầu dao (deploy mechanism) inventory

A1. Container runtime

  • Container: incomex-nuxt, image nuxt-ssr-local:s174, status: Up 3 weeks (healthy)
  • Bind mount: /opt/incomex/deploys/nuxt-output/app/.output (rw)
  • CMD: node .output/server/index.mjs (reads from bind mount, NOT from image layer)
  • WORKDIR: /app

A2. Bind mount state — what's currently serving

  • /opt/incomex/deploys/nuxt-output/server/index.mjs mtime: 2026-04-02 16:37
  • 28 days old; predates HEAD commit 5ce3437 (2026-04-30) by 28 days
  • Backups present:
    • nuxt-output.prev: 2026-04-02 15:57 (same-day previous)
    • nuxt-output.prev2: 2026-03-08 05:05

A3. Local images on VPS

Image Created marker
nuxt-ssr-local:s174 (currently running) tag = s174
nuxt-ssr-local:latest exists
docker-nuxt:latest exists
asia-southeast1-docker.pkg.dev/.../nuxt-ssr:latest LEGACY (Artifact Registry API disabled S154)

A4. Dockerfile (nuxt-repo/web/Dockerfile)

  • Two-stage: node:20-alpine builder runs pnpm install --frozen-lockfile + pnpm run build
  • Production stage copies /app/.output from builder. .output/ IS baked into image at /app/.output.
  • However: bind mount in compose shadows the image's /app/.output at runtime → only bind mount contents matter.

A5. Build tools on VPS host

  • pnpm: NOT FOUND
  • node: NOT FOUND
  • docker: present
  • Disk: 55G free; RAM: 8.2G available — sufficient for docker build

A6. Compose file (/opt/incomex/docker/docker-compose.yml)

  • nuxt service hardcoded to image: nuxt-ssr-local:s174 (note: differs from infra/docker/docker-compose.yml in repo which uses ${NUXT_SSR_IMAGE:-...artifact-registry...})
  • Bind mount /opt/incomex/deploys/nuxt-output:/app/.output
  • The deployed compose file has DRIFTED from repo infra/docker/docker-compose.yml (image pin to nuxt-ssr-local:s174)

A7. Discovered deploy paths

Cầu dao 1 — GitHub Actions deploy-vps.yml job deploy-direct (DEPLOY_MODE=direct)

  • Active gate: vars.DEPLOY_MODE == 'direct'
  • Mechanism: GitHub runner builds .output/ (pnpm) → rsync -avz --delete web/.output/ → /opt/incomex/deploys/nuxt-output/docker compose restart nuxt → health check → auto-rollback to nuxt-output.prev on failure.
  • Also rsyncs: dot/bin, dot/config, scripts/integrity, contracts, sql migrations.
  • Also overwrites: /opt/incomex/docker/docker-compose.yml from repo infra/docker/docker-compose.yml (this would FLIP the image away from nuxt-ssr-local:s174 → would try to pull from disabled Artifact Registry → BREAK).
  • Trigger: workflow_run after Nuxt CI passes on main, or workflow_dispatch.
  • Evidence of past use: matches the .output/ produced 2026-04-02 + presence of nuxt-output.prev + DOT scripts on VPS.

Cầu dao 2 — GitHub Actions deploy-vps.yml job deploy-artifact (DEPLOY_MODE != direct)

  • Status: LEGACY — Artifact Registry API disabled S154 (per workflow comment).
  • Image used at runtime: nuxt-ssr-local:s174 (local), NOT asia-southeast1-docker.pkg.dev/.... So this path is NOT what produced current state.
  • Verdict: inactive cầu dao.

Cầu dao 3 — Manual host build (pnpm/node on VPS)

  • Status: NOT POSSIBLE — host has no pnpm/node.

Cầu dao 4 — Manual docker-build-on-VPS + extract .output/ + rsync to bind mount + restart

  • Status: technically feasible (docker present, Dockerfile bakes .output); has not been used historically (no evidence in deploy logs/scripts).
  • Risk: untested, off-pipeline, drifts from CI build context (env vars NUXT_PUBLIC_SITE_URL, etc., are baked at build time per Dockerfile ARG).

A8. Cầu dao table

# Cầu dao Evidence Last used Đang active? Activate 5ce3437?
1 GHA deploy-direct (DEPLOY_MODE=direct) matches .output/ 2026-04-02; rsync target = bind mount; auto-rollback uses .prev 2026-04-02 (28d ago) YES (pipeline) — but compose-file overwrite step would replace pinned nuxt-ssr-local:s174 with ${NUXT_SSR_IMAGE:-...artifact registry...} reference → risk of breaking on next restart YES — IF you accept GHA run + accept that compose drift would be re-introduced
2 GHA deploy-artifact (legacy) Artifact Registry disabled S154; running image is local unknown / never on this stack NO (inactive) NO (registry disabled)
3 Manual host pnpm build no pnpm/node on host never NO NO
4 Manual VPS docker build + extract + rsync + restart feasible; no prior evidence never NO (not in pipeline) YES technically — but off-pipeline and untested for this stack

Why I'm stopping

Per prompt rule:

Nhiều cầu dao → liệt kê + STOP báo cáo xin ý kiến. Không tự chọn.

Two viable cầu dao to activate 5ce3437:

  • Cầu dao 1 (GHA direct, established): correct mechanism, but I cannot trigger GitHub Actions from this SSH session — that's a GitHub-side action, not a VPS-side action. Also the workflow's compose-overwrite step is a side effect that wasn't authorized in this prompt.
  • Cầu dao 4 (manual VPS docker build): I CAN execute from SSH, but it's off-pipeline. The prompt says "Path 2 (build .output → rsync to nuxt-output → docker restart) chỉ được bật nếu Phase A xác nhận đây là cầu dao duy nhất đang active". It is not the cầu dao that has been active — Cầu dao 1 has been. So per the rule I should not auto-pick it.

Additional concern: the deployed /opt/incomex/docker/docker-compose.yml has drifted from repo infra/docker/docker-compose.yml (image pin). The GHA pipeline would re-sync repo compose and break the pin. This is a separate compose-gate question.


Decision needed (from user)

Pick one and confirm:

  1. Trigger GHA deploy-vps.yml (workflow_dispatch) — proper pipeline path. User must trigger from GitHub UI. Compose-overwrite side effect must be accepted or pre-resolved (the repo's infra/docker/docker-compose.yml would need its image line aligned with nuxt-ssr-local:s174 first, or you accept the registry pull will fail and cause auto-rollback).
  2. Manual VPS docker-build path — I'd run on VPS:
    • cd /opt/incomex/docker/nuxt-repo && docker build -t nuxt-ssr-local:p10d -f web/Dockerfile web/
    • extract /app/.output from image into /tmp/p10d-output
    • backup current /opt/incomex/deploys/nuxt-outputnuxt-output.bak.p10d-<ts>
    • rsync -a --delete /tmp/p10d-output/ /opt/incomex/deploys/nuxt-output/
    • docker restart incomex-nuxt
    • run Phase C verifies; rollback on fail
    • Risks: (a) Dockerfile build args pin NUXT_PUBLIC_SITE_URL/NUXT_PUBLIC_DIRECTUS_URL at build-time — needs --build-arg to match VPS prod URLs (https://vps.incomexsaigoncorp.vn / https://directus.incomexsaigoncorp.vn); (b) build will pull dependencies through host network; (c) image tag nuxt-ssr-local:p10d ≠ running tag s174 so container keeps running off s174 image but reads new .output/ from bind mount — same as direct-deploy semantics.
  3. Defer — investigate compose drift (docker-compose.yml vs infra/docker/docker-compose.yml) first.

I have NOT executed Phase B or Phase C. No backup created. No file modified on VPS.


Commands executed (read-only Phase A)

ssh contabo 'hostname -f && docker exec postgres psql -U directus -d directus -tAc "SELECT current_database() || chr(47) || current_user;"'
ssh contabo 'docker ps --format ... | grep -i nuxt'
ssh contabo 'docker inspect incomex-nuxt --format ... .Mounts/.Image/.Cmd/.WorkingDir'
ssh contabo 'ls/stat /opt/incomex/deploys/nuxt-output{,.prev,.prev2}/'
ssh contabo 'docker images | grep -i nuxt'
ssh contabo 'find /opt/incomex -maxdepth 5 ... | xargs grep -l ...'
ssh contabo 'grep -A 30 "^[[:space:]]*nuxt:" /opt/incomex/docker/docker-compose.yml'
ssh contabo 'cat /opt/incomex/docker/nuxt-repo/web/Dockerfile'
ssh contabo 'which pnpm node docker; df -h; free -h'
ssh contabo 'cd .../nuxt-repo && git log/status'
ssh contabo 'cat .../deploy-vps.yml'
ssh contabo 'grep -A 25 "nuxt" .../infra/docker/docker-compose.yml'

No mutations. No DB writes. No file changes.

Back to Knowledge Hub knowledge/dev/laws/dieu38-trien-khai/reports/p10d-2d-deploy-discovery-activate-2026-04-30.md