Deploy Runbook
⚠️ Operators only — this is not a page for RP integration developers. It is for the ops team that directly deploys and rolls back the logi-server / docs-site / logi-db Render instances.
📌 Placeholder notation: The
<LOGI_WEB_SERVICE_ID>/<LOGI_DOCS_SERVICE_ID>/<LOGI_PG_INSTANCE_ID>on this page are placeholders that mask the real Render resource IDs. Look up the real values in the ops team's internal runbook or in the Render Dashboard → Service settings. (This follows the policy for the publicly accessible docs subdomain — 2026-05-15.)
Service inventory
| Resource | ID | autoDeploy | Notes |
|---|---|---|---|
| logi-server (web) | <LOGI_WEB_SERVICE_ID> | manual | standard plan, Singapore, https://logi-server.onrender.com → api.1pass.dev |
| logi-docs (static) | <LOGI_DOCS_SERVICE_ID> | automatic (push main) | VitePress build |
| logi-db (postgres) | <LOGI_PG_INSTANCE_ID> | — | basic_256mb, PG 18 |
(Live values were verified via the Render API get_service / get_postgres — 2026-05-15.)
There is no worker — with SOLID_QUEUE_IN_PUMA=true, the dispatcher and worker run inside the web process. (We reconfirmed on 2026-05-15 via list_services that no separate logi-solidqueue service exists in prod. The worker entry in server/render.yaml was removed at the same time.)
Deploy triggers
docs-site
A push to origin/main is picked up by Render immediately → VitePress build → publish. No separate action required.
logi-server (manual)
Render dashboard → service → Manual Deploy → click "Deploy latest commit". Or via the API:
curl -X POST "https://api.render.com/v1/services/<LOGI_WEB_SERVICE_ID>/deploys" \
-H "Authorization: Bearer $RENDER_API_KEY"Check the deployed commit:
curl -s "https://api.render.com/v1/services/<LOGI_WEB_SERVICE_ID>/deploys?limit=5" \
-H "Authorization: Bearer $RENDER_API_KEY" | jq '.[].deploy | {id, commit: .commit.id, status, createdAt}'Pre-deploy checks
Locally, before deploying:
cd server
bundle exec rubocop --parallel
bundle exec rspec
bundle exec brakeman --quiet -q -w2
bin/rails zeitwerk:checkCI (.github/workflows/ci.yml) runs the same checks — only green PRs get merged to main.
Migrations
⚠️ Render does not run migrations automatically. Run them manually over SSH right after the deploy:
ssh -o StrictHostKeyChecking=no \
<LOGI_WEB_SERVICE_ID>@<RENDER_SSH_HOST> \
"cd /opt/render/project/src/server && \
/opt/render/project/.gems/bin/bundle exec rails db:migrate RAILS_ENV=production 2>&1"- Ignore the
client_global_hostkeys_prove_confirmwarning. - Destructive migrations (dropping a column, etc.) go in a separate PR and a separate step before the deploy — let the schema sit for one cycle so that both code and schema can read stale rows, then drop in the next PR.
Post-deploy checks
Within 5 minutes of the deploy + migration:
# 1. health (same as Render's healthCheckPath — the Rails health controller)
curl -i https://api.1pass.dev/healthz # expect 200 OK
# 2. /up returns the same result — the standard Rails health probe (load-balancer/uptime-monitor friendly)
curl -i https://api.1pass.dev/up # expect 200 OK
# 3. OIDC discovery
curl -i https://api.1pass.dev/.well-known/openid-configuration # 200 + JSON
# 4. JWKS
curl -s https://api.1pass.dev/.well-known/jwks.json | jq '.keys | length' # >= 1The endpoint Render probes automatically is /healthz (render.yaml healthCheckPath). /up is the standard Rails health endpoint — usable the same way for external monitors and local checks. Both endpoints are defined in routes.rb and return the same health result.
Then monitor the Render dashboard logs for 5 minutes — confirm the error rate is 0.
Rollback
Code-only rollback (when the migration is backward-compatible)
Render dashboard → Deploys tab → the last green deploy → "Rollback to this deploy".
Or via git:
git revert <bad-sha> && git push origin main
# logi-server is manual, so trigger Manual Deploy again as aboveWhen the migration is destructive
A code rollback alone is not enough — the older code that read the new column/constraint breaks. Procedure:
- Roll the code back to the last green deploy (procedure above).
- Over SSH, run
rails db:rollback STEP=N RAILS_ENV=production(N = the number of migrations to revert). - Check the
schema_migrationsstate withrails db:migrate:status.
Mentally simulate these two steps before merging a destructive migration.
Environment variables
Render dashboard → service → Environment.
⚠️ When editing env vars via the API, never use the collection PUT (PUT /v1/services/{id}/env-vars). It replaces the entire set, so any omitted keys vanish (the 2026-03-30 ainote incident where 52 keys dropped to 4). Always use the individual PUT:
curl -X PUT "https://api.render.com/v1/services/<LOGI_WEB_SERVICE_ID>/env-vars/MY_KEY" \
-H "Authorization: Bearer $RENDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"value": "new-value"}'Changing an env var triggers an automatic Render redeploy.
Key env vars (high-impact when changed)
APNS_*(KEY_ID/TEAM_ID/TOPIC/ENV) — affects all push.APPLE_TEAM_ID,APPLE_KEY_ID,APPLE_PRIVATE_KEY— Sign in with Apple revoke.SOLID_QUEUE_IN_PUMA=true,JOB_CONCURRENCY=1— worker integration.ENFORCE_CANONICAL_RESOLUTION— ⚠️ enforces canonical resolution after a merge. Before flipping it, see the incident response §merge race.
Secret File
apns.p8 is mounted as a Render Secret File (/etc/secrets/apns.p8). To rotate the key, replace it in the dashboard's Secret Files tab + redeploy. It is not an env var, so the API above does not apply.