{"id":32101,"date":"2026-06-18T22:04:37","date_gmt":"2026-06-18T20:04:37","guid":{"rendered":"https:\/\/contabo.com\/blog\/?p=32101"},"modified":"2026-07-13T20:17:20","modified_gmt":"2026-07-13T18:17:20","slug":"self-host-tracearr-vps-docker","status":"publish","type":"post","link":"https:\/\/contabo.com\/blog\/self-host-tracearr-vps-docker\/","title":{"rendered":"How to Self-Host Tracearr on a VPS with Docker (2026)\u00a0"},"content":{"rendered":"\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"630\" src=\"https:\/\/contabo.com\/blog\/wp-content\/uploads\/2026\/07\/blog-head_how2-self-host-tracearr-on-a-vps-with-docker.webp\" alt=\"\" class=\"wp-image-32124\" srcset=\"https:\/\/contabo.com\/blog\/wp-content\/uploads\/2026\/07\/blog-head_how2-self-host-tracearr-on-a-vps-with-docker.webp 1200w, https:\/\/contabo.com\/blog\/wp-content\/uploads\/2026\/07\/blog-head_how2-self-host-tracearr-on-a-vps-with-docker-600x315.webp 600w, https:\/\/contabo.com\/blog\/wp-content\/uploads\/2026\/07\/blog-head_how2-self-host-tracearr-on-a-vps-with-docker-768x403.webp 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/figure>\n\n\n\n<p>By the end of this guide you&#8217;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 \u2014 all persisted in PostgreSQL and cached in Redis, with your data staying in an EU data center.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-prerequisites\"><strong>Prerequisites<\/strong><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A VPS with at least 2 GB of RAM for example Contabo <a href=\"https:\/\/contabo.com\/en\/vps\/cloud-vps-10\/?image=ubuntu.332&amp%3Bqty=1&amp%3Bcontract=12&amp%3Bstorage-type=cloud-vps-10-150-gb-ssd\">Cloud VPS 4<\/a> or <a href=\"https:\/\/contabo.com\/en\/vps\/cloud-vps-20\/?image=ubuntu.332&amp%3Bqty=1&amp%3Bcontract=12&amp%3Bstorage-type=cloud-vps-20-200-gb-ssd\">Cloud VPS 6<\/a> are good fits thanks to their RAM-to-price ratio and EU (Germany) data center locations.&nbsp;<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Docker and Docker Compose installed on the server.&nbsp;<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A domain or subdomain with an A record pointing at your VPS IP.&nbsp;<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>API credentials for at least one media server: a Plex token, Jellyfin API key, or Emby API key.&nbsp;<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-step-1-prepare-the-vps-nbsp\"><strong>Step 1: Prepare the VPS<\/strong>&nbsp;<\/h2>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>Provision your VPS <\/strong>through your provider&#8217;s control panel and wait for it to finish booting.&nbsp;<\/li>\n<\/ol>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li><strong>SSH in: <\/strong>ssh user@your-server-ip&nbsp;<\/li>\n<\/ol>\n\n\n\n<ol start=\"3\" class=\"wp-block-list\">\n<li><strong>Install Docker <\/strong>using the official install script at <a href=\"https:\/\/docs.docker.com\/engine\/install\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">docs.docker.com\/engine\/install<\/a>, then confirm both tools are available:&nbsp;<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>docker --version<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>docker compose version&nbsp;<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-step-2-docker-compose-yml-tracearr-postgresql-redis-nbsp\"><strong>Step 2: docker-compose.yml \u2014 Tracearr + PostgreSQL + Redis<\/strong>&nbsp;<\/h2>\n\n\n\n<p>Create a working directory, generate two random secrets, and write them to .env:&nbsp;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir -p \/opt\/tracearr &amp;&amp; cd \/opt\/tracearr&nbsp;<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>echo \"JWT_SECRET=$(openssl rand -hex 32)\" &gt; .env\necho \"COOKIE_SECRET=$(openssl rand -hex 32)\" &gt;&gt; .env&nbsp;<\/code><\/pre>\n\n\n\n<p>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 \u2014 not optional extras \u2014 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.&nbsp;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>services:&nbsp;\n\n&nbsp; tracearr:&nbsp;\n\n&nbsp;&nbsp;&nbsp; image: ghcr.io\/connorgallopo\/tracearr:latest&nbsp;\n\n&nbsp;&nbsp;&nbsp; container_name: tracearr&nbsp;\n\n&nbsp;&nbsp;&nbsp; ports:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - \"${PORT:-3000}:3000\"&nbsp;\n\n&nbsp;&nbsp;&nbsp; environment:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - NODE_ENV=production&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - PORT=3000&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - HOST=0.0.0.0&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - TZ=${TZ:-UTC}&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - DATABASE_URL=postgres:\/\/tracearr:${DB_PASSWORD:-tracearr}@timescale:5432\/tracearr&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - REDIS_URL=redis:\/\/redis:6379&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - JWT_SECRET=${JWT_SECRET}&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - COOKIE_SECRET=${COOKIE_SECRET}&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - CORS_ORIGIN=${CORS_ORIGIN:-*}&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - LOG_LEVEL=${LOG_LEVEL:-info}&nbsp;\n\n&nbsp;&nbsp;&nbsp; depends_on:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; timescale:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; condition: service_healthy&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; redis:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; condition: service_healthy&nbsp;\n\n&nbsp;&nbsp;&nbsp; volumes:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - tracearr_backups:\/data\/backup&nbsp;\n\n&nbsp;&nbsp;&nbsp; restart: unless-stopped&nbsp;\n\n&nbsp;&nbsp;&nbsp; networks:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - tracearr-network&nbsp;\n\n&nbsp; timescale:&nbsp;\n\n&nbsp;&nbsp;&nbsp; image: timescale\/timescaledb-ha:pg18.1-ts2.25.0&nbsp;\n\n&nbsp;&nbsp;&nbsp; container_name: tracearr-db&nbsp;\n\n&nbsp;&nbsp;&nbsp; shm_size: 512mb&nbsp;\n\n&nbsp;&nbsp;&nbsp; ulimits:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nofile:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; soft: 65536&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hard: 65536&nbsp;\n\n&nbsp;&nbsp;&nbsp; command: postgres -c timescaledb.license=timescale&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -c timescaledb.max_tuples_decompressed_per_dml_transaction=0&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -c max_locks_per_transaction=4096&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -c timescaledb.telemetry_level=off&nbsp;\n\n&nbsp;&nbsp;&nbsp; environment:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - POSTGRES_USER=tracearr&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - POSTGRES_PASSWORD=${DB_PASSWORD:-tracearr}&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - POSTGRES_DB=tracearr&nbsp;\n\n&nbsp;&nbsp;&nbsp; volumes:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - timescale_data:\/home\/postgres\/pgdata\/data&nbsp;\n\n&nbsp;&nbsp;&nbsp; healthcheck:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test: &#91;\"CMD-SHELL\", \"pg_isready -U tracearr\"]&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; interval: 10s&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; timeout: 5s&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; retries: 5&nbsp;\n\n&nbsp;&nbsp;&nbsp; restart: unless-stopped&nbsp;\n\n&nbsp;&nbsp;&nbsp; networks:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - tracearr-network&nbsp;\n\n&nbsp; redis:&nbsp;\n\n&nbsp;&nbsp;&nbsp; image: redis:8-alpine&nbsp;\n\n&nbsp;&nbsp;&nbsp; container_name: tracearr-redis&nbsp;\n\n&nbsp;&nbsp;&nbsp; command: redis-server --appendonly yes&nbsp;\n\n&nbsp;&nbsp;&nbsp; volumes:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - redis_data:\/data&nbsp;\n\n&nbsp;&nbsp;&nbsp; healthcheck:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test: &#91;\"CMD\", \"redis-cli\", \"ping\"]&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; interval: 10s&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; timeout: 5s&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; retries: 5&nbsp;\n\n&nbsp;&nbsp;&nbsp; restart: unless-stopped&nbsp;\n\n&nbsp;&nbsp;&nbsp; networks:&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - tracearr-network&nbsp;\n\nnetworks:&nbsp;\n\n&nbsp; tracearr-network:&nbsp;\n\n&nbsp;&nbsp;&nbsp; driver: bridge&nbsp;\n\nvolumes:&nbsp;\n\n&nbsp; tracearr_backups:&nbsp;\n\n&nbsp; timescale_data:&nbsp;\n\n&nbsp; redis_data:<\/code><\/pre>\n\n\n\n<p>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.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-step-3-first-run-and-connect-your-media-servers-nbsp\"><strong>Step 3: First Run and Connect Your Media Servers<\/strong>&nbsp;<\/h2>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>Start the stack: <\/strong>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.&nbsp;<\/li>\n<\/ol>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li><strong>Open the setup screen: <\/strong>Navigate to http:\/\/your-server-ip:3000 in a browser. Create your admin account on the first-run screen.&nbsp;<\/li>\n<\/ol>\n\n\n\n<ol start=\"3\" class=\"wp-block-list\">\n<li><strong>Add your media server: <\/strong>Go to Settings &gt; Servers. For Plex, enter your Plex token. For Jellyfin or Emby, enter the server URL and an API key from that server&#8217;s dashboard.&nbsp;<\/li>\n<\/ol>\n\n\n\n<ol start=\"4\" class=\"wp-block-list\">\n<li><strong>Migrate from Tautulli (optional): <\/strong>Go to Settings &gt; Import. Enter your Tautulli URL and API key (found under Tautulli&#8217;s Settings &gt; Web Interface), select the target Plex server, and click Start Import. Tracearr fetches your full session history through the API \u2014 no file export needed. Running the import more than once is safe; duplicate records are skipped automatically.&nbsp;<\/li>\n<\/ol>\n\n\n\n<ol start=\"5\" class=\"wp-block-list\">\n<li><strong>Migrate from Jellystat (optional): <\/strong>Export an Activity backup from Jellystat&#8217;s Settings &gt; Backup panel, then upload it through the same Import screen.&nbsp;<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-step-4-reverse-proxy-and-https-nbsp\"><strong>Step 4: Reverse Proxy and HTTPS<\/strong>&nbsp;<\/h2>\n\n\n\n<p>Port 3000 exposed directly is fine for testing. For production, put Tracearr behind a reverse proxy with TLS. Two common options:&nbsp;<\/p>\n\n\n\n<p><strong>Caddy (easiest \u2014 automatic TLS)<\/strong>&nbsp;<\/p>\n\n\n\n<p>Open your Caddyfile and add the following block, replacing tracearr.yourdomain.com with your actual domain. Caddy provisions a Let&#8217;s Encrypt certificate and renews it automatically.&nbsp;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>tracearr.yourdomain.com {&nbsp;\n\n&nbsp;&nbsp;&nbsp; reverse_proxy localhost:3000&nbsp;\n\n}<\/code><\/pre>\n\n\n\n<p>Apply with: <em>caddy reload&nbsp;<\/em><\/p>\n\n\n\n<p><strong>Nginx + Certbot<\/strong>&nbsp;<\/p>\n\n\n\n<p>Create a server block at \/etc\/nginx\/sites-available\/tracearr:&nbsp;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>server {&nbsp;\n\n&nbsp;&nbsp;&nbsp; listen 80;&nbsp;\n\n&nbsp;&nbsp;&nbsp; server_name tracearr.yourdomain.com;&nbsp;\n\n&nbsp;&nbsp;&nbsp; location \/ {&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; proxy_pass http:\/\/localhost:3000;&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; proxy_http_version 1.1;&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; proxy_set_header Upgrade $http_upgrade;&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; proxy_set_header Connection 'upgrade';&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; proxy_set_header Host $host;&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; proxy_set_header X-Real-IP $remote_addr;&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; proxy_set_header X-Forwarded-Proto $scheme;&nbsp;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; proxy_cache_bypass $http_upgrade;&nbsp;\n\n&nbsp;&nbsp;&nbsp; }&nbsp;\n\n}<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>ln -s \/etc\/nginx\/sites-available\/tracearr \/etc\/nginx\/sites-enabled\/&nbsp;<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>certbot --nginx -d tracearr.yourdomain.com&nbsp;<\/code><\/pre>\n\n\n\n<p>Once HTTPS is live, update CORS_ORIGIN in .env to your HTTPS domain and restart the stack:&nbsp;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker compose up -d&nbsp;<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-step-5-set-up-sharing-detection-rules-nbsp\"><strong>Step 5: Set Up Sharing-Detection Rules<\/strong>&nbsp;<\/h2>\n\n\n\n<p>Go to Configuration &gt; Rules and click New Rule. Tracearr evaluates rules in real time against every active session.&nbsp;<\/p>\n\n\n\n<p><strong>Concurrent stream limit: <\/strong>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&#8217;t count as separate streams.&nbsp;<\/p>\n\n\n\n<p><strong>Geographic anomaly detection: <\/strong>Use the Active Session Distance field to flag sessions starting simultaneously from locations more than a set number of kilometers apart \u2014 a strong signal for sharing outside a single household. Pair it with Is Local Network is No to avoid false positives from local devices.&nbsp;<\/p>\n\n\n\n<p>Each rule can trigger a Discord or webhook notification, adjust a user&#8217;s trust score, send a message to the client, or terminate the stream outright.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-why-run-tracearr-on-contabo-nbsp\"><strong>Why Run Tracearr on Contabo<\/strong>&nbsp;<\/h2>\n\n\n\n<p>Tracearr runs continuously with a low baseline memory footprint, which makes flat-rate VPS pricing a natural fit. Contabo&#8217;s Cloud VPS line starts around 4.50 EUR per month for 4 vCPU cores and 8 GB of RAM \u2014 enough to run the Tracearr container, TimescaleDB, and Redis with headroom for a reverse proxy and other services. There&#8217;s no per-hour metering that accumulates from 24\/7 uptime. The EU data centers in Germany keep your viewers&#8217; watch history in a jurisdiction you can reason about. If analytics load grows, the NVMe-backed tier handles PostgreSQL&#8217;s frequent small reads and writes noticeably faster than standard SSD storage.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-faq-self-hosting-tracearr\"><strong>FAQ: Self-Hosting Tracearr<\/strong><\/h2>\n\n\n\n<div class=\"schema-faq wp-block-yoast-faq-block\"><div class=\"schema-faq-section\" id=\"faq-question-1783886952594\"><strong class=\"schema-faq-question\"><strong>Does Tracearr need PostgreSQL and Redis?<\/strong>\u00a0<\/strong> <p class=\"schema-faq-answer\">Yes \u2014 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&#8217;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.\u00a0<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1783886959612\"><strong class=\"schema-faq-question\"><strong>How much RAM does Tracearr need?<\/strong>\u00a0<\/strong> <p class=\"schema-faq-answer\">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 \u2014 especially when handling multiple media servers or importing large Tautulli datasets \u2014 2\u20134 GB is recommended. A Contabo Cloud VPS 4 with 8 GB gives you plenty of headroom above the minimum.\u00a0<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1783886962971\"><strong class=\"schema-faq-question\"><strong>Can I migrate from Tautulli to Tracearr?<\/strong>\u00a0<\/strong> <p class=\"schema-faq-answer\">Yes. Tracearr has a built-in Tautulli import that connects directly to your existing Tautulli instance through its API \u2014 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.\u00a0<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1783886967535\"><strong class=\"schema-faq-question\"><strong>How do I put Tracearr behind HTTPS?<\/strong>\u00a0<\/strong> <p class=\"schema-faq-answer\">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&#8217;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.\u00a0<\/p> <\/div> <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Self-host Tracearr on a VPS: a 2026 step-by-step Docker Compose guide with PostgreSQL and Redis to monitor Plex, Jellyfin, and more from one dashboard.<\/p>\n","protected":false},"author":50,"featured_media":32124,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"_uag_custom_page_level_css":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":"","_members_access_role":[],"_members_access_error":""},"categories":[18],"tags":[],"ppma_author":[1491],"class_list":["post-32101","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials"],"uagb_featured_image_src":{"full":["https:\/\/contabo.com\/blog\/wp-content\/uploads\/2026\/07\/blog-head_how2-self-host-tracearr-on-a-vps-with-docker.webp",1200,630,false],"thumbnail":["https:\/\/contabo.com\/blog\/wp-content\/uploads\/2026\/07\/blog-head_how2-self-host-tracearr-on-a-vps-with-docker-150x150.webp",150,150,true],"medium":["https:\/\/contabo.com\/blog\/wp-content\/uploads\/2026\/07\/blog-head_how2-self-host-tracearr-on-a-vps-with-docker-600x315.webp",600,315,true],"medium_large":["https:\/\/contabo.com\/blog\/wp-content\/uploads\/2026\/07\/blog-head_how2-self-host-tracearr-on-a-vps-with-docker-768x403.webp",768,403,true],"large":["https:\/\/contabo.com\/blog\/wp-content\/uploads\/2026\/07\/blog-head_how2-self-host-tracearr-on-a-vps-with-docker.webp",1200,630,false],"1536x1536":["https:\/\/contabo.com\/blog\/wp-content\/uploads\/2026\/07\/blog-head_how2-self-host-tracearr-on-a-vps-with-docker.webp",1200,630,false],"2048x2048":["https:\/\/contabo.com\/blog\/wp-content\/uploads\/2026\/07\/blog-head_how2-self-host-tracearr-on-a-vps-with-docker.webp",1200,630,false]},"uagb_author_info":{"display_name":"Tobias Mildenberger","author_link":"https:\/\/contabo.com\/blog\/author\/tobias\/"},"uagb_comment_info":0,"uagb_excerpt":"Self-host Tracearr on a VPS: a 2026 step-by-step Docker Compose guide with PostgreSQL and Redis to monitor Plex, Jellyfin, and more from one dashboard.","authors":[{"term_id":1491,"user_id":50,"is_guest":0,"slug":"tobias","display_name":"Tobias Mildenberger","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/077178d5dce6c3d4c0c0396857a7e544bfdf8adf04145fff5160b33a22e28b1f?s=96&d=mm&r=g","author_category":"","user_url":"https:\/\/contabo.com\/en\/","last_name":"Mildenberger","first_name":"Tobias","job_title":"","description":""}],"_links":{"self":[{"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/posts\/32101","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/users\/50"}],"replies":[{"embeddable":true,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/comments?post=32101"}],"version-history":[{"count":2,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/posts\/32101\/revisions"}],"predecessor-version":[{"id":32129,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/posts\/32101\/revisions\/32129"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/media\/32124"}],"wp:attachment":[{"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/media?parent=32101"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/categories?post=32101"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/tags?post=32101"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=32101"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}