Run the database in a container of our own
Supabase was only ever the host: the application has talked to PostgreSQL
directly through pg/Kysely for a while. So the move is mostly about
supplying what the platform used to supply.
Proved before building anything. All 65 migrations replay onto an empty
database, and the result matches production exactly — 183 columns, 25
policies, 68 indexes, 84 constraints, identical sets, no diff. The only
function missing from the rebuild turned out to matter, see below.
What the platform supplied, deploy/db-init now does:
- alpenwerk_app, explicitly NOBYPASSRLS. The whole access model is 21
RLS policies; a role that bypasses them would leave everything
working while showing too much, and nobody would notice.
- pgcrypto and pg_trgm. uuid-ossp was available on Supabase but is
used nowhere — no column default, no function calls uuid_generate_*.
- anon, authenticated and service_role as NOLOGIN placeholders. No
policy names them; they only carry grants the platform handed out,
and a data dump referencing them would fail to restore without them.
- A stub `auth` schema. The end state needs none of it — checked: no
foreign key, no policy, no column default refers to it. The June
2026 migrations do, and rewriting those would be falsifying history;
they describe what was true then.
The gap the comparison found: rls_auto_enable() and the ensure_rls event
trigger existed only in the running database, created by hand, in no
migration. That is the net which forces RLS on every newly created
table — the reason a forgotten policy yields an empty table instead of
an open one. A rebuild from migrations would silently not have had it:
everything works, and the next new table is unprotected. Now a migration
(20260819100000), verified by creating a table on the rebuild and
confirming RLS came on by itself.
Data moves separately, via scripts/umzug-von-supabase.sh: schema from
the migrations, then pg_dump --data-only --disable-triggers for the rows.
Without --disable-triggers every foreign key trips over load order. RLS
does not interfere — none of the 19 tables uses FORCE ROW LEVEL
SECURITY, so the owner writes through. The dump is deliberately left on
disk afterwards.
psql and node come from two `tools`-profile services rather than being
installed on the host, so the server needs nothing but Docker. The db
service publishes no port at all — reachable only inside the compose
network.
SUPABASE_DB_URL is renamed MIGRATE_DATABASE_URL, since after this it
describes something else entirely; the old name still works so existing
.env files keep running. Both were exercised, as was the error when
neither is set.
The deploy workflow is set to manual-only. Its preconditions were never
met — no secrets, and whether the job container can reach the host's
Docker daemon is untested — and failing on every push teaches people to
ignore red runs. It also needs updating for the new database service
before it could work at all.
Not verified: none of this has run in an actual container. There is no
Docker daemon on this machine. What is verified is the part that
decides whether it can work — the schema, on a real empty database.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,15 +42,20 @@ const argumente = new Set(process.argv.slice(2));
|
||||
const nurZeigen = argumente.has("--dry-run");
|
||||
const baseline = argumente.has("--baseline");
|
||||
|
||||
// SUPABASE_DB_URL ist der direkte Zugang (Port 5432, Sitzungsmodus).
|
||||
// DATABASE_URL zeigt in dieser Anwendung auf den Transaktions-Pooler (6543),
|
||||
// und der verträgt kein `create schema` in einer Transaktion mit mehreren
|
||||
// Anweisungen — Migrationen gehören deshalb über die direkte Verbindung.
|
||||
const verbindung = process.env.SUPABASE_DB_URL || process.env.MIGRATE_DATABASE_URL;
|
||||
// Der **direkte** Zugang als Verwalter — nicht DATABASE_URL.
|
||||
//
|
||||
// DATABASE_URL gehört der Anwendungsrolle, und die darf bewusst kein Schema
|
||||
// ändern; ausserdem zeigte sie bei Supabase auf den Transaktions-Pooler, der
|
||||
// mehrteilige Transaktionen nicht mitmacht.
|
||||
//
|
||||
// `SUPABASE_DB_URL` heisst nach dem Umzug auf einen eigenen Container nicht
|
||||
// mehr, was es ist — der Name bleibt als Rückfallebene, damit bestehende
|
||||
// .env-Dateien und Arbeitsstationen weiterlaufen.
|
||||
const verbindung = process.env.MIGRATE_DATABASE_URL || process.env.SUPABASE_DB_URL;
|
||||
if (!verbindung) {
|
||||
console.error(
|
||||
"SUPABASE_DB_URL fehlt. Erwartet wird der **direkte** Postgres-Zugang (bei Supabase Port 5432),\n" +
|
||||
"nicht der Transaktions-Pooler aus DATABASE_URL."
|
||||
"MIGRATE_DATABASE_URL fehlt. Erwartet wird der **direkte** Zugang als Verwalter,\n" +
|
||||
"nicht die Verbindung der Anwendung aus DATABASE_URL."
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user