Host Your Own AI Agent with OpenClaw - Free 1-Click Setup!

How to Self-Host Tracearr on a VPS with Docker (2026) 

By the end of this guide you’ll have a fully operational Tracearr instance running behind HTTPS on a VPS, connected to your Plex, Jellyfin, or Emby media server. Tracearr will track active streams in real time, map viewer locations, and optionally flag account sharing — all persisted in PostgreSQL and cached in Redis, with your data staying in an EU data center. 

Prerequisites

  • A VPS with at least 2 GB of RAM for example Contabo Cloud VPS 4 or Cloud VPS 6 are good fits thanks to their RAM-to-price ratio and EU (Germany) data center locations. 
  • Docker and Docker Compose installed on the server. 
  • A domain or subdomain with an A record pointing at your VPS IP. 
  • API credentials for at least one media server: a Plex token, Jellyfin API key, or Emby API key. 

Step 1: Prepare the VPS 

  1. Provision your VPS through your provider’s control panel and wait for it to finish booting. 
  1. SSH in: ssh user@your-server-ip 
  1. Install Docker using the official install script at docs.docker.com/engine/install, then confirm both tools are available: 
docker --version
docker compose version 

Step 2: docker-compose.yml — Tracearr + PostgreSQL + Redis 

Create a working directory, generate two random secrets, and write them to .env: 

mkdir -p /opt/tracearr && cd /opt/tracearr 
echo "JWT_SECRET=$(openssl rand -hex 32)" > .env
echo "COOKIE_SECRET=$(openssl rand -hex 32)" >> .env 

Create docker-compose.yml with the following content. The stack deploys three containers: Tracearr itself, TimescaleDB (PostgreSQL 18), and Redis. TimescaleDB and Redis are hard requirements — not optional extras — which is why a VPS makes sense here: you need persistent storage, sufficient RAM to run all three, and a machine that stays online around the clock. 

services: 

  tracearr: 

    image: ghcr.io/connorgallopo/tracearr:latest 

    container_name: tracearr 

    ports: 

      - "${PORT:-3000}:3000" 

    environment: 

      - NODE_ENV=production 

      - PORT=3000 

      - HOST=0.0.0.0 

      - TZ=${TZ:-UTC} 

      - DATABASE_URL=postgres://tracearr:${DB_PASSWORD:-tracearr}@timescale:5432/tracearr 

      - REDIS_URL=redis://redis:6379 

      - JWT_SECRET=${JWT_SECRET} 

      - COOKIE_SECRET=${COOKIE_SECRET} 

      - CORS_ORIGIN=${CORS_ORIGIN:-*} 

      - LOG_LEVEL=${LOG_LEVEL:-info} 

    depends_on: 

      timescale: 

        condition: service_healthy 

      redis: 

        condition: service_healthy 

    volumes: 

      - tracearr_backups:/data/backup 

    restart: unless-stopped 

    networks: 

      - tracearr-network 

  timescale: 

    image: timescale/timescaledb-ha:pg18.1-ts2.25.0 

    container_name: tracearr-db 

    shm_size: 512mb 

    ulimits: 

      nofile: 

        soft: 65536 

        hard: 65536 

    command: postgres -c timescaledb.license=timescale 

             -c timescaledb.max_tuples_decompressed_per_dml_transaction=0 

             -c max_locks_per_transaction=4096 

             -c timescaledb.telemetry_level=off 

    environment: 

      - POSTGRES_USER=tracearr 

      - POSTGRES_PASSWORD=${DB_PASSWORD:-tracearr} 

      - POSTGRES_DB=tracearr 

    volumes: 

      - timescale_data:/home/postgres/pgdata/data 

    healthcheck: 

      test: ["CMD-SHELL", "pg_isready -U tracearr"] 

      interval: 10s 

      timeout: 5s 

      retries: 5 

    restart: unless-stopped 

    networks: 

      - tracearr-network 

  redis: 

    image: redis:8-alpine 

    container_name: tracearr-redis 

    command: redis-server --appendonly yes 

    volumes: 

      - redis_data:/data 

    healthcheck: 

      test: ["CMD", "redis-cli", "ping"] 

      interval: 10s 

      timeout: 5s 

      retries: 5 

    restart: unless-stopped 

    networks: 

      - tracearr-network 

networks: 

  tracearr-network: 

    driver: bridge 

volumes: 

  tracearr_backups: 

  timescale_data: 

  redis_data:

Named volumes are used intentionally. PostgreSQL expects its data directory to be owned by the postgres user with specific permissions, and named volumes let Docker manage that ownership cleanly. Bind mounts can cause permission conflicts on hosts with non-standard filesystem setups. 

Step 3: First Run and Connect Your Media Servers 

  1. Start the stack: From your working directory, run docker compose up -d. Docker pulls the images and waits for TimescaleDB and Redis to pass their health checks before Tracearr starts. 
  1. Open the setup screen: Navigate to http://your-server-ip:3000 in a browser. Create your admin account on the first-run screen. 
  1. Add your media server: Go to Settings > Servers. For Plex, enter your Plex token. For Jellyfin or Emby, enter the server URL and an API key from that server’s dashboard. 
  1. Migrate from Tautulli (optional): Go to Settings > Import. Enter your Tautulli URL and API key (found under Tautulli’s Settings > Web Interface), select the target Plex server, and click Start Import. Tracearr fetches your full session history through the API — no file export needed. Running the import more than once is safe; duplicate records are skipped automatically. 
  1. Migrate from Jellystat (optional): Export an Activity backup from Jellystat’s Settings > Backup panel, then upload it through the same Import screen. 

Step 4: Reverse Proxy and HTTPS 

Port 3000 exposed directly is fine for testing. For production, put Tracearr behind a reverse proxy with TLS. Two common options: 

Caddy (easiest — automatic TLS) 

Open your Caddyfile and add the following block, replacing tracearr.yourdomain.com with your actual domain. Caddy provisions a Let’s Encrypt certificate and renews it automatically. 

tracearr.yourdomain.com { 

    reverse_proxy localhost:3000 

}

Apply with: caddy reload 

Nginx + Certbot 

Create a server block at /etc/nginx/sites-available/tracearr: 

server { 

    listen 80; 

    server_name tracearr.yourdomain.com; 

    location / { 

        proxy_pass http://localhost:3000; 

        proxy_http_version 1.1; 

        proxy_set_header Upgrade $http_upgrade; 

        proxy_set_header Connection 'upgrade'; 

        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_cache_bypass $http_upgrade; 

    } 

}
ln -s /etc/nginx/sites-available/tracearr /etc/nginx/sites-enabled/ 
certbot --nginx -d tracearr.yourdomain.com 

Once HTTPS is live, update CORS_ORIGIN in .env to your HTTPS domain and restart the stack: 

docker compose up -d 

Step 5: Set Up Sharing-Detection Rules 

Go to Configuration > Rules and click New Rule. Tracearr evaluates rules in real time against every active session. 

Concurrent stream limit: Set Concurrent Streams is greater than 2 (adjust to your household). Enable the Unique IPs checkbox so multiple devices on the same home network don’t count as separate streams. 

Geographic anomaly detection: Use the Active Session Distance field to flag sessions starting simultaneously from locations more than a set number of kilometers apart — a strong signal for sharing outside a single household. Pair it with Is Local Network is No to avoid false positives from local devices. 

Each rule can trigger a Discord or webhook notification, adjust a user’s trust score, send a message to the client, or terminate the stream outright. 

Why Run Tracearr on Contabo 

Tracearr runs continuously with a low baseline memory footprint, which makes flat-rate VPS pricing a natural fit. Contabo’s Cloud VPS line starts around 4.50 EUR per month for 4 vCPU cores and 8 GB of RAM — enough to run the Tracearr container, TimescaleDB, and Redis with headroom for a reverse proxy and other services. There’s no per-hour metering that accumulates from 24/7 uptime. The EU data centers in Germany keep your viewers’ watch history in a jurisdiction you can reason about. If analytics load grows, the NVMe-backed tier handles PostgreSQL’s frequent small reads and writes noticeably faster than standard SSD storage. 

FAQ: Self-Hosting Tracearr

Does Tracearr need PostgreSQL and Redis? 

Yes — both are hard requirements. Tracearr needs a PostgreSQL-compatible database and Redis to function. The recommended database image is TimescaleDB, which extends PostgreSQL with time-series optimizations that Tracearr’s analytics and session history depend on. Redis handles caching and real-time session state. Neither can be skipped, and running Tracearr against plain PostgreSQL without the TimescaleDB extension is not supported. 

How much RAM does Tracearr need? 

The Tracearr container uses roughly 1 GB of RAM. Add TimescaleDB and Redis and the practical minimum for the full stack lands around 2 GB. For comfortable operation — especially when handling multiple media servers or importing large Tautulli datasets — 2–4 GB is recommended. A Contabo Cloud VPS 4 with 8 GB gives you plenty of headroom above the minimum. 

Can I migrate from Tautulli to Tracearr? 

Yes. Tracearr has a built-in Tautulli import that connects directly to your existing Tautulli instance through its API — no file export needed. Go to Settings > Import, enter your Tautulli URL and API key, select the target Plex server, and start the import. Tracearr fetches your full session history in pages and skips duplicates automatically, so running the import more than once is safe. After importing, run the Fix Imported Progress and Backfill User Dates maintenance jobs from Settings > Jobs to ensure all timestamps and progress values are populated correctly. 

How do I put Tracearr behind HTTPS? 

Point a domain or subdomain at your VPS IP, then set up a reverse proxy on the host to forward requests to Tracearr on port 3000. Caddy is the quickest option: it provisions and renews Let’s Encrypt certificates automatically with no extra tooling. Nginx paired with Certbot is equally valid and widely documented. Whichever you choose, make sure the proxy forwards the X-Forwarded-For and X-Forwarded-Proto headers so Tracearr receives accurate client IP addresses for geolocation. After switching to HTTPS, update CORS_ORIGIN in your .env file to your HTTPS domain and restart the compose stack. 

Scroll to Top