UAT & Staging Auto-Deploy Pipeline
GitHub Actions workflows for UAT and staging; dev-dashboard tab rewrite; release script cleanup
Overview
UAT and staging moved to push-triggered auto-deploys via GitHub Actions. Preprod and production remain manual-only by deliberate design - fts1 is IP-allowlisted and GitHub-hosted runners cannot SSH in. The dev dashboard's Release Preparation and Deployment tabs were rewritten to match the current 5-environment / 3-host reality.
What Changed
Auto-Deploy Workflows
Two new workflows in .github/workflows/:
deploy-uat.yml
- Trigger: push to
uatbranch +workflow_dispatchfor manual runs. - SSH target:
root@ftsuat(Vultr host). - Runs:
cd ~ && bun run release:uat. - Authenticated via
DEPLOY_SSH_KEY+DEPLOY_KNOWN_HOSTSsecrets.
deploy-staging.yml
- Trigger: push to
stagingbranch +workflow_dispatch. - SSH target:
root@ftsstaging(Vultr host). - Same script pattern as UAT.
Preprod Stays Manual
An attempt at auto-deploying preprod via a self-hosted runner on fts1 was tried (feat(ci): add Deploy Preprod workflow, ci(preprod): use self-hosted runner on fts1) and reverted (revert(ci): remove preprod auto-deploy + self-hosted runner). Reasons:
- fts1's IP allowlist makes manual-on-host the right default for preprod + production.
- A self-hosted runner with sudo access on fts1 is a larger trust surface than a manual deploy.
Preprod deploys therefore remain: ssh ubuntu@fts1 && sudo -u ftsuser bash -c 'cd ~/preprod && bun run release:preprod'.
Env-Named Symlinks
Release script now swaps ~/<env> directly instead of hard-coded ~/live. UAT and staging hosts therefore have ~/uat and ~/staging symlinks that nginx points at, so the swap is atomic and visible.
post-release.sh Extensions
- New
DEPLOY_ENVenv var (falls back toLIVE_LINK_NAMEfor backwards compat). - UAT and staging branches added to the restart dispatch.
release:uatandrelease:stagingnpm scripts.
Dev Dashboard Tabs Rewritten
scripts/dev-dashboard-server.ts Tabs 3 (Release Preparation) and 4 (Deployment) rewritten end-to-end after ground-truth verification on fts1:
- Overview table showing 5 envs × 3 hosts with per-env trigger (UAT/staging auto via CI; preprod/candidate/prod manual on fts1).
- Separate "UAT & staging - automated via GitHub Actions" section covering push triggers.
- "Preprod, candidate, production - manual on fts1" section with SSH + command reference.
~/livesymlink (not~/source); release dirs namedlive-*(notsource-*); PHP-FPM 8.3 (not 8.2).
Script Cleanup
- Dropped the
:safesuffix fromrelease:prod:safe->release:prodnow that there is only one release script. - Deleted the dead
scripts/release-prod-symlink.ts(old in-place build approach, no callers in operational code). release:*:safealiases were added in an interim step and then folded back into the short forms.
Files Touched
| File | Change |
|---|---|
.github/workflows/deploy-uat.yml | New |
.github/workflows/deploy-staging.yml | New |
.github/workflows/deploy-preprod.yml | Added, then removed |
scripts/post-release.sh | UAT/staging branches, DEPLOY_ENV |
scripts/release-prod-safe.ts | CONFIG_ENV_NAME override; env-named symlink swap |
scripts/dev-dashboard-server.ts | Tabs 3 & 4 rewrite |
scripts/dev-commands.ts | Deploy section split into manual-on-fts1 vs automated-via-CI |
scripts/release-prod-symlink.ts | Deleted |
package.json | release:uat, release:staging, refresh:uat, refresh:staging |
CLAUDE.md | Deployment pipeline docs |
.test_credentials | SSH targets for all 5 envs |
Changelog Reference
- chore: set up UAT/staging deployment pipeline (4a3ee951)
- feat(ci): restart services after UAT/staging deploys (a444abe2)
- feat(ci): add Deploy Preprod workflow (31e91767)
- ci(preprod): use self-hosted runner on fts1 (dbdbaa0f)
- revert(ci): remove preprod auto-deploy + self-hosted runner (db1fdc56)
- refactor(ci): hoist deploy target to workflow-level env block (59ac0fa0)
- feat(deploy): swap env-named symlink directly instead of ~/live (14394ae0)
- fix(ci): invoke release script by path with inline env vars (fcbe0f14)
- fix(ci): resolve fts hosts in deploy workflows via /etc/hosts (2a7d406e)
- docs(dashboard): rewrite deployment and release-prep tabs to match reality (1aa6393b)
- chore(deploy): drop :safe suffix, delete dead release-prod-symlink script (cd5b2268)
- chore: add release:*:safe variants and alias short forms (fbc1fb9b)