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 -d3. Verify
curl http://localhost:8787/v2/healthExpected 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
| Variable | Default | Description |
|---|---|---|
DEPLOYMENT | on-prem | Must be on-prem for Docker deployment |
AUTH_REQUIRED | false | Set to true to require API key authentication |
PORT | 8787 | HTTP port |
DATABASE_PATH | /app/data/surfinguard.db | SQLite database path |
LOG_LEVEL | info | Logging level (debug, info, warn, error) |
Enabling Authentication
To require API keys for all requests:
environment:
- DEPLOYMENT=on-prem
- AUTH_REQUIRED=trueThen 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 surfinguardProduction 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: trueflag 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 -dThe SQLite schema is automatically migrated on startup.