Self-Hosting Guide
This guide covers everything needed to run BetBlocker on your own infrastructure. The self-hosted deployment is identical in functionality to the hosted platform except billing and the automated discovery pipeline are disabled.
System Requirements
Section titled “System Requirements”Minimum (single user, personal use)
Section titled “Minimum (single user, personal use)”| Resource | Minimum |
|---|---|
| CPU | 1 vCPU |
| RAM | 1 GB |
| Disk | 5 GB |
| OS | Any Linux, macOS, or Windows with Docker |
Recommended (small organisation, up to ~50 devices)
Section titled “Recommended (small organisation, up to ~50 devices)”| Resource | Recommended |
|---|---|
| CPU | 2 vCPU |
| RAM | 2 GB |
| Disk | 20 GB SSD |
| OS | Ubuntu 22.04 LTS / Debian 12 |
Network requirements
Section titled “Network requirements”- Outbound HTTPS to
feed.betblocker.org(community blocklist sync) - Inbound on your chosen
API_PORT(default8443) reachable by enrolled devices - Inbound on
WEB_PORT(default80) for the web dashboard
Docker Deployment (Recommended)
Section titled “Docker Deployment (Recommended)”1. Install Docker
Section titled “1. Install Docker”# Ubuntu / Debiancurl -fsSL https://get.docker.com | shsudo usermod -aG docker $USER# Log out and back in for group change to take effectVerify: docker compose version — must be v2.x (docker compose, not docker-compose).
2. Get the deployment files
Section titled “2. Get the deployment files”git clone https://github.com/JerrettDavis/BetBlocker.gitcd betblocker/deployOr download just the deploy/ directory from a release archive if you prefer not to clone the full repo.
3. Configure environment
Section titled “3. Configure environment”cp .env.example .envEdit .env:
# REQUIRED: change thisDB_PASSWORD=use-a-long-random-string-here
# REQUIRED: must be reachable by enrolled devicesBETBLOCKER_EXTERNAL_URL=https://betblocker.example.com:8443
# Optional overrides (defaults shown)# BETBLOCKER_VERSION=latest# API_PORT=8443# WEB_PORT=80# LOG_LEVEL=info# BETBLOCKER_COMMUNITY_FEED_URL=https://feed.betblocker.org/v14. Start services
Section titled “4. Start services”docker compose up -ddocker compose ps # wait for all services to be healthy5. Run first-time setup
Section titled “5. Run first-time setup”docker compose exec api /betblocker-api setupThis runs migrations, generates keys, and prompts for the initial admin account. Run it once. Running it again on an already-initialised instance is a no-op.
6. Access the dashboard
Section titled “6. Access the dashboard”Open http://your-server (or https:// if you have TLS configured — see below). Log in with the admin credentials created in step 5.
Manual Deployment (Build from Source)
Section titled “Manual Deployment (Build from Source)”Use this path if you cannot use Docker or need to customise the build.
Prerequisites
Section titled “Prerequisites”# Rust (version pinned in rust-toolchain.toml)curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Node.js 20+curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -sudo apt-get install -y nodejs
# PostgreSQL 16 + TimescaleDB# See: https://docs.timescale.com/self-hosted/latest/install/# API and workercargo build --release -p bb-api -p bb-worker
# Web dashboardcd web && npm ci && npm run buildBinaries will be at target/release/bb-api and target/release/bb-worker.
# Set all required environment variables (see Environment Variables section)export BB_DATABASE_URL="postgres://betblocker:password@localhost:5432/betblocker"export BB_REDIS_URL="redis://localhost:6379"export BB_JWT_PRIVATE_KEY_PATH="/etc/betblocker/jwt-signing.pem"export BB_JWT_PUBLIC_KEY_PATH="/etc/betblocker/jwt-signing-pub.pem"
./target/release/bb-api serve &./target/release/bb-worker &
# Web dashboardcd web && npm startFor production, wrap each process in a systemd unit. See deploy/systemd/ for example unit files.
Database Setup
Section titled “Database Setup”The Docker deployment handles this automatically. If deploying manually:
Install TimescaleDB
Section titled “Install TimescaleDB”TimescaleDB is a PostgreSQL extension — it runs as a standard PostgreSQL instance:
# After installing PostgreSQL 16 and the timescaledb package:sudo -u postgres psql -c "CREATE USER betblocker WITH PASSWORD 'your-password';"sudo -u postgres psql -c "CREATE DATABASE betblocker OWNER betblocker;"sudo -u postgres psql -d betblocker -c "CREATE EXTENSION IF NOT EXISTS timescaledb;"Run migrations
Section titled “Run migrations”./target/release/bb-api migrateMigrations are embedded in the binary and run automatically via setup or can be run explicitly with migrate.
TimescaleDB hypertables
Section titled “TimescaleDB hypertables”The worker creates hypertable partitioning on the events table during first setup. No manual SQL is needed.
Environment Variables Reference
Section titled “Environment Variables Reference”All variables use the BB_ prefix (e.g., BB_DATABASE_URL). In docker-compose.yml some variables are named without the prefix for Docker compatibility — the table below shows both forms where they differ.
| Variable | Required | Default | Description |
|---|---|---|---|
BB_DATABASE_URL | Yes | — | PostgreSQL connection string |
BB_REDIS_URL | No | redis://localhost:6379 | Redis connection URL |
BB_HOST | No | 0.0.0.0 | API bind address |
BB_PORT | No | 3000 | API bind port (Docker exposes on 8443) |
BB_JWT_PRIVATE_KEY_PATH | Yes | — | Path to Ed25519 private key PEM for JWT signing |
BB_JWT_PUBLIC_KEY_PATH | Yes | — | Path to Ed25519 public key PEM for JWT verification |
BB_JWT_ACCESS_TOKEN_TTL_SECS | No | 3600 | Access token lifetime in seconds |
BB_JWT_REFRESH_TOKEN_TTL_DAYS | No | 30 | Refresh token lifetime in days |
BB_CORS_ALLOWED_ORIGINS | No | * | Comma-separated allowed CORS origins |
BB_PUBLIC_BASE_URL | No | — | Public URL used in generated links and QR codes |
BB_BILLING_ENABLED | No | false | Enable Stripe billing endpoints (hosted only) |
BB_STRIPE_SECRET_KEY | Conditional | — | Required when BILLING_ENABLED=true |
BB_STRIPE_WEBHOOK_SECRET | Conditional | — | Required when BILLING_ENABLED=true |
BETBLOCKER_DEPLOYMENT | No | — | Set to self-hosted to disable hosted-only features |
BETBLOCKER_COMMUNITY_FEED_URL | No | https://feed.betblocker.org/v1 | Blocklist feed URL |
BETBLOCKER_FEDERATED_REPORT_UPSTREAM | No | — | Opt-in: upstream URL for federated report contribution |
BETBLOCKER_FEDERATED_REPORT_API_KEY | No | — | API key for federated upstream |
LOG_LEVEL | No | info | Log verbosity: trace, debug, info, warn, error |
BETBLOCKER_LOG_FORMAT | No | json | Log format: json or text |
Key files (mounted via Docker volumes)
Section titled “Key files (mounted via Docker volumes)”| Path | Contents |
|---|---|
/keys/jwt-signing.key | Ed25519 JWT signing key |
/keys/blocklist-signing.key | Blocklist signing key |
/keys/root-ca.key | Root CA private key |
/keys/device-ca.key | Device certificate CA key |
Keys are generated by bb-api setup and stored in the betblocker-keys Docker volume. Back this volume up — losing it requires re-enrolling all devices.
Reverse Proxy Setup (nginx)
Section titled “Reverse Proxy Setup (nginx)”Run nginx in front of the API if you want standard HTTPS on port 443 and to avoid exposing port 8443.
nginx configuration
Section titled “nginx configuration”# Redirect HTTP to HTTPSserver { listen 80; server_name betblocker.example.com; return 301 https://$host$request_uri;}
# APIserver { listen 443 ssl http2; server_name betblocker.example.com;
ssl_certificate /etc/letsencrypt/live/betblocker.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/betblocker.example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off;
# Web dashboard location / { proxy_pass http://127.0.0.1:80; 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; }
# API location /v1/ { proxy_pass http://127.0.0.1:8443; 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; proxy_read_timeout 30s; client_max_body_size 2M; }
location /health { proxy_pass http://127.0.0.1:8443; }}sudo ln -s /etc/nginx/sites-available/betblocker /etc/nginx/sites-enabled/sudo nginx -t && sudo systemctl reload nginxAfter setting up the proxy, update .env:
BETBLOCKER_EXTERNAL_URL=https://betblocker.example.comAPI_PORT=8443 # still needed for container bindingTLS/SSL Certificates
Section titled “TLS/SSL Certificates”Let’s Encrypt (recommended for internet-facing instances)
Section titled “Let’s Encrypt (recommended for internet-facing instances)”sudo apt install certbot python3-certbot-nginxsudo certbot --nginx -d betblocker.example.comCertbot will auto-renew via a systemd timer. Verify: sudo certbot renew --dry-run
Self-signed certificate (LAN / internal use)
Section titled “Self-signed certificate (LAN / internal use)”If your instance is not internet-facing:
openssl req -x509 -newkey ed25519 \ -keyout betblocker-tls.key \ -out betblocker-tls.crt \ -days 3650 \ -nodes \ -subj "/CN=betblocker.local"Enrolled devices must trust your self-signed CA. The agent allows configuring a custom CA certificate at enrollment time.
Backup and Restore
Section titled “Backup and Restore”What to back up
Section titled “What to back up”| Data | Location | Priority |
|---|---|---|
| Database | betblocker-db Docker volume | Critical |
| Keys | betblocker-keys Docker volume | Critical — losing keys requires full re-enrollment |
| Data | betblocker-data Docker volume | Important |
.env file | deploy/.env | Important |
Database backup
Section titled “Database backup”# Dumpdocker compose exec db pg_dump -U betblocker betblocker | gzip > backup-$(date +%Y%m%d).sql.gz
# Restoregunzip -c backup-20260315.sql.gz | docker compose exec -T db psql -U betblocker betblockerVolume backup
Section titled “Volume backup”# Backup keys volume (CRITICAL)docker run --rm \ -v betblocker_betblocker-keys:/keys:ro \ -v $(pwd):/backup \ alpine tar czf /backup/keys-backup-$(date +%Y%m%d).tar.gz -C /keys .Restore from backup
Section titled “Restore from backup”- Stop services:
docker compose down - Restore volumes from archives
- Restore database from dump
- Start services:
docker compose up -d
Updating to a New Version
Section titled “Updating to a New Version”cd deploy
# Pull new imagesdocker compose pull
# Restart with new images (zero-downtime if you have multiple API replicas)docker compose up -d
# Migrations run automatically on API startup# Check logs to confirmdocker compose logs api | grep -E "migration|error"To pin a specific version, set BETBLOCKER_VERSION=1.2.3 in .env.
Monitoring and Health Checks
Section titled “Monitoring and Health Checks”Health endpoint
Section titled “Health endpoint”curl -s https://your-server/health | jq .# {"status": "ok", "version": "1.2.3", "db": "ok", "cache": "ok"}Container health
Section titled “Container health”All containers expose Docker health checks. Integrate with your monitoring tool:
# Quick statusdocker compose ps
# JSON output for scriptingdocker compose ps --format jsonLog aggregation
Section titled “Log aggregation”Logs are emitted as JSON to stdout (configurable via BETBLOCKER_LOG_FORMAT). Pipe to any log aggregator:
# Example: ship to a syslog targetdocker compose logs -f api | logger -t betblocker-api
# Example: forward with Filebeat / Fluentd# Point your agent at the Docker daemon's log driver outputKey metrics to alert on
Section titled “Key metrics to alert on”| Metric | Alert condition |
|---|---|
| Container restarts | > 2 in 5 minutes |
| API health endpoint | Non-ok response |
| Database disk | > 80% full |
| Worker last run | No successful run in 1 hour |
| Heartbeat failures | Spike in missed device heartbeats |
Disk usage
Section titled “Disk usage”The database grows as events accumulate. TimescaleDB’s compression and retention policies reduce this significantly. Configure retention from Admin > Analytics Settings in the dashboard.
Federated Reporting (Opt-In)
Section titled “Federated Reporting (Opt-In)”Self-hosted instances can contribute unknown domain reports back to the central BetBlocker blocklist. This strengthens the community feed for everyone.
To opt in, add to .env:
BETBLOCKER_FEDERATED_REPORT_UPSTREAM=https://api.betblocker.org/v1/reportsBETBLOCKER_FEDERATED_REPORT_API_KEY=your-key-from-betblocker.comPrivacy guarantee: only blocked/flagged domain metadata is sent — never full browsing history, never user-identifying information, never device identifiers. Source IP addresses are stripped before federated report processing.