Skip to content

Deploy the app on a push to main, and put it back if it fails - #169

Merged
Alexgodoroja merged 2 commits into
mainfrom
ci-deploy-on-main
Sep 16, 2026
Merged

Alexgodoroja merged 2 commits into
mainfrom
ci-deploy-on-main

Conversation

@Alexgodoroja

Copy link
Copy Markdown
Collaborator

What changes

  • Trigger. deploy-app.yml runs on a push to main that touches app/**
    (or the workflow itself), plus the manual dispatch it already had. A change
    to cmd/shell/**, worker/** or web/** does not deploy this Worker: those
    reach people through the relay Worker and published binaries.
  • Failing changes nothing. Build, tests and migration all run before
    production is touched. Migrations are additive and one transaction per file,
    so a run that stops partway leaves the serving Worker against a schema that
    only gained things. If the Worker deploys but does not answer, or answers
    with the previous index.html, the version that was serving is rolled back.
  • The database password stops being a GitHub secret. It is read from Secret
    Manager with the short-lived Workload Identity token the deploy already needs
    for the Cloud SQL proxy, and masked before use. One copy, held where the
    service reads it, and none of it in a public repository.
  • New npm run db:verify. Read-only: nothing pending, nothing edited since
    it was applied, every table the store reads present. It runs after migrating
    and before the Worker goes out.
  • CI migrates from nothing, twice. Against the empty postgres database on
    the Postgres service the app job already runs, then verifies, then does both
    again, so a migration that is not re-runnable fails in CI.

Migrations: idempotent, and why not sqitch

Checked rather than assumed, against a real Postgres:

first migrate (fresh database)  → 13 applied
verify                          → schema is what this build expects (13 migrations, 19 tables)
second migrate                  → schema is up to date        (no-op)
verify again                    → unchanged

The runner already takes an advisory lock, applies each file in its own
transaction with the schema_migrations row written inside it, and records a
checksum so an edited file is refused instead of half-applied. Sqitch would add
a Perl toolchain and a second source of truth to get deploy/revert/verify;
here deploy and verify now exist, and revert is deliberately not a thing:
migrations only ever add, which is what makes rolling the Worker back safe. A
schema that could be reverted under a running Worker would be the more
dangerous design, not the safer one. Worth revisiting if a destructive change
is ever genuinely needed.

Secrets and variables

Everything needed is listed by the preflight step, which fails naming what is
missing. Required secrets: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID,
GCP_WORKLOAD_IDENTITY_PROVIDER, GCP_SERVICE_ACCOUNT. DATABASE_URL is no
longer used and can be deleted. One new repository variable,
DATABASE_URL_SECRET, names the Secret Manager resource to read.

Leak review

No pull_request_target, issue_comment or workflow_run triggers; no
github.event.* interpolated into a shell; every action pinned by SHA;
persist-credentials: false on checkout; configured values passed through the
environment. The deploy is gated on the production environment.

Alexgodoroja added 2 commits September 15, 2026 22:12
Migrating reports what it applied. This reports what is there, which is the
question the code about to serve traffic has: is anything still pending, has
an applied file been edited since, and does every table the store reads exist.
Read-only, so it is safe against production and says the same thing twice.
The workflow only ran when somebody remembered to dispatch it, and the last
three runs failed on missing configuration, so app/ has been deployed by hand.
It now runs on a push to main that touches app/, and nothing else: a change to
the CLI, the relay or the site reaches people another way.

Failing leaves production alone. The build, the tests and the migration all
happen before anything is deployed, and the schema only ever gains things, so
a run that stops partway leaves the Worker that is serving untouched. If the
new Worker deploys but does not answer, or answers with the previous build,
the version that was serving is put back.

The database password is no longer a GitHub secret: it is read from Secret
Manager with the short-lived identity the deploy already uses, so the one copy
is the one the service reads, and this repository -- public -- holds none of
it. Configured values reach scripts through the environment rather than being
interpolated into them.

CI migrates an empty database twice and verifies it, so a migration that only
works on a schema that already exists, or that cannot be applied twice, fails
there rather than against production.
@Alexgodoroja
Alexgodoroja merged commit b2d47e7 into main Sep 16, 2026
15 checks passed
@Alexgodoroja
Alexgodoroja deleted the ci-deploy-on-main branch September 16, 2026 07:15
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