Pre-deploy security checklist for Postgres with Electric. Checks REPLICATION role, SELECT grants, CREATE on database, table ownership, REPLICA IDENTITY FULL on all synced tables, publication management (auto vs manual with ELECTRIC_MANUAL_TABLE_PUBLISHING), connection pooler exclusion for DATABASE_URL (use direct connection), and ELECTRIC_POOLED_DATABASE_URL for pooled queries. Load before deploying Electric to production or when diagnosing Postgres permission errors.
This skill builds on electric-proxy-auth. Read it first for proxy security patterns.
Run through each section before deploying Electric to production.
Expected:
SELECT rolreplication FROM pg_roles WHERE rolname = 'electric_user';
-- Should return: true
Fail condition: rolreplication = false or user does not exist.
Fix: ALTER ROLE electric_user WITH REPLICATION;
Expected:
SELECT has_table_privilege('electric_user', 'todos', 'SELECT');
-- Should return: true
Fail condition: Returns false.
Fix: GRANT SELECT ON todos TO electric_user; or GRANT SELECT ON ALL TABLES IN SCHEMA public TO electric_user;
Expected:
SELECT has_database_privilege('electric_user', current_database(), 'CREATE');
-- Should return: true (unless using manual publishing mode)
Fail condition: Returns false and not using ELECTRIC_MANUAL_TABLE_PUBLISHING=true.
Fix: GRANT CREATE ON DATABASE mydb TO electric_user;
Expected:
SELECT relname, relreplident
FROM pg_class
WHERE relname IN ('todos', 'users')
AND relreplident = 'f'; -- 'f' = FULL
Fail condition: relreplident is 'd' (default) or 'n' (nothing).
Fix: ALTER TABLE todos REPLICA IDENTITY FULL;
Expected:
SELECT tablename FROM pg_publication_tables
WHERE pubname = 'electric_publication_default';
Fail condition: Synced tables missing from the list.
Fix (manual mode): ALTER PUBLICATION electric_publication_default ADD TABLE todos;
Expected:
DATABASE_URL=postgres://user:pass@db-host:5432/mydb
Fail condition: URL points to a connection pooler (e.g., PgBouncer on port 6432, Supabase pooler).
Fix: Use direct Postgres connection for DATABASE_URL. Set ELECTRIC_POOLED_DATABASE_URL separately for pooled queries.
Expected:
SHOW wal_level;
-- Should return: logical
Fail condition: Returns replica or minimal.
Fix: Set wal_level = logical in postgresql.conf and restart Postgres.
Wrong:
DATABASE_URL=postgres://user:[email protected]:6432/mydb
Correct:
DATABASE_URL=postgres://user:[email protected]:5432/mydb
ELECTRIC_POOLED_DATABASE_URL=postgres://user:[email protected]:6432/mydb
Connection poolers (except PgBouncer 1.23+) do not support logical replication. Electric must connect directly to Postgres for its replication slot.
Source: website/docs/guides/deployment.md:91
Wrong:
CREATE TABLE todos (id UUID PRIMARY KEY, text TEXT);
-- Replica identity defaults to 'default' (PK only)
Correct:
CREATE TABLE todos (id UUID PRIMARY KEY, text TEXT);
ALTER TABLE todos REPLICA IDENTITY FULL;
Without REPLICA IDENTITY FULL, Electric cannot stream the full row on updates and deletes. Updates may be missing non-PK columns.
Source: website/docs/guides/troubleshooting.md:373
Wrong:
CREATE USER electric_user WITH PASSWORD 'secret';
Correct:
CREATE USER electric_user WITH PASSWORD 'secret' REPLICATION;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO electric_user;
Electric uses logical replication and requires the REPLICATION role on the database user.
Source: website/docs/guides/postgres-permissions.md
REPLICATION roleSELECT on all synced tablesCREATE on database (or manual publishing configured)REPLICA IDENTITY FULLDATABASE_URL uses direct Postgres connection (not pooler)wal_level = logical in Postgres configELECTRIC_SECRET is set (not using ELECTRIC_INSECURE=true)See also: electric-proxy-auth/SKILL.md — Proxy injects secrets that Postgres security enforces. See also: electric-deployment/SKILL.md — Deployment requires correct Postgres configuration.
Targets Electric sync service v1.x.