Deployment

Habeas runs as two containers from the same image. The engine runs libtorrent + the FastAPI backend. The web service runs the SvelteKit frontend and proxies API calls to the engine.

docket-engine libtorrent + FastAPI :54323
← HTTP + WS →
docket-web SvelteKit + proxy :54322
browser

Network modes

Pick one compose file. Each wires the two containers differently based on how you want traffic routed.

Default bridge networking. No VPN tunnel. Good for testing or if your host already has a VPN interface you handle separately.

docker-compose.yml
services:
  docket-engine:
    build: .
    container_name: docket-engine
    env_file: .env
    environment:
      - DOCKET_NETWORK_PROFILE=direct
      - VPN_COMPOSE_MODE=container
    volumes:
      - ./data:/app/data
      - ./logs:/app/logs
      - ${DOCKET_STORAGE_MOUNT:-./storage:/storage}
    ports:
      - "${DOCKET_ENGINE_PORT:-54323}:54323"
    restart: unless-stopped
    command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "54323"]

  docket-web:
    build: .
    container_name: docket-web
    depends_on:
      docket-engine:
        condition: service_healthy
    env_file: .env
    environment:
      - ENGINE_URL=http://docket-engine:54323
      - ENGINE_WS_URL=ws://docket-engine:54323/ws
    volumes:
      - ./data:/app/data
      - ${DOCKET_STORAGE_MOUNT_RO:-./storage:/storage:ro}
    ports:
      - "${DOCKET_WEB_PORT:-54322}:54322"
    restart: unless-stopped
    command: ["uvicorn", "web_service:app", "--host", "0.0.0.0", "--port", "54322"]
note In gluetun and host modes, both containers share a network namespace. The web service reaches the engine at 127.0.0.1 instead of using Docker DNS.

Quick start

1
git clone https://github.com/palaV/Habeas && cd Habeas
2
cp .env.example .env

Edit .env to set your ports, storage path, timezone, and network profile.

3
docker compose -f docker-compose.yml up -d

Open http://localhost:54322. First visit triggers passkey registration.

Reverse proxy

Passkey authentication (WebAuthn) requires HTTPS with a valid origin. Put Habeas behind a reverse proxy.

Caddyfile
habeas.yourdomain.com {
    reverse_proxy localhost:54322
}

Then set in your .env:

FRONTEND_URL=https://habeas.yourdomain.com
TRUST_PROXY=true
TRUSTED_PROXY_IPS=127.0.0.1,::1
COOKIE_SECURE=true

Storage

The engine writes torrent data to the path configured via DOCKET_STORAGE_MOUNT. The web service mounts the same path read-only for the file browser.

DOCKET_STORAGE_MOUNT ./storage:/storage Engine: read-write mount for downloads
DOCKET_STORAGE_MOUNT_RO ./storage:/storage:ro Web: read-only mount for file browser
DOCKET_DEFAULT_DOWNLOAD_PATH /storage/Downloads Default download directory inside the container
tip Both mounts must resolve to the same host directory. The engine needs write access; the web service only reads. Data (SQLite DB, secrets) lives in ./data.

Persistent data

./data:/app/data both SQLite DB, secrets, libtorrent resume data, settings
./logs:/app/logs both Structured log files (rotated, configurable size)
/storage engine rw, web ro Torrent downloads and organized media

Config generator

Fill in your settings and copy the generated .env. Then grab the matching compose file from the repo.

.env
DOCKET_WEB_PORT=54322
DOCKET_ENGINE_PORT=54323
DOCKET_NETWORK_PROFILE=direct
VPN_COMPOSE_MODE=container
DOCKET_STORAGE_MOUNT=/mnt/media:/storage
DOCKET_STORAGE_MOUNT_RO=/mnt/media:/storage:ro
TZ=America/New_York
PUID=1000
PGID=1000
docker compose -f docker-compose.yml up -d

All environment variables

The generator covers the basics. Below is the full reference for .env.

Core

DOCKET_ENGINE_PORT 54323 Engine API port
DOCKET_WEB_PORT 54322 Web UI port
FRONTEND_URL http://localhost:54322 Public URL for CORS, cookies, passkey origin
DOCKET_NETWORK_PROFILE direct Deployment mode: direct, host, or gluetun
VPN_COMPOSE_MODE container How containers reach each other: container or host
TRUST_PROXY false Enable if behind a reverse proxy (Caddy, nginx, Traefik)
TRUSTED_PROXY_IPS 127.0.0.1,::1 IPs allowed to set X-Forwarded headers
PUID / PGID 1000 User/group ID for file ownership inside containers
TZ America/New_York Timezone for logs and scheduling
DOCKET_STORAGE_MOUNT ./storage:/storage Host:container path for torrent downloads
COOKIE_SECURE true Secure cookie flag. Set false for HTTP-only dev.

Security

IP_ALLOWLIST_ENABLED false Restrict access to specific IPs
IP_ALLOWLIST Comma-separated CIDRs (e.g. 192.168.1.0/24)
FAIL2BAN_ENABLED true Auto-block IPs after repeated auth failures
DB_ENCRYPTION_ENABLED false Encrypt SQLite database at rest
DB_ENCRYPTION_KEY Encryption key if DB encryption is enabled
SESSION_SECRET (auto) Override auto-generated session signing key
CORS_ORIGINS Additional allowed CORS origins
RP_ID (derived) WebAuthn relying party ID for passkey auth

qBittorrent API

QBIT_USERNAME Username for the qBittorrent-compatible API
QBIT_PASSWORD Password for the qBittorrent-compatible API
QBIT_SESSION_TTL 86400 Session duration in seconds (default 24h)

Tracker automation

DOCKET_AUTO_TRACKER_NORMALIZATION false Normalize tracker URLs on add
DOCKET_AUTO_TRACKER_HTTPS_UPGRADE false Upgrade HTTP tracker URLs to HTTPS
DOCKET_AUTO_TRACKER_HTTP_FALLBACK false Fall back to HTTP if HTTPS tracker fails
DOCKET_ALLOW_INSECURE_TRACKER_TLS false Accept invalid TLS certs from trackers

Fetch safety

DOCKET_ALLOW_PRIVATE_FETCH false Allow fetching from private/internal IPs (SSRF risk)
DOCKET_FETCH_HOST_ALLOWLIST Comma-separated hosts exempt from SSRF checks
DOCKET_COOKIE_FORWARD_HOST_ALLOWLIST Hosts that receive forwarded cookies on fetch

Logging

LOG_CONSOLE false Echo logs to stdout (useful in Docker)
LOG_LEVEL INFO Minimum log level: DEBUG, INFO, WARNING, ERROR
LOG_MAX_BYTES 10485760 Max log file size before rotation (10MB)
LOG_BACKUP_COUNT 5 Number of rotated log files to keep