Docker Installation
Setting up AINexLayer using Docker
This guide explains how to run SurfSense using Docker, with options ranging from a single-command install to a fully manual setup.
Quick Start
Option 1 — Install Script (recommended)
Downloads the compose files, generates a SECRET_KEY, starts all services, and sets up Watchtower for automatic daily updates.
Windows users: install WSL first and run the command below in the Ubuntu terminal.
curl -fsSL https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/scripts/install.sh | bashThis creates a ./surfsense/ directory with docker-compose.yml and .env, then runs docker compose up -d.
To skip Watchtower (e.g. in production where you manage updates yourself):
curl -fsSL https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/scripts/install.sh | bash -s -- --no-watchtowerTo customise the check interval (default 24h), use --watchtower-interval=SECONDS.
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
docker compose up -dAfter starting, access SurfSense at:
- Frontend: http://localhost:3000
- Backend API: http://localhost:8000
- API Docs: http://localhost:8000/docs
- Electric SQL: http://localhost:5133
Updating
Option 1 — Watchtower daemon (recommended, auto-updates every 24 h):
If you used the install script (Option 1 above), Watchtower is already running. No extra setup needed.
For manual Docker Compose installs (Option 2), start Watchtower separately:
docker run -d --name watchtower \
--restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
nickfedor/watchtower \
--label-enable \
--interval 86400Option 2 — Watchtower one-time update:
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
nickfedor/watchtower --run-once \
--label-filter "com.docker.compose.project=surfsense"Use nickfedor/watchtower. The original containrrr/watchtower is no longer maintained and may fail with newer Docker versions.
Option 3 — Manual:
cd surfsense # or SurfSense/docker if you cloned manually
docker compose pull && docker compose up -dDatabase migrations are applied automatically on every startup.
Configuration
All configuration lives in a single docker/.env file (or surfsense/.env if you used the install script). Copy .env.example to .env and edit the values you need.
Required
| Variable | Description |
|---|---|
SECRET_KEY | JWT secret key. Generate with: openssl rand -base64 32. Auto-generated by the install script. |
Core Settings
| Variable | Description | Default |
|---|---|---|
SURFSENSE_VERSION | Image tag to deploy. Use latest, a clean version (e.g. 0.0.14), or a specific build (e.g. 0.0.14.1) | latest |
AUTH_TYPE | Authentication method: LOCAL (email/password) or GOOGLE (OAuth) | LOCAL |
ETL_SERVICE | Document parsing: DOCLING (local), UNSTRUCTURED, or LLAMACLOUD | DOCLING |
EMBEDDING_MODEL | Embedding model for vector search | sentence-transformers/all-MiniLM-L6-v2 |
TTS_SERVICE | Text-to-speech provider for podcasts | local/kokoro |
STT_SERVICE | Speech-to-text provider for audio files | local/base |
REGISTRATION_ENABLED | Allow new user registrations | TRUE |
Ports
| Variable | Description | Default |
|---|---|---|
FRONTEND_PORT | Frontend service port | 3000 |
BACKEND_PORT | Backend API service port | 8000 |
ELECTRIC_PORT | Electric SQL service port | 5133 |
Custom Domain / Reverse Proxy
Only set these if serving SurfSense on a real domain via a reverse proxy (Caddy, Nginx, Cloudflare Tunnel, etc.). Leave commented out for standard localhost deployments.
| Variable | Description |
|---|---|
NEXT_FRONTEND_URL | Public frontend URL (e.g. https://app.yourdomain.com) |
BACKEND_URL | Public backend URL for OAuth callbacks (e.g. https://api.yourdomain.com) |
NEXT_PUBLIC_FASTAPI_BACKEND_URL | Backend URL used by the frontend (e.g. https://api.yourdomain.com) |
NEXT_PUBLIC_ELECTRIC_URL | Electric SQL URL used by the frontend (e.g. https://electric.yourdomain.com) |
Database
Defaults work out of the box. Change for security in production.
| Variable | Description | Default |
|---|---|---|
DB_USER | PostgreSQL username | surfsense |
DB_PASSWORD | PostgreSQL password | surfsense |
DB_NAME | PostgreSQL database name | surfsense |
DB_HOST | PostgreSQL host | db |
DB_PORT | PostgreSQL port | 5432 |
DB_SSLMODE | SSL mode: disable, require, verify-ca, verify-full | disable |
DATABASE_URL | Full connection URL override. Use for managed databases (RDS, Supabase, etc.) | (built from above) |
Electric SQL
| Variable | Description | Default |
|---|---|---|
ELECTRIC_DB_USER | Replication user for Electric SQL | electric |
ELECTRIC_DB_PASSWORD | Replication password for Electric SQL | electric_password |
ELECTRIC_DATABASE_URL | Full connection URL override for Electric. Set to host.docker.internal when pointing at a local Postgres instance | (built from above) |
Authentication
| Variable | Description |
|---|---|
GOOGLE_OAUTH_CLIENT_ID | Google OAuth client ID (required if AUTH_TYPE=GOOGLE) |
GOOGLE_OAUTH_CLIENT_SECRET | Google OAuth client secret (required if AUTH_TYPE=GOOGLE) |
Create credentials at the Google Cloud Console.
External API Keys
| Variable | Description |
|---|---|
FIRECRAWL_API_KEY | Firecrawl API key for web crawling |
UNSTRUCTURED_API_KEY | Unstructured.io API key (required if ETL_SERVICE=UNSTRUCTURED) |
LLAMA_CLOUD_API_KEY | LlamaCloud API key (required if ETL_SERVICE=LLAMACLOUD) |
Connector OAuth Keys
Uncomment the connectors you want to use. Redirect URIs follow the pattern http://localhost:8000/api/v1/auth/<connector>/connector/callback.
| Connector | Variables |
|---|---|
| Google Drive / Gmail / Calendar | GOOGLE_DRIVE_REDIRECT_URI, GOOGLE_GMAIL_REDIRECT_URI, GOOGLE_CALENDAR_REDIRECT_URI |
| Notion | NOTION_CLIENT_ID, NOTION_CLIENT_SECRET, NOTION_REDIRECT_URI |
| Slack | SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_REDIRECT_URI |
| Discord | DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET, DISCORD_BOT_TOKEN, DISCORD_REDIRECT_URI |
| Jira & Confluence | ATLASSIAN_CLIENT_ID, ATLASSIAN_CLIENT_SECRET, JIRA_REDIRECT_URI, CONFLUENCE_REDIRECT_URI |
| Linear | LINEAR_CLIENT_ID, LINEAR_CLIENT_SECRET, LINEAR_REDIRECT_URI |
| ClickUp | CLICKUP_CLIENT_ID, CLICKUP_CLIENT_SECRET, CLICKUP_REDIRECT_URI |
| Airtable | AIRTABLE_CLIENT_ID, AIRTABLE_CLIENT_SECRET, AIRTABLE_REDIRECT_URI |
| Microsoft Teams | TEAMS_CLIENT_ID, TEAMS_CLIENT_SECRET, TEAMS_REDIRECT_URI |
For Airtable, create an OAuth integration at the Airtable Developer Hub.
Observability (optional)
| Variable | Description |
|---|---|
LANGSMITH_TRACING | Enable LangSmith tracing (true / false) |
LANGSMITH_ENDPOINT | LangSmith API endpoint |
LANGSMITH_API_KEY | LangSmith API key |
LANGSMITH_PROJECT | LangSmith project name |
Advanced (optional)
| Variable | Description | Default |
|---|---|---|
SCHEDULE_CHECKER_INTERVAL | How often to check for scheduled connector tasks (e.g. 5m, 1h) | 5m |
RERANKERS_ENABLED | Enable document reranking for improved search | FALSE |
RERANKERS_MODEL_NAME | Reranker model name (e.g. ms-marco-MiniLM-L-12-v2) | |
RERANKERS_MODEL_TYPE | Reranker model type (e.g. flashrank) | |
PAGES_LIMIT | Max pages per user for ETL services | unlimited |
Docker Services
| Service | Description |
|---|---|
db | PostgreSQL with pgvector extension |
redis | Message broker for Celery |
backend | FastAPI application server |
celery_worker | Background task processing (document indexing, etc.) |
celery_beat | Periodic task scheduler (connector sync) |
electric | Electric SQL — real-time sync for the frontend |
frontend | Next.js web application |
All services start automatically with docker compose up -d.
The backend includes a health check — dependent services (workers, frontend) wait until the API is fully ready before starting. You can monitor startup progress with docker compose ps (look for (health: starting) → (healthy)).
Development Compose File
If you're contributing to SurfSense and want to build from source, use docker-compose.dev.yml instead:
cd SurfSense/docker
docker compose -f docker-compose.dev.yml up --buildThis file builds the backend and frontend from your local source code (instead of pulling prebuilt images) and includes pgAdmin for database inspection at http://localhost:5050. Use the production docker-compose.yml for all other cases.
The following .env variables are only used by the dev compose file (they have no effect on the production docker-compose.yml):
| Variable | Description | Default |
|---|---|---|
PGADMIN_PORT | pgAdmin web UI port | 5050 |
PGADMIN_DEFAULT_EMAIL | pgAdmin login email | admin@surfsense.com |
PGADMIN_DEFAULT_PASSWORD | pgAdmin login password | surfsense |
REDIS_PORT | Exposed Redis port (internal-only in prod) | 6379 |
NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE | Frontend build arg for auth type | LOCAL |
NEXT_PUBLIC_ETL_SERVICE | Frontend build arg for ETL service | DOCLING |
NEXT_PUBLIC_DEPLOYMENT_MODE | Frontend build arg for deployment mode | self-hosted |
NEXT_PUBLIC_ELECTRIC_AUTH_MODE | Frontend build arg for Electric auth | insecure |
In the production compose file, the NEXT_PUBLIC_* frontend variables are automatically derived from AUTH_TYPE, ETL_SERVICE, and the port settings. In the dev compose file, they are passed as build args since the frontend is built from source.
Migrating from the All-in-One Container
If you were previously using docker-compose.quickstart.yml (the legacy all-in-one surfsense container), your data lives in a surfsense-data volume and requires a one-time migration before switching to the current setup. PostgreSQL has been upgraded from version 14 to 17, so a simple volume swap will not work.
See the full step-by-step guide: Migrate from the All-in-One Container.
Useful Commands
# View logs (all services)
docker compose logs -f
# View logs for a specific service
docker compose logs -f backend
docker compose logs -f electric
# Stop all services
docker compose down
# Restart a specific service
docker compose restart backend
# Stop and remove all containers + volumes (destructive!)
docker compose down -vTroubleshooting
- Ports already in use — Change the relevant
*_PORTvariable in.envand restart. - Permission errors on Linux — You may need to prefix
dockercommands withsudo. - Electric SQL not connecting — Check
docker compose logs electric. If it showsdomain does not exist: db, ensureELECTRIC_DATABASE_URLis not set to a stale value in.env. - Real-time updates not working in browser — Open DevTools → Console and look for
[Electric]errors. Check thatNEXT_PUBLIC_ELECTRIC_URLmatches the running Electric SQL address. - Line ending issues on Windows — Run
git config --global core.autocrlf truebefore cloning.
