Skip to content

Deploy labelled PRs to staging with prod data and smoke tests - #235

Draft
jzhou-otterai wants to merge 2 commits into
mainfrom
feat/staging-deploy-action
Draft

jzhou-otterai wants to merge 2 commits into
mainfrom
feat/staging-deploy-action

Conversation

@jzhou-otterai

@jzhou-otterai jzhou-otterai commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Summary

Add the staging label to a PR to deploy it to the staging box (the server-infra EC2 instance at 3.135.22.212). Each later push to that PR redeploys it. Run workflow can also deploy main or a PR by number, and can refresh staging's data from production.

The workflow reuses the existing scripts instead of adding new tooling. It runs as one job on ubuntu-latest, which is the free 4 vCPU / 16 GB runner for public repos; the org's Free plan can't use larger runners.

  1. script/build.sh api frontend builds the images, tagged neuwflow/<service>:<sha> just like production builds.
  2. Port 22 on the box's security group opens to the runner's IP for the length of the job, through an OIDC role.
  3. docker save | ssh … docker load copies the images to the box. Nothing is pushed to a registry.
  4. On the box, ~/uwflow checks out the same commit, so the migrations, Hasura metadata and nginx config match the images.
  5. On the first deploy, or when asked, the box runs script/fetch-dump.sh uwflow-prod and then make setup.
  6. docker compose up -d postgres hasura api frontend starts the stack. uw and email stay off, because email would send mail to real users from the production queue.
  7. Smoke tests check that the right images are running, index.html loads, anonymous GraphQL returns courses, /api/data/search works, the Hasura metadata is consistent, and no container is crash-looping.

Deploying a PR also removes the staging label from the PR that held it before.

Two small changes make the existing scripts work on the box:

  • docker-compose.yml: Hasura now listens on $HASURA_PORT, so staging can move it off 8080, which server-infra's nginx already uses. Production uses 8080 either way, so nothing changes there.
  • script/start.sh: falls back to docker compose when the docker-compose v1 binary is missing, which is the case on the box.

The one-time setup (box .env, prod SSH config, AWS role, GitHub environment) is in docs/staging.md.

Testing

  • actionlint passes. script/test-monorepo.py passes. Shellcheck finds no new warnings in start.sh.
  • docker compose config with HASURA_PORT=18080 renders the expected ports, the HASURA_GRAPHQL_SERVER_PORT env var and the neuwflow/*:<sha> images. The cli-migrations Hasura image serves /healthz on 18080.
  • From the first iteration: a real prod dump (2026-08-10) restores cleanly, and Hasura applies the latest migrations with consistent metadata.
  • Not yet run on the box or on GitHub. It needs the one-time setup first. make setup wasn't run locally, because it would wipe the local backend_postgres volume.

🤖 Generated with Claude Code

jzhou-otterai and others added 2 commits October 18, 2026 10:18
Adding the `staging` label to a PR builds its images on GitHub runners,
pushes them to GHCR, and deploys them to the server-infra EC2 box. Later
pushes redeploy automatically, and a manual run can deploy main or any PR
by number. Staging is a single slot, so deploying one PR takes the label
off the previous one.

The box runs a root-owned `uwflow-staging` command, installed from main
and reached through a forced SSH command. A PR can therefore supply only
images plus its hasura/ and nginx/ trees, never code that runs next to
the production credentials. Data refreshes copy the newest prod dump with
scp, restore it into a scratch database, and swap it in only after a
clean restore. The importer and email worker never start.

Port 22 is opened to the runner's IP only for the length of the job,
through an OIDC role scoped to the box's security group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop the registry, the staging compose override, and the root-owned
deploy tooling on the box. The workflow now builds api and frontend with
script/build.sh on the free 4-vCPU runner and streams the images over SSH
with docker load. On the box it checks out the same commit and runs
fetch-dump.sh plus make setup when data needs refreshing. Then it starts
postgres, hasura, api and frontend from docker-compose.yml.

Two small changes make the existing scripts work on the box:
- Hasura listens on $HASURA_PORT, so a host whose 8080 is taken can move
  it in .env. Production is unchanged, since it uses 8080 either way.
- start.sh falls back to `docker compose` on hosts without the v1
  docker-compose binary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant