SurfSense Docker & Self-Hosted Deployment

GitHub

Open-source NotebookLM alternative. Research the open web with live data(Reddit, YT, IG, TikTok, Indeed, Google Search, Maps etc) through one platform, API...

15,651 stars Python Python 1 file ยท ~8,248 tokens #agent#agents#ai#fastapi#langchain#langgraph#nextjs#notebooklm
RAW Compose
1. Pull Image (DOCKERHUB) docker pull pgvector/pgvector
2. Docker Run (1-Click Launch) docker run -d --name surfsense -p 5432:5432 --restart always pgvector/pgvector:pg17
3. Podman Run podman run -d --name surfsense -p 5432:5432 --restart always pgvector/pgvector:pg17
4. Skopeo Inspect (Zero-Pull Manifest) skopeo inspect docker://docker.io/pgvector/pgvector:pg17
5. ORAS Pull (OCI Artifact) oras pull pgvector/pgvector:pg17
6. Compose Deployment docker compose up -d
docker/docker-compose.deps-only.yml
Download .yml
# =============================================================================
# SurfSense โ€” Dependencies only (no backend / frontend / Celery images)
# =============================================================================
# Postgres, Redis, pgAdmin, Zero โ€” run API + Next + Celery on the host.
# Celery is not Dockerized here: use `uv run` from surfsense_backend/ (no extra
# backend image build just for workers).
#
# From repo root (SurfSense/):
#   docker compose -f docker/docker-compose.deps-only.yml up -d
#
# Compose variable substitution uses `docker/.env` (copy from .env.example).
# Bind mounts use ./postgresql.conf in this directory.
#
# Local Celery (from surfsense_backend/, after Redis is up):
#   uv run celery -A celery_worker.celery_app worker --loglevel=info --concurrency=1 --pool=solo --queues=surfsense,surfsense.connectors
#   uv run celery -A celery_worker.celery_app beat --loglevel=info
#
# Host setup:
#   - Backend .env: DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/surfsense
#   - Backend .env: CELERY_BROKER_URL / REDIS_APP_URL โ†’ redis://localhost:6379/0
#   - Web .env: NEXT_PUBLIC_ZERO_CACHE_URL=http://localhost:${ZERO_CACHE_PORT:-4848}
#
# IMPORTANT โ€” schema migrations:
#   This compose file does NOT build the backend image and therefore cannot
#   run a `migrations` service. You MUST run alembic on the host before
#   bringing zero-cache up, or zero-cache will crash-loop with
#   `Unknown or invalid publications. Specified: [zero_publication]`.
#
#   First-time / after-pull:
#     cd surfsense_backend && uv run alembic upgrade head
#
#   The other compose files (docker-compose.yml, docker-compose.dev.yml)
#   handle this automatically via a dedicated `migrations` service.
# =============================================================================

name: surfsense-deps

services:
  db:
    image: pgvector/pgvector:pg17
    ports:
      - "${POSTGRES_PORT:-5432}:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./postgresql.conf:/etc/postgresql/postgresql.conf:ro
    environment:
      - POSTGRES_USER=${DB_USER:-postgres}
      - POSTGRES_PASSWORD=${DB_PASSWORD:-postgres}
      - POSTGRES_DB=${DB_NAME:-surfsense}
    command: postgres -c config_file=/etc/postgresql/postgresql.conf
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-surfsense}"]
      interval: 10s
      timeout: 5s
      retries: 5

  pgadmin:
    image: dpage/pgadmin4
    ports:
      - "${PGADMIN_PORT:-5050}:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL:[email protected]}
      - PGADMIN_DEFAULT_PASSWORD=${PGADMIN_DEFAULT_PASSWORD:-surfsense}
    volumes:
      - pgadmin_data:/var/lib/pgadmin
    depends_on:
      - db

  redis:
    image: redis:8-alpine
    ports:
      - "${REDIS_PORT:-6379}:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Google Search scraper fallback. The host-run backend reaches it with
  # SEARXNG_URL=http://localhost:8888.
  searxng:
    image: searxng/searxng:2026.3.13-3c1f68c59
    ports:
      - "${SEARXNG_PORT:-8888}:8080"
    volumes:
      - ./searxng:/etc/searxng
    environment:
      - SEARXNG_SECRET=${SEARXNG_SECRET:-surfsense-searxng-secret}
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/healthz"]
      interval: 10s
      timeout: 5s
      retries: 5

  # NOTE: zero-cache requires the `zero_publication` Postgres publication to
  # exist before it starts. In this deps-only stack there is no backend
  # container to run migrations, so you must run `uv run alembic upgrade head`
  # from `surfsense_backend/` on the host BEFORE `docker compose up -d`.
  zero-cache:
    image: rocicorp/zero:1.6.0
    ports:
      - "${ZERO_CACHE_PORT:-4848}:4848"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    depends_on:
      db:
        condition: service_healthy
    environment:
      - ZERO_UPSTREAM_DB=postgresql://${DB_USER:-postgres}:${DB_PASSWORD:-postgres}@db:5432/${DB_NAME:-surfsense}?sslmode=${DB_SSLMODE:-disable}
      - ZERO_CVR_DB=postgresql://${DB_USER:-postgres}:${DB_PASSWORD:-postgres}@db:5432/${DB_NAME:-surfsense}?sslmode=${DB_SSLMODE:-disable}
      - ZERO_CHANGE_DB=postgresql://${DB_USER:-postgres}:${DB_PASSWORD:-postgres}@db:5432/${DB_NAME:-surfsense}?sslmode=${DB_SSLMODE:-disable}
      - ZERO_REPLICA_FILE=/data/zero.db
      - ZERO_ADMIN_PASSWORD=${ZERO_ADMIN_PASSWORD:-surfsense-zero-admin}
      - ZERO_APP_PUBLICATIONS=${ZERO_APP_PUBLICATIONS:-zero_publication}
      - ZERO_AUTO_RESET=${ZERO_AUTO_RESET:-true}
      - ZERO_NUM_SYNC_WORKERS=${ZERO_NUM_SYNC_WORKERS:-4}
      - ZERO_UPSTREAM_MAX_CONNS=${ZERO_UPSTREAM_MAX_CONNS:-20}
      - ZERO_CVR_MAX_CONNS=${ZERO_CVR_MAX_CONNS:-30}
      - ZERO_QUERY_URL=${ZERO_QUERY_URL:-http://host.docker.internal:3000/api/zero/query}
      - ZERO_MUTATE_URL=${ZERO_MUTATE_URL:-http://host.docker.internal:3000/api/zero/mutate}
      - ZERO_QUERY_FORWARD_COOKIES=${ZERO_QUERY_FORWARD_COOKIES:-true}
      - ZERO_QUERY_API_KEY=${ZERO_QUERY_API_KEY:-}
      - ZERO_AUTH_REVALIDATE_INTERVAL_SECONDS=${ZERO_AUTH_REVALIDATE_INTERVAL_SECONDS:-60}
      - ZERO_AUTH_RETRANSFORM_INTERVAL_SECONDS=${ZERO_AUTH_RETRANSFORM_INTERVAL_SECONDS:-60}
    volumes:
      - zero_cache_data:/data
    restart: unless-stopped
    stop_grace_period: 300s
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4848/keepalive"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 600s

  # Sandbox control plane for code execution / artifact generation. It spawns
  # sandbox containers on the host daemon as siblings, so it needs the socket.
  # The host-run backend reaches it with OPENSANDBOX_DOMAIN=localhost:8080.
  opensandbox-server:
    image: opensandbox/server:v0.2.2
    ports:
      - "${OPENSANDBOX_PORT:-8080}:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./opensandbox/sandbox.toml:/etc/opensandbox/config.toml:ro
      - opensandbox_data:/data
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      - OPENSANDBOX_SERVER_API_KEY=${OPENSANDBOX_API_KEY:-surfsense-dev-sandbox}
    mem_limit: ${OPENSANDBOX_SERVER_MEMORY_LIMIT:-512m}
    restart: unless-stopped
    # The runtime image ships neither curl nor wget; python is on PATH.
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8080/health', timeout=3).status == 200 else 1)"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s

  # OPTIONAL โ€” Azurite emulates Azure Blob Storage for testing the Azure
  # original-file backend. The default filesystem backend needs none of this.
  # To exercise it, set in surfsense_backend/.env:
  #   FILE_STORAGE_BACKEND=azure
  #   AZURE_STORAGE_CONTAINER=surfsense-documents
  #   AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:${AZURITE_BLOB_PORT:-10000}/devstoreaccount1;
  # The backend creates blobs on upload; create the container once first
  # (Azure CLI / Storage Explorer), then upload a document.
  azurite:
    image: mcr.microsoft.com/azure-storage/azurite:3.33.0
    command: azurite-blob --blobHost 0.0.0.0 --blobPort 10000
    ports:
      - "${AZURITE_BLOB_PORT:-10000}:10000"
    volumes:
      - azurite_data:/data
    restart: unless-stopped

volumes:
  postgres_data:
    name: surfsense-deps-postgres
  pgadmin_data:
    name: surfsense-deps-pgadmin
  redis_data:
    name: surfsense-deps-redis
  zero_cache_data:
    name: surfsense-deps-zero-cache
  azurite_data:
    name: surfsense-deps-azurite
  opensandbox_data:
    name: surfsense-deps-opensandbox