Skip to content

fix(deploy): move Postgres off Render, which expires it after 30 days - #37

Open
thefcan wants to merge 3 commits into
mainfrom
fix/postgres-off-render
Open

fix(deploy): move Postgres off Render, which expires it after 30 days#37
thefcan wants to merge 3 commits into
mainfrom
fix/postgres-off-render

Conversation

@thefcan

@thefcan thefcan commented Aug 11, 2026

Copy link
Copy Markdown
Owner

What broke

ragdesk-api has been crash-looping in production. 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 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:

Service Status
ragdesk-api down — crash-loop on migration
ragdesk-ai up — survives only because it does not migrate on boot
ragdesk-web up — a front end with no API behind it

The fix

Declaring the database in render.yaml is what put it on the 30-day clock, so it is no longer declared. DATABASE_URL now comes from a ragdesk-db environment group filled in with a connection string from a provider whose free tier does not expire.

Nothing needs migrating: api/migrations/0002_documents.sql already runs CREATE 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: false entries — 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-redis is untouched: free Key Value does not expire the way free Postgres does, and REDIS_URL uses fromService, which keeps working.

What you still have to do — this PR does not revive the service

  1. Create a free Postgres with pgvector (Neon or Supabase).
  2. Merge this PR.
  3. In Render, Manual sync the blueprint, then set DATABASE_URL on the ragdesk-db env group. Paste it in Render's UI yourself — it is a credential.
    • It must end with ?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.
    • Prefer the provider's pooled host — free instances cap connections below what several services reconnecting after a cold start will ask for.
  4. ragdesk-api stays down until steps 1–3 are done.

Separately: the expired ragdesk-db and 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

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>
Copilot AI lite review requested due to automatic review settings August 11, 2026 12:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.yaml and introduced an env var group intended to provide DATABASE_URL.
  • Updated Blueprint service wiring to consume DATABASE_URL from 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 envVars entry (- fromGroup: …), not a separate envVarGroups: block under the service. As written, ragdesk-ai may never receive DATABASE_URL.
    envVarGroups:
      - ragdesk-db # DATABASE_URL

render.yaml:111

  • Same issue as above: env var groups are included via envVars: - fromGroup: ragdesk-db rather than envVarGroups: under the service. Otherwise ragdesk-api won’t get DATABASE_URL and 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 thread render.yaml Outdated
Comment on lines +33 to +34
- key: DATABASE_URL
sync: false
Comment thread docs/deploy.md Outdated
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**.
thefcan and others added 2 commits August 11, 2026 21:44
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>
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.

2 participants