Docker Installation
Run SurfSense with Docker in minutes
Docker is the recommended way to run SurfSense. Everything — database, backend, frontend, background workers, real-time sync, and a reverse proxy — comes pre-configured.
Prerequisites: Docker Desktop (or Docker Engine with Compose) must be installed and running.
Option 1: One-Line Install Script (Recommended)
Linux/macOS:
curl -fsSL https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/scripts/install.sh | bashWindows (PowerShell):
irm https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/scripts/install.ps1 | iexThe script creates a ./surfsense/ directory with the compose files and a .env, generates a secret key, starts all services, and waits until they're healthy. It also:
- Detects NVIDIA GPUs and asks whether to use GPU acceleration, picking the compatible backend image automatically.
- Asks whether to enable automatic daily updates via Watchtower (updates can download several GB in the background — pass
--no-watchtowerto skip). - Detects a legacy all-in-one installation and migrates its data automatically (see Updating).
Option 2: Manual Docker Compose
git clone https://github.com/MODSetter/SurfSense.git
cd SurfSense/docker
cp .env.example .env
# Edit .env — at minimum set SECRET_KEY (generate with: openssl rand -base64 32)
docker compose up -dAccess SurfSense
After starting, everything is served through the bundled Caddy reverse proxy on a single origin:
- SurfSense: http://localhost:3929
- Backend API: http://localhost:3929/api/v1
- Zero sync:
ws://localhost:3929/zero
Sign up with email/password (local auth is the default) and you're in.
Configuration
All configuration lives in a single .env file — surfsense/.env if you used the install script, docker/.env if you cloned manually. The bundled .env.example documents every option inline with comments and working defaults; edit the values you need and restart:
docker compose up -dThe defaults give you local email/password auth, local document parsing with Docling (no API keys), and local TTS/STT. Common things you might change, all documented in the file:
- Authentication — switch to Google OAuth login.
- Document parsing — switch to Unstructured or LlamaCloud (both need API keys).
- Connector credentials — OAuth apps for external connectors that need them on self-hosted deployments.
- Messaging channels — Telegram, WhatsApp, Slack, and Discord bots (see Messaging Channels).
NVIDIA GPU Acceleration
Add these values to .env to use the CUDA backend image (the host needs the NVIDIA Container Toolkit):
SURFSENSE_VARIANT=cuda
COMPOSE_FILE=docker-compose.yml:docker-compose.gpu.yml
SURFSENSE_GPU_COUNT=1Use SURFSENSE_VARIANT=cuda126 for older NVIDIA driver stacks. On Windows, use ; instead of : in COMPOSE_FILE. Then apply:
docker compose pull && docker compose up -d --waitCustom Domain / Automatic HTTPS
Point DNS at your Docker host and set in .env:
SURFSENSE_SITE_ADDRESS=surf.example.com
LISTEN_HTTP_PORT=80
LISTEN_HTTPS_PORT=443
CERT_EMAIL=you@example.com
SURFSENSE_PUBLIC_URL=https://surf.example.comThen docker compose up -d --wait. Caddy issues and renews Let's Encrypt certificates automatically — ports 80 and 443 must be reachable from the internet.
Bring Your Own Proxy
If you already run nginx, Traefik, Cloudflare Tunnel, or another ingress, comment out the proxy service and route traffic to the internal services with the same path contract:
| Public path | Upstream |
|---|---|
/auth/* | backend:8000 |
/api/v1/* | backend:8000 |
/zero/* | zero-cache:4848 |
/* | frontend:3000 |
Your proxy must preserve WebSocket upgrades for /zero, avoid buffering streaming responses, allow long-running requests, and support large uploads.
What's Running
| Service | Description |
|---|---|
proxy | Caddy reverse proxy — the only public ingress |
db | PostgreSQL with pgvector |
migrations | Short-lived: runs database migrations, then exits |
redis | Message broker for background tasks |
backend | FastAPI application server |
celery_worker | Background task processing (document indexing, etc.) |
celery_beat | Periodic task scheduler |
zero-cache | Real-time sync (replicates Postgres to browsers) |
frontend | Next.js web application |
Migrations run first on every startup; everything else waits for them to succeed, so docker compose up -d after an update is always safe. Monitor startup with docker compose ps — services go from (health: starting) to (healthy).
Useful Commands
# View logs (all services, or one)
docker compose logs -f
docker compose logs -f backend
# Restart a service
docker compose restart backend
# Stop all services
docker compose down
# Stop and remove containers + volumes (destroys your data!)
docker compose down -vTo update SurfSense, see Updating.
Building from Source (Contributors)
If you're contributing to SurfSense and want to build the images from your local checkout, use the dev compose file:
cd SurfSense/docker
docker compose -f docker-compose.dev.yml up --buildIt builds the backend and frontend from source, publishes raw service ports for debugging, and includes pgAdmin at http://localhost:5050. There's also a docker-compose.deps-only.yml that runs just the dependencies (Postgres, Redis, zero-cache) in Docker while you run the backend and frontend natively — see Manual Installation.
Troubleshooting
- Port already in use: Change
LISTEN_HTTP_PORTin.envand restart. In domain mode, ports 80/443 are required for certificate issuance. - Permission errors on Linux: Prefix
dockercommands withsudo, or add your user to thedockergroup. - Real-time updates not working: Open DevTools → Console and check for WebSocket errors. The expected URL is
${SURFSENSE_PUBLIC_URL}/zero. - Line ending issues on Windows: Run
git config --global core.autocrlf truebefore cloning.
Migration service exited non-zero
The stack halts if migrations fail. Inspect what happened:
docker compose logs migrationsSlow disks (Windows / WSL2) may hit the default 900-second migration timeout — set MIGRATION_TIMEOUT in .env to increase it.
Zero-cache stuck on Unknown or invalid publications
This means zero-cache started before migrations created its publication — usually only possible if you started services individually. Recover with:
docker compose down
docker volume rm surfsense-zero-cache # wipe the half-built replica
docker compose up -d # migrations run first, then zero-cacheThe same recovery fixes _zero.tableMetadata crashes (a half-initialized replica left behind by a previous crash).