fix(deploy): move Postgres off Render, which expires it after 30 days - #37
Open
thefcan wants to merge 3 commits into
Open
fix(deploy): move Postgres off Render, which expires it after 30 days#37thefcan wants to merge 3 commits into
thefcan wants to merge 3 commits into
Conversation
ragdesk-api has been crash-looping in production. The cause was not the code: ERROR migrate: create schema_migrations: failed to connect to `user=ragdesk_db_user database=ragdesk_db`: hostname resolving error: lookup dpg-d91opdvavr4c73fp8abg-a on 169.254.20.10:53: no such host Render's free Postgres expires 30 days after creation. ragdesk-db expired, its internal hostname stopped resolving, and every boot the API failed its startup migration and exited 1. The blueprint has shown "Failed sync" ever since, which reads like a repo or permissions problem and is neither — the sync created four of five resources fine. ragdesk-ai survived only because it does not migrate on boot; ragdesk-web stayed up serving a front end with no API behind it. Declaring the database in render.yaml is what put it on that clock, so it is no longer declared. DATABASE_URL now comes from a `ragdesk-db` environment group the operator fills in with a connection string from a provider whose free tier does not expire — Neon and Supabase both ship pgvector, and 0002_documents.sql already does CREATE EXTENSION IF NOT EXISTS vector, so a fresh database bootstraps itself on first boot. An env group rather than two `sync: false` entries: the API and the AI service must agree on one database, and pasting the same connection string into two services separately is how they quietly stop agreeing — a typo in one would reproduce this exact crash-loop in a way that is no easier to diagnose. ragdesk-redis is untouched. Free Key Value does not expire the way free Postgres does, and REDIS_URL uses fromService, which keeps working. Docs updated in the same pass, since the blueprint no longer provisions a database: README's deploy section and docs/deploy.md, which now carry the two requirements most likely to make this land broken anyway — the connection string needs ?sslmode=require (Render's internal URL did not), and should use the provider's pooled host, since free instances cap connections below what several services reconnecting after a cold start will ask for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the Render Blueprint deployment to stop provisioning Postgres on Render’s free tier (which expires) and instead require an externally hosted Postgres (e.g., Neon/Supabase) shared via a single environment group value.
Changes:
- Removed the Render-managed Postgres resource from
render.yamland introduced an env var group intended to provideDATABASE_URL. - Updated Blueprint service wiring to consume
DATABASE_URLfrom the shared group (API + AI). - Updated README and deploy docs to explain the external Postgres requirement and the operational reason.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| render.yaml | Removes Render Postgres and introduces a shared DB env var group; wires DB config into API/AI services. |
| README.md | Updates deployment description to reflect external Postgres requirement. |
| docs/deploy.md | Updates Render Blueprint instructions to include creating external Postgres and setting DATABASE_URL. |
Suppressed comments (2)
render.yaml:69
- Render Blueprints attach env var groups to a service via an
envVarsentry (- fromGroup: …), not a separateenvVarGroups:block under the service. As written,ragdesk-aimay never receiveDATABASE_URL.
envVarGroups:
- ragdesk-db # DATABASE_URL
render.yaml:111
- Same issue as above: env var groups are included via
envVars: - fromGroup: ragdesk-dbrather thanenvVarGroups:under the service. Otherwiseragdesk-apiwon’t getDATABASE_URLand will fail during startup migrations.
envVarGroups:
- ragdesk-db # DATABASE_URL
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+33
to
+34
| - key: DATABASE_URL | ||
| sync: false |
Comment on lines
+49
to
+52
| 3. In Render: **New → Blueprint**, connect your fork. Render reads `render.yaml` | ||
| and provisions every service, then prompts for `DATABASE_URL` — that is the | ||
| `ragdesk-db` environment group, shared by the API and the AI service, so you | ||
| paste the connection string **once**. |
govulncheck fails CI on two advisories published since the pipeline last ran in full (5 July — everything green on main after that was CodeQL or Dependabot, neither of which runs this job): GO-2026-6061 google.golang.org/grpc v1.81.1 -> v1.82.1 GO-2026-5970 golang.org/x/text v0.38.0 -> v0.39.0 Both are indirect: grpc arrives through otlptracehttp, x/text through pgx. Both are reachable — govulncheck traced grpc to pgxpool.Pool.Close and x/text to pgxpool.NewWithConfig, so this is a real call path, not just a module in the graph. After the bump govulncheck reports no module vulnerabilities at all. Locally it still flags three in the standard library, but those are an artifact of this machine's Go 1.26.3 (fixed in 1.26.4 and 1.26.5); CI resolves its toolchain from api/go.mod's `go 1.26` and therefore installs a patched release, which is why its run listed exactly the two module advisories above and no standard-library ones. Build, vet and the full test suite pass against the new versions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…roup The env var group in the previous commit does not work, and would have failed in exactly the way this branch exists to prevent. Render's Blueprint spec: "You can't apply `sync: false` to environment variables defined in an environment group. If you do this, Render ignores the environment variable." So the group would have synced with DATABASE_URL simply absent, and both services would have crash-looped on boot migrations against no database — the outage this branch is fixing, reintroduced by its own fix. The service attachment syntax was wrong too: a service joins a group through an `envVars: - fromGroup: NAME` entry, not a service-level `envVarGroups:` list, so ragdesk-ai would not have received the variable regardless. Caught by the Copilot review on the PR; confirmed against the spec before changing anything. DATABASE_URL is now a `sync: false` placeholder on ragdesk-ai and ragdesk-api — Render's documented pattern for a secret, and already the convention in this file for AI_SERVICE_URL and friends. The cost is that the same connection string is pasted twice and nothing verifies the two agree, so that requirement is stated at both declaration sites and called out in docs/deploy.md rather than left to be discovered. Co-Authored-By: Claude Opus 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.
What broke
ragdesk-apihas been crash-looping in production. Not the code:Render's free Postgres expires 30 days after creation.
ragdesk-dbexpired, its internal hostname stopped resolving, and the API died on its startup migration every boot.The blueprint's "Failed sync" badge is a red herring — it reads like a repo or permissions problem and is neither. That sync created four of five resources fine; only
ragdesk-api's deploy failed, and the badge has stuck since.Current state of the stack:
ragdesk-apiragdesk-airagdesk-webThe fix
Declaring the database in
render.yamlis what put it on the 30-day clock, so it is no longer declared.DATABASE_URLnow comes from aragdesk-dbenvironment group filled in with a connection string from a provider whose free tier does not expire.Nothing needs migrating:
api/migrations/0002_documents.sqlalready runsCREATE EXTENSION IF NOT EXISTS vector, so a fresh Neon/Supabase database bootstraps its own schema on first boot.Why an env group and not two
sync: falseentries — the API and the AI service must agree on one database. Pasting the same connection string into two services separately is how they quietly stop agreeing, and a typo in one reproduces this exact crash-loop with no better diagnostics.ragdesk-redisis untouched: free Key Value does not expire the way free Postgres does, andREDIS_URLusesfromService, which keeps working.What you still have to do — this PR does not revive the service
DATABASE_URLon theragdesk-dbenv group. Paste it in Render's UI yourself — it is a credential.?sslmode=require. Render's internal URL did not need it; a hosted one does, and without it the failure looks identical to the bug above.ragdesk-apistays down until steps 1–3 are done.Separately: the expired
ragdesk-dband its data are deleted on Render's own timeline regardless of this PR. The data is gone either way unless the instance is upgraded to a paid plan before then.🤖 Generated with Claude Code