Self-HostingDocker

Docker Self-Hosting

Surfinguard can be self-hosted using Docker for on-premises deployments where data must not leave your infrastructure. The on-prem version uses SQLite instead of Cloudflare D1 and runs as a standard Node.js server.

Prerequisites

  • Docker and Docker Compose installed
  • At least 512MB RAM and 1GB disk space

Quick Start

1. Create docker-compose.yml

version: '3.8'
 
services:
  surfinguard:
    image: surfinguard/api:latest
    ports:
      - "8787:8787"
    environment:
      - DEPLOYMENT=on-prem
      - AUTH_REQUIRED=false
      - PORT=8787
    volumes:
      - surfinguard-data:/app/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8787/v2/health"]
      interval: 30s
      timeout: 10s
      retries: 3
 
volumes:
  surfinguard-data:

2. Start the Service

docker compose up -d

3. Verify

curl http://localhost:8787/v2/health

Expected response:

{
  "status": "ok",
  "version": "6.0.0",
  "analyzers": 18,
  "deployment": "on-prem",
  "auth": false,
  "llm": false,
  "uptime": 42
}

4. Test a Check

curl -X POST http://localhost:8787/v2/check/url \
  -H "Content-Type: application/json" \
  -d '{"url": "https://g00gle-login.tk/verify"}'

Configuration

Environment Variables

VariableDefaultDescription
DEPLOYMENTon-premMust be on-prem for Docker deployment
AUTH_REQUIREDfalseSet to true to require API key authentication
PORT8787HTTP port
DATABASE_PATH/app/data/surfinguard.dbSQLite database path
LOG_LEVELinfoLogging level (debug, info, warn, error)

Enabling Authentication

To require API keys for all requests:

environment:
  - DEPLOYMENT=on-prem
  - AUTH_REQUIRED=true

Then create an API key:

# Connect to the running container
docker compose exec surfinguard node -e "
  const db = require('better-sqlite3')('/app/data/surfinguard.db');
  const key = 'sg_live_' + require('crypto').randomBytes(20).toString('hex');
  db.prepare('INSERT INTO api_keys (id, key_hash, name, tier) VALUES (?, ?, ?, ?)').run(
    'key_' + require('crypto').randomBytes(8).toString('hex'),
    require('crypto').createHash('sha256').update(key).digest('hex'),
    'admin',
    'enterprise'
  );
  console.log('API Key:', key);
"

Data Persistence

The SQLite database is stored in the surfinguard-data volume. To back it up:

# Backup
docker compose exec surfinguard cp /app/data/surfinguard.db /app/data/backup.db
docker cp $(docker compose ps -q surfinguard):/app/data/backup.db ./surfinguard-backup.db
 
# Restore
docker cp ./surfinguard-backup.db $(docker compose ps -q surfinguard):/app/data/surfinguard.db
docker compose restart surfinguard

Production Deployment

For production environments, use the following hardened configuration:

version: '3.8'
 
services:
  surfinguard:
    image: surfinguard/api:latest
    ports:
      - "127.0.0.1:8787:8787"  # Bind to localhost only
    environment:
      - DEPLOYMENT=on-prem
      - AUTH_REQUIRED=true
      - LOG_LEVEL=warn
    volumes:
      - surfinguard-data:/app/data
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '1.0'
        reservations:
          memory: 256M
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8787/v2/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /tmp
 
  # Reverse proxy with TLS
  nginx:
    image: nginx:alpine
    ports:
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - surfinguard
 
volumes:
  surfinguard-data:

Nginx Configuration

# nginx.conf
server {
    listen 443 ssl;
    server_name api.yourdomain.com;
 
    ssl_certificate /etc/nginx/certs/cert.pem;
    ssl_certificate_key /etc/nginx/certs/key.pem;
 
    location / {
        proxy_pass http://surfinguard:8787;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

SQLite Backend

The on-prem version uses better-sqlite3 which implements the same D1 interface used in the Cloudflare deployment. All features work identically:

  • API key authentication and tier-based rate limiting
  • Event pipeline with TTL-based expiration
  • Session tracking
  • Policy management
  • Organization management and audit logging

The SQLite database is automatically initialized with all required tables on first run.

Connecting SDKs

Point your SDK at the self-hosted instance:

// JavaScript
const guard = new Guard({
  mode: 'api',
  apiKey: 'sg_live_your_self_hosted_key',
  baseUrl: 'https://api.yourdomain.com',
});
# Python
guard = Guard(
    api_key="sg_live_your_self_hosted_key",
    base_url="https://api.yourdomain.com",
)

Limitations

The self-hosted version does not include:

  • LLM enhancement: The enhance: true flag has no effect (requires Workers AI or Gemini API access)
  • Threat feed: Community threat intelligence is not available in on-prem mode
  • Telemetry: No telemetry data is collected or sent

All 18 analyzers and 152 threat patterns work identically in both cloud and self-hosted modes since they run locally via the CoreEngine.

Updating

docker compose pull
docker compose up -d

The SQLite schema is automatically migrated on startup.