fix(deploy): make the Render blueprint deployable (verified against the image) - #63
Open
adamXbot wants to merge 2 commits into
Open
fix(deploy): make the Render blueprint deployable (verified against the image)#63adamXbot wants to merge 2 commits into
adamXbot wants to merge 2 commits into
Conversation
The blueprint could not have provisioned as written, and would have silently lost client IPs if it had. Blocking: the database specified `plan: starter`, which Render now classes as a LEGACY instance type that cannot be used for new databases. Switched to `basic-256mb` (the current flexible type). Blocking: the database had no `region`, so it defaulted independently of the web service. Render services reach a database over the private network only when they share a region — and since `ipAllowList: []` disables public ingress, a region mismatch leaves the app with no route to Postgres at all. Both are now pinned to oregon with a comment saying they must match. Silent: no TRUST_PROXY_HEADERS, the same defect just fixed for Fly. The image sets NODE_ENV=production, where mantis distrusts forwarding headers unless told otherwise, so every hit records ip = null. Also pins TRUSTED_IP_HEADER=x-forwarded-for, since Render does not strip an inbound CF-Connecting-IP and mantis tries that header first — without the pin a client can forge its recorded IP with one header. Also: - MANTIS_API_KEY_PEPPER moves from `sync: false` to `generateValue: true` — Render generates it once at create and never rotates it on later deploys, which matches the write-once requirement exactly and means the operator never has to handle the value - postgresMajorVersion 16 -> "17" (quoted per spec); the schema only needs gen_random_uuid(), core since 13 - documents that the first-boot log prints the admin key, and that the structured line above the banner carries only the 18-char prefix, so a naive grep for mantis_live_ yields a truncated key that 401s - optional retention/SMTP/BOOTSTRAP_API_KEY blocks, and a sharper free-tier warning: a tripwire is idle by definition, so spin-down means nearly every REAL hit pays the cold start Verified by booting the production image against Postgres 17.10 with exactly this env set: migrations applied, health db:ok, the bootstrap banner printed a working key (200 with it, 401 without), and a trigger carrying `X-Forwarded-For: 198.51.100.42` plus a forged `CF-Connecting-IP: 6.6.6.6` recorded 198.51.100.42. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Findings from a pre-deploy double-check against the current blueprint spec, on top of the earlier plan/region/proxy fixes: - diskSizeGB: 5 — omitting it gives Basic-tier databases a 15 GB disk by default, ~3x the storage cost for nothing on a canary DB. Disks can grow later but never shrink, so pin it small up front. - postgresMajorVersion "17" -> "18": parity with docker-compose, CI and the tier-2 suite, which all run postgres:18. Re-verified the image end-to-end against 18.4: migrations applied, health db:ok, bootstrap banner key authenticates (200/401), forged CF-Connecting-IP ignored. - Document that the <name>.onrender.com namespace is global: plain "mantis" is taken, and when the name is taken Render silently serves at <name>-<suffix>.onrender.com. PUBLIC_BASE_URL then mints canary URLs pointing at ANOTHER customer's app — for a tripwire that means handed-out canary URLs trigger someone else's server. Usage now says pick a unique name, verify the served URL after first deploy, and fix PUBLIC_BASE_URL in this file (the blueprint is the source of truth; dashboard edits to synced vars are overwritten on the next sync). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Pre-deploy verification of
deploy/render.yaml.examplefor a both-in-Render (web service + Postgres) deployment. The blueprint could not have provisioned as written, and had two silent defects that matter specifically for a tripwire service. Every fix is verified against the production Docker image, not just the docs.Blocking defects (blueprint would not provision)
plan: starteron the database — Render now classesstarter/standard/proas legacy Postgres instance types that cannot be used for new databases. →basic-256mb(current flexible type, ~$6/mo).region— the DB region defaulted independently of the web service'soregon. Private-network connection strings only resolve within the same region, and sinceipAllowList: []disables public ingress, a region mismatch leaves the app with no route to Postgres at all. Both are now pinned together with a comment saying they must move as a pair.Silent defects
NODE_ENV=production, where mantis distrusts forwarding headers unlessTRUST_PROXY_HEADERS=1. Without it every hit recordsip = null. Also pinsTRUSTED_IP_HEADER=x-forwarded-forsince Render does not strip an inboundCF-Connecting-IP(forgeable with one request header otherwise). Comment covers thecf-connecting-ipswitch if fronted by Cloudflare later.PUBLIC_BASE_URLvs the global onrender.com namespace —<name>.onrender.comis shared by all Render customers; plainmantisis taken, and when a name is taken Render silently serves at<name>-<suffix>.onrender.com. The hardcoded base URL would then mint canary URLs pointing at another customer's app — handed-out canaries would trigger someone else's server. Usage now: pick a unique name, verify the served URL after first deploy, fix it in this file (the blueprint is the source of truth; dashboard edits to synced vars are overwritten on sync).diskSizeGBis omitted, ~3× the storage cost for nothing. Pinned to 5 GB (disks grow but never shrink).Improvements
MANTIS_API_KEY_PEPPERusesgenerateValue: true— generated once at create, never rotated on later deploys, which is exactly the write-once property the pepper needs; the operator never handles the value. (Recreating the service from scratch requires copying it across first — documented.)postgresMajorVersion: "18"— parity with docker-compose, CI, and the tier-2 suite, all on postgres:18.mantis_live_returns an 18-char prefix from a structured log line that 401s — take the key from the banner.BOOTSTRAP_API_KEY, retention (MANTIS_*_RETENTION_DAYS), and SMTP; sharper free-tier warning (a tripwire is idle by definition, so spin-down means nearly every real hit pays the cold start).Verification
Booted the production image with exactly the blueprint's env against Postgres 18.4 (and previously 17.10):
/api/health→{"status":"ok","db":"ok"}X-Forwarded-For: 198.51.100.42+ forgedCF-Connecting-IP: 6.6.6.6records198.51.100.42fromDatabasereference resolves;diskSizeGBsatisfies the 1-or-multiple-of-5 rule; version is a quoted stringNot verifiable without a Render account: live blueprint parsing on Render's side. Everything checkable locally has been checked.
🤖 Generated with Claude Code