Trigger.dev's cloud is the easiest starting point, but for teams with strict data-residency requirements or high job volumes, self-hosting gives you full control. As of v4, the self-hosted platform is a multi-service stack rather than a single container: a webapp control plane (dashboard, API, Postgres, Redis, a built-in container registry, and object storage) plus a worker plane that runs each task in its own Docker container. Railway can comfortably host the control plane; the worker needs a Docker-capable machine.
This guide walks through a complete v4 self-hosted setup: the Postgres database and Redis cache, the Trigger.dev webapp on Railway, a worker (supervisor) on a Docker host, and deploying your tasks to the instance with the CLI.
Architecture Overview
| Service | Purpose | Where it runs |
|---|---|---|
| webapp | Dashboard, REST API, and realtime updates | Railway service (Trigger.dev image) |
| postgres | Task metadata and run history | Railway Postgres plugin |
| redis | Queues and pub/sub | Railway Redis plugin |
| registry | Stores the container images your task deploys produce | Railway service or external registry |
| object storage (MinIO / S3) | Large run payloads and outputs | Railway volume or external S3 / R2 |
| supervisor + runners | Pulls task images and runs each execution in its own Docker container | A Docker-capable machine (not a managed Railway service) |
The split matters for hosting. The control-plane services are long-running and fit Railway's managed model well. The worker plane is different: the supervisor talks to a Docker daemon to spin up a fresh container for every run, so it has to run somewhere it can reach a Docker socket. That's why this setup keeps the webapp, Postgres, and Redis on Railway but runs the supervisor on a separate Docker machine.
Step 1: Create a Railway Project
# Install Railway CLI npm install -g @railway/cli # Login and create a project railway login railway init --name trigger-self-hosted
Step 2: Add Postgres and Redis
In the Railway dashboard, click Add Service → Database → PostgreSQL, then repeat for Redis. Railway automatically sets `DATABASE_URL` and `REDIS_URL` environment variables that other services in the project can reference.
Step 3: Deploy the Webapp (Control Plane)
Create a Dockerfile and railway.toml so Railway can run the Trigger.dev webapp image:
# Dockerfile FROM ghcr.io/triggerdotdev/trigger.dev:latest # The webapp reads all configuration from environment variables. # It listens on port 8030 and runs database migrations on boot. EXPOSE 8030
# railway.toml [build] dockerfilePath = "Dockerfile" [deploy] restartPolicyType = "on_failure" restartPolicyMaxRetries = 3 # The webapp listens on 8030. Set PORT=8030 in the service # variables so Railway routes external HTTPS to the container.
Step 4: Set Environment Variables
# Required variables for the Trigger.dev webapp # Set these in Railway dashboard -> Service -> Variables # Core secrets (generate each with the command below) SESSION_SECRET=your-32-byte-hex-secret MAGIC_LINK_SECRET=your-32-byte-hex-secret ENCRYPTION_KEY=your-32-byte-hex-secret MANAGED_WORKER_SECRET=your-32-byte-hex-secret # Database and cache (from the Railway plugins) DATABASE_URL=${{Postgres.DATABASE_URL}} DIRECT_URL=${{Postgres.DATABASE_URL}} REDIS_HOST=${{Redis.RAILWAY_PRIVATE_DOMAIN}} REDIS_PORT=6379 # Public URLs (use the domain Railway assigns the webapp) APP_ORIGIN=https://trigger-webapp-production.up.railway.app LOGIN_ORIGIN=https://trigger-webapp-production.up.railway.app API_ORIGIN=https://trigger-webapp-production.up.railway.app # Email for magic-link auth (SMTP, or a provider like Resend) RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxx FROM_EMAIL=noreply@yourapp.com
# Generate each 32-byte secret (run once per secret) node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" # Or, if you clone the repo, use the bundled helper: # ./hosting/docker/generate-secrets.sh
Step 5: First Boot and Login
The v4 webapp applies its database migrations automatically the first time it boots — there is no separate migrate command to run. Watch the service logs for the magic-link message you'll use to create the first account:
# Follow the webapp logs on Railway railway logs --service trigger-webapp # On a plain Docker host you would use: # docker compose logs -f webapp
Railway's service-to-service networking uses internal hostnames. If the webapp can't reach Postgres on boot, check that DATABASE_URL uses the internal Railway URL (not the public one) for security and lower latency.Step 6: Run a Worker and Deploy Your Tasks
# --- On a Docker-capable machine: run the worker (supervisor) --- git clone --depth=1 https://github.com/triggerdotdev/trigger.dev cd trigger.dev/hosting/docker cp .env.example .env # The webapp prints a TRIGGER_WORKER_TOKEN in its logs on first boot. # In .env, set: # TRIGGER_API_URL=https://trigger-webapp-production.up.railway.app # TRIGGER_WORKER_TOKEN=tr_wgt_xxxxxxxxxxxxxxxxxxxx cd worker docker compose up -d # --- From your app repo: deploy your tasks to the instance --- export TRIGGER_API_URL=https://trigger-webapp-production.up.railway.app export TRIGGER_ACCESS_TOKEN=tr_pat_xxxxxxxxxxxxxxxxxxxx # personal access token npx trigger.dev@latest deploy --self-hosted --push
# .env.local (your Next.js app) TRIGGER_SECRET_KEY=tr_dev_xxxxxxxxxxxxxxxxxxxx # from your self-hosted dashboard TRIGGER_API_URL=https://trigger-webapp-production.up.railway.app
Production Checklist
| Item | Status | Notes |
|---|---|---|
| Postgres backups | Enable in Railway | Railway offers point-in-time recovery on paid plans |
| Redis persistence | AOF enabled by default | Also back up object storage (MinIO) and the registry if you self-host them |
| HTTPS | Automatic | Railway provisions TLS for all public domains |
| Webapp scaling | Single instance | Run one webapp; scale throughput on the worker plane instead |
| Worker scaling | Add worker machines | Each supervisor runs task containers on its Docker host; add machines or raise concurrency to run more tasks in parallel |
| Monitoring | Add Railway metrics | Alert on Postgres connection count and Redis memory usage |
| Job history retention | Configure in Trigger dashboard | Default is 30 days; adjust for compliance needs |
Viewing the Dashboard
Open your webapp's Railway URL (for example https://trigger-webapp-production.up.railway.app, which serves the dashboard on port 8030). Sign in with a magic link sent to the email you configured, then create an organization and project to get the TRIGGER_SECRET_KEY your app uses to trigger tasks.
The worker plane (supervisor + runners) needs a real Docker daemon to launch task containers, which Railway's managed service runtime does not expose. Run the supervisor on a Docker-capable machine — a small VM or your own server — and keep only the webapp, Postgres, and Redis on Railway.Cost Estimate on Railway
| Service | Railway Cost |
|---|---|
| Webapp (recommended 3+ vCPU, 6+ GB RAM) | ~$60-120/mo at sustained use |
| Postgres (managed plugin) | ~$5-15/mo |
| Redis | ~$3-10/mo |
| Registry + object storage (MinIO) | ~$5-15/mo |
| Worker host (4+ vCPU, 8+ GB, Docker) | ~$40-80/mo (separate Docker VM) |
| Total | ~$120-250/mo, scaling with concurrency (vs ~$13/mo for the old v2 single-container setup) |