Real-Time Sync with Zero
How SurfSense uses Rocicorp Zero for instant real-time data synchronization
Real-Time Sync with Zero
SurfSense uses Rocicorp Zero for real-time data synchronization. Zero continuously replicates data from PostgreSQL to a local cache on each client, enabling instant UI updates for notifications, documents, connectors, chat messages, and comments.
How It Works
Zero runs a zero-cache server that sits between PostgreSQL and the browser:
- zero-cache replicates data from PostgreSQL into a local SQLite replica using logical replication
- The browser connects to zero-cache via WebSocket and syncs relevant data locally
- When data changes in PostgreSQL (e.g., a new notification), zero-cache pushes the update to all connected clients instantly
- Queries run against local data first for instant results, then update when server data arrives
Architecture
| Component | Role |
|---|---|
| PostgreSQL | Source of truth (with wal_level=logical) |
| zero-cache | Replicates Postgres → SQLite, serves client sync via WebSocket |
| Browser | Stores synced data locally, runs queries against local cache |
Configuration
Docker Deployment
zero-cache is included in the Docker Compose setup. The key environment variables are:
| Variable | Description | Default |
|---|---|---|
SURFSENSE_PUBLIC_URL | Public SurfSense origin used by the browser | http://localhost:3929 |
ZERO_ADMIN_PASSWORD | Password for the zero-cache admin UI and /statz endpoint | surfsense-zero-admin |
ZERO_UPSTREAM_DB | PostgreSQL connection URL for replication | Built from DB_* vars |
/zero | Same-origin browser path Caddy routes to zero-cache | ${SURFSENSE_PUBLIC_URL}/zero |
ZERO_APP_PUBLICATIONS | PostgreSQL publication restricting which tables are replicated | zero_publication |
ZERO_NUM_SYNC_WORKERS | Number of view-sync worker processes. Must be ≤ ZERO_UPSTREAM_MAX_CONNS and ≤ ZERO_CVR_MAX_CONNS | 4 |
ZERO_UPSTREAM_MAX_CONNS | Max connections to upstream PostgreSQL for mutations | 20 |
ZERO_CVR_MAX_CONNS | Max connections to the CVR database | 30 |
Manual / Local Development
If running the frontend outside Docker (e.g. pnpm dev), you need:
-
A running zero-cache instance pointing at your PostgreSQL database. The easiest path is the official Docker image:
docker run -d --name surfsense-zero-cache \ -p 4848:4848 \ --add-host=host.docker.internal:host-gateway \ -e ZERO_UPSTREAM_DB="postgresql://postgres:postgres@host.docker.internal:5432/surfsense?sslmode=disable" \ -e ZERO_CVR_DB="postgresql://postgres:postgres@host.docker.internal:5432/surfsense?sslmode=disable" \ -e ZERO_CHANGE_DB="postgresql://postgres:postgres@host.docker.internal:5432/surfsense?sslmode=disable" \ -e ZERO_REPLICA_FILE=/data/zero.db \ -e ZERO_ADMIN_PASSWORD=surfsense-zero-admin \ -e ZERO_APP_PUBLICATIONS=zero_publication \ -e ZERO_QUERY_URL="http://host.docker.internal:3000/api/zero/query" \ -e ZERO_MUTATE_URL="http://host.docker.internal:3000/api/zero/mutate" \ -v surfsense-zero-cache:/data \ rocicorp/zero:1.4.0Run
uv run alembic upgrade headfromsurfsense_backend/before starting this container so thezero_publicationexists. -
If the frontend is not behind bundled Caddy, set
NEXT_PUBLIC_ZERO_CACHE_URL=http://localhost:4848before building/running the frontend so the browser connects directly to zero-cache. -
wal_level = logicalin your PostgreSQL config (see Manual Installation → Configure PostgreSQL for Zero Sync).
For the full manual setup walkthrough, see the Manual Installation guide.
Custom Domain / Reverse Proxy
The production Docker stack includes Caddy by default. Zero is exposed under the
same public origin as the app at ${SURFSENSE_PUBLIC_URL}/zero, for example
https://surf.example.com/zero. Zero accepts this single path-component base
URL, so Caddy forwards /zero/* to the internal zero-cache:4848 service
without stripping the prefix.
Database Requirements
zero-cache connects to PostgreSQL using logical replication. The database must meet these requirements:
wal_level = logical— already configured in the bundledpostgresql.conf- The database user must have
REPLICATIONprivilege — required for creating logical replication slots
In the default Docker setup, the surfsense user is a PostgreSQL superuser and has all required privileges automatically.
For managed databases (RDS, Supabase, Cloud SQL, etc.) where the app user may not be a superuser, you need to grant replication privileges:
ALTER USER surfsense WITH REPLICATION;
GRANT CREATE ON DATABASE surfsense TO surfsense;The REPLICATION privilege allows zero-cache to create a logical replication slot for streaming changes. The CREATE privilege allows zero-cache to create internal schemas (zero, zero_0) for its metadata.
Synced Tables
Zero syncs the following tables for real-time features:
| Table | Used By |
|---|---|
notifications | Inbox (comments, document processing, connector status) |
documents | Document list, processing status indicators |
folders | Nested folder tree for organizing documents |
search_source_connectors | Connector status, indexing progress |
new_chat_messages | Live chat message sync for shared chats |
chat_comments | Real-time comment threads on AI responses |
chat_session_state | Collaboration indicators (who is typing) |
automation_runs | Live run status and per-step progress (thin column set; heavy fields stay on REST) |
Troubleshooting
- zero-cache not starting: Check
docker compose logs zero-cache. Ensure PostgreSQL haswal_level=logical(configured inpostgresql.conf). - "Insufficient upstream connections" error: zero-cache defaults
ZERO_NUM_SYNC_WORKERSto the number of CPU cores, which can exceed connection pool limits on high-core machines. LowerZERO_NUM_SYNC_WORKERSor raiseZERO_UPSTREAM_MAX_CONNS/ZERO_CVR_MAX_CONNSin your.env. - Frontend not syncing: Open DevTools → Console and check for WebSocket connection errors. In production Docker, verify Caddy serves
${SURFSENSE_PUBLIC_URL}/zero. In manual local development, verifyNEXT_PUBLIC_ZERO_CACHE_URLpoints at the running zero-cache port. - Stale data after restart: zero-cache rebuilds its SQLite replica from PostgreSQL on startup. This may take a moment for large databases.
