5.8 KiB
Deployment Runbook
Scope
- Runtime:
console.svc.plus - Topology:
Caddy + Docker Compose + GitHub Actions - Deploy host:
root@47.120.61.35 - Public domains:
https://cn.svc.plushttps://cn.onwalk.net
- Primary origin:
https://cn.svc.plus
Current Delivery Model
The production frontend is deployed as a prebuilt container image from GitHub Actions.
- The target host does not build images locally.
- The workflow builds an
linux/amd64image and pushes it toghcr.io/<owner>/dashboard:<sha>. - The host only performs
docker login,docker compose pull, static asset extraction, anddocker compose up. /docsand/blogsfetch their content fromdocs.svc.plusat runtime; the frontend image no longer packsknowledge/or synced markdown payloads.- Static assets are extracted from the image into a shared Docker volume so Caddy can serve
/_next/static/*and checked-in public files directly.
This is intentionally static-first for the current weak-IO single-node host. Dynamic HTML, auth routes, and API proxy routes still run through the Next.js container, but docs/blog content delivery is now delegated to docs.svc.plus.
Control Plane & DNS Stage
The control repo (github-org-x-evor) tracks console.svc.plus through console.svc.plus.code-workspace and keeps the subrepos/accounts.svc.plus pointer in sync via skills/cross-repo-upstream-submodule-sync. Releases resolve metadata with that workspace and the config/single-node-release manifests. After .github/workflows/service_release_frontend-deploy.yml finishes pushing the new image, the control-plane workflow .github/workflows/service_release_apiserver-deploy.yml calls scripts/github-actions/update-release-dns.sh to update Cloudflare DNS so the new endpoint is reachable under cn.svc.plus and cn.onwalk.net.
Runtime Layout
Remote directory:
/opt/console-svc-plus
Files deployed there:
docker-compose.yml
Caddyfile
.env.runtime
Containers:
dashboard: Next.js standalone runtime on port3000frontend-assets: one-shot task that copiesstatic/andpublic/into a shared volumecaddy: TLS termination and reverse proxy
GitHub Actions Inputs
Workflow:
.github/workflows/service_release_frontend-deploy.yml
Secrets required:
SINGLE_NODE_VPS_SSH_PRIVATE_KEYOPENCLAW_GATEWAY_TOKENif usedVAULT_TOKENif usedAI_GATEWAY_ACCESS_TOKENif usedINTERNAL_SERVICE_TOKENif usedCLOUDFLARE_DNS_API_TOKENfor the Cloudflare DNS stageCLOUDFLARE_API_TOKENif homepage Cloudflare analytics are enabled at runtime
Repository/environment variables recommended:
APP_BASE_URLNEXT_PUBLIC_APP_BASE_URLNEXT_PUBLIC_SITE_URLNEXT_PUBLIC_LOGIN_URLNEXT_PUBLIC_DOCS_BASE_URLACCOUNT_SERVICE_URLNEXT_PUBLIC_ACCOUNT_SERVICE_URLSERVER_SERVICE_URLNEXT_PUBLIC_SERVER_SERVICE_URLRUNTIME_HOSTNAMEDEPLOYMENT_HOSTNAMEDOCS_SERVICE_URLDOCS_SERVICE_INTERNAL_URLNEXT_PUBLIC_RUNTIME_ENVIRONMENTNEXT_PUBLIC_RUNTIME_REGIONNEXT_PUBLIC_GISCUS_*NEXT_PUBLIC_STRIPE_*NEXT_PUBLIC_PAYPAL_CLIENT_IDCLOUDFLARE_ZONE_TAGif homepage Cloudflare analytics are enabled at runtimeCLOUDFLARE_DNS_ZONE_TAGonly for single-domain manual DNS override; the GitHub Actions DNS stage resolves zones from each domain automatically
Release Flow
- GitHub Actions checks out the repo.
- Docker builds the frontend image with the public
NEXT_PUBLIC_*values needed at build time. - The image is pushed to GHCR.
- The workflow runs a matrix DNS stage, updating one public domain per job.
- The workflow renders
.env.runtime, including docs service runtime endpoints. - The workflow uploads
docker-compose.yml,Caddyfile, and.env.runtimeto the host. - The host pulls the new image, refreshes the static asset volume, and starts
dashboard + caddy. - The workflow verifies
cn.svc.plusandcn.onwalk.net.
Verification Commands
Local syntax checks:
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/console.svc.plus
bash -n scripts/github-actions/render-frontend-runtime-env.sh
bash -n scripts/github-actions/deploy-frontend-single-node.sh
cp deploy/single-node/.env.runtime.example deploy/single-node/.env.runtime
docker compose -f deploy/single-node/docker-compose.yml --env-file deploy/single-node/.env.runtime config >/tmp/console-compose.rendered.yaml
rm -f deploy/single-node/.env.runtime
python3 - <<'PY'
from pathlib import Path
import yaml
yaml.safe_load(Path('.github/workflows/service_release_frontend-deploy.yml').read_text())
print('workflow yaml ok')
PY
Remote checks:
ssh root@47.120.61.35 "cd /opt/console-svc-plus && docker compose --env-file .env.runtime ps"
ssh root@47.120.61.35 "curl -fsSI -H 'Host: cn.svc.plus' http://127.0.0.1/"
curl -fsSIL https://cn.svc.plus
curl -fsSIL https://cn.onwalk.net
Failure Signatures
docker login ghcr.iofails The workflow token or package visibility is wrong.frontend-assetsfails The image layout changed and no longer contains/app/dashboard/staticor/app/dashboard/public.cn.svc.plusreturns502Caddy is up, but thedashboardcontainer failed or is not reachable on port3000.cn.onwalk.netdoes not redirect Check the deployedCaddyfileand domain DNS.
Rollback
- Re-run the workflow with a previous known-good image tag.
- Or update
/opt/console-svc-plus/.env.runtimeand setFRONTEND_IMAGE=ghcr.io/<owner>/dashboard:<previous-tag>. - Restart the services:
ssh root@47.120.61.35 "cd /opt/console-svc-plus && docker compose --env-file .env.runtime run --rm frontend-assets"
ssh root@47.120.61.35 "cd /opt/console-svc-plus && docker compose --env-file .env.runtime up -d dashboard caddy"
- Verify
https://cn.svc.plusagain before closing the incident.