Deployment guide

Installing Trigger.dev on a VPS: complete self-hosted guide

Deploy on a VPS Cloud →

Tutorial

Installing Trigger.dev on a VPS: complete self-hosted guide

Automation9 min read6 steps

Trigger.dev is a background task orchestration platform built for developers: durable jobs, automatic retries and a real-time dashboard. By self-hosting it on a VPS, you get a completely free installation with no execution quota — no SaaS subscription, no execution quota. This guide covers the full setup from A to Z: prerequisites, secret generation, HTTPS reverse proxy, connecting your first TypeScript project and troubleshooting the most common errors.

Contents· Why self-host Trigger.dev on a VPS1/10
  1. 01Why self-host Trigger.dev on a VPS
  2. 02What you gain by self-hosting
  3. 03Trigger.dev self-hosted vs cloud: what actually changes
  4. 04Hardware and software requirements
  5. 05Install Trigger.dev with Docker in 6 steps
  6. 06Troubleshooting: common errors and their solutions
  7. 07Securing and maintaining your instance
  8. 08Horizontal worker scalability
  9. 09Writing and deploying your first Trigger.dev job
  10. 10Use cases best suited to self-hosting

Why self-host Trigger.dev on a VPS

Trigger.dev replaces homemade queues (BullMQ, fragile crons, Lambdas that time out) with a unified platform: durable tasks, automatic retries, concurrency management and a real-time dashboard. The cloud version bills by the number of executions and limits job duration, which quickly becomes constraining for ETL processing, mass email sending or slow API calls.

Self-hosted on a VPS, you keep your workers on your side: your third-party API keys (OpenAI, Stripe, Resend) never pass through a third party, and your jobs can run for 10 minutes or several hours without being cut off. The stack relies on PostgreSQL, Redis and Docker, making it reproducible and easy to back up. The installation is completely free: no licence, no subscription — only the VPS cost applies.

What you gain by self-hosting

  • No limit on the duration or number of executions of your background tasks.
  • Your secrets (API keys, tokens) stay in your infrastructure, never on a third-party SaaS.
  • Predictable fixed cost: a single VPS instead of a bill that climbs with volume.
  • No public network hop if your workers run next to your database and your application.
  • Full control over versions, environment variables and log retention.
  • The ability to trigger jobs from your internal webhooks without exposing any external service.
  • Update at your own pace: you choose when to upgrade to a new version of Trigger.dev.

Trigger.dev self-hosted vs cloud: what actually changes

Scroll the table

CriterionCloud (SaaS)Self-hosted VPS
Maximum job durationLimited (a few minutes)No duration limit
Cost at volumeGrows with executionsFixed (monthly VPS)
Secrets / API keysPass through the cloudStay in your infra
UpdatesAutomatic, imposedAt your own pace
Internal network accessImpossible without tunnelDirect, no round-trip via the public Internet
Log retentionLimited by planControlled by you
Initial setupZero configuration~30 minutes (this guide)

Hardware and software requirements

Trigger.dev is a relatively resource-hungry stack because it combines the web application, workers, PostgreSQL and Redis. Plan for a VPS with at least 4 GB of RAM and 2 vCPU for light production use; aim for 8 GB of RAM and 4 vCPU if you run many concurrent jobs or heavy processing. Allow 40 GB of SSD disk for the database, logs and Docker images.

On the software side: Docker Engine 24+ and the Docker Compose v2 plugin (check with docker compose version), a domain name pointing to the VPS IP (for example trigger.mydomain.com), and port 443 open for HTTPS. Two secrets are essential: MAGIC_LINK_SECRET (passwordless authentication) and ENCRYPTION_KEY (32 hexadecimal characters, encrypts project environment variables). Generate them before you begin — they cannot be regenerated without breaking existing data.

Install Trigger.dev with Docker in 6 steps

  1. Prepare the VPS and install Docker

    Connect over SSH with a sudo user, update the system (apt update && apt upgrade -y) then install Docker in one command: curl -fsSL https://get.docker.com | sh. Add your user to the docker group (usermod -aG docker $USER) to avoid always using sudo. Create a dedicated folder: mkdir -p /opt/trigger && cd /opt/trigger. Verify that Compose v2 is available with docker compose version — the output should show at least v2.x.x.

  2. Fetch the official self-hosting stack

    Clone the official repository: git clone https://github.com/triggerdotdev/trigger.dev /opt/trigger/src. Navigate to the deployment subfolder: cd /opt/trigger/src/docker. This directory contains the docker-compose.yml that orchestrates the web application (webapp), workers, PostgreSQL and Redis. Copy the example file to your configuration: cp .env.example .env. Do not launch anything before configuring the .env — the stack will refuse to start with default values.

  3. Generate the secrets and configure the domain

    Open .env in your editor and set at minimum these variables:

    - ENCRYPTION_KEY: openssl rand -hex 16 (16 bytes = 32 hex chars)
    - MAGIC_LINK_SECRET: openssl rand -hex 16 (same)
    - LOGIN_ORIGIN: https://trigger.mydomain.com
    - APP_ORIGIN: https://trigger.mydomain.com
    - POSTGRES_PASSWORD: a strong password (openssl rand -base64 24)
    - REDIS_PASSWORD: same

    Leave DATABASE_URL and REDIS_URL as-is if you use the internal Compose services — they reference Docker service names. Also set SESSION_SECRET with openssl rand -hex 32.

  4. Launch the stack and create the first account

    Start everything in the background: docker compose up -d. Follow the database migrations in real time: docker compose logs -f webapp. Wait for the message Listening on port 3030 before continuing — migrations can take 30 to 60 seconds on first startup. Once the application has started, the signup magic link appears in the logs: copy it and open it in your browser to create the first admin account. If you missed the link: docker compose logs webapp | grep magic.

  5. Expose the application via an HTTPS reverse proxy

    Place Caddy in front of the application to handle TLS automatically. Create /etc/caddy/Caddyfile with this minimal content:

    trigger.mydomain.com {
        reverse_proxy localhost:3030
    }

    Reload Caddy (systemctl reload caddy): Let's Encrypt issues the certificate in seconds. With nginx, create a vhost that proxies to http://127.0.0.1:3030 and activate a certificate via certbot --nginx. Then verify that https://trigger.mydomain.com responds correctly before moving on.

  6. Connect your first TypeScript project

    In your Node.js project, install the SDK: npm install @trigger.dev/sdk. Authenticate the CLI against your instance: npx trigger.dev@latest login --api-url https://trigger.mydomain.com. Then create your first project in the dashboard, retrieve the project secret key (sk_...) and add it to your local .env: TRIGGER_SECRET_KEY=sk_.... Initialise the configuration with npx trigger.dev@latest init and deploy your first job with npx trigger.dev@latest deploy.

Troubleshooting: common errors and their solutions

Here are the five most common errors encountered when installing Trigger.dev self-hosted.

Error: ENCRYPTION_KEY must be 32 characters — You used openssl rand -base64 16 instead of openssl rand -hex 16. The base64 form produces characters outside the hexadecimal character set. Regenerate with -hex 16 (exactly 32 chars).

webapp exited with code 1 on startup — Check the full logs with docker compose logs webapp. Most common cause: incorrect DATABASE_URL or PostgreSQL not yet ready. Wait 10 seconds and restart with docker compose restart webapp.

The account creation magic link does not appear — The application may have started before migrations finished. Restart docker compose restart webapp and watch the logs from the beginning. If the link is still absent, check that LOGIN_ORIGIN exactly matches your domain (no trailing slash).

Failed to connect in the CLI during login — The reverse proxy is not yet active or DNS has not yet propagated to your VPS. Test locally with curl http://127.0.0.1:3030/healthcheck from the VPS: if it responds, the issue is on the proxy or DNS side.

The job deploys but does not run — Check that the workers are running: docker compose ps should show the worker service in running state. If the worker is stopped, docker compose up -d worker restarts it. Also check that TRIGGER_SECRET_KEY in your project matches the key of the correct project in the dashboard.

Securing and maintaining your instance

A Trigger.dev instance exposes the dashboard and the API on the same domain. A few precautions reduce the attack surface without complicating operations.

Firewall: block all ports except 22 (SSH), 80 and 443 (with ufw allow for each). Port 3030 must never be directly accessible from outside — it is reserved for the local reverse proxy.

Secret rotation: MAGIC_LINK_SECRET can be rotated without breaking data. ENCRYPTION_KEY, however, encrypts project environment variables — rotation requires a data migration. Store them in a secrets manager (Bitwarden, 1Password or HashiCorp Vault).

Updates: Trigger.dev publishes releases on GitHub. To update, pull the new version (git pull in /opt/trigger/src), rebuild the images (docker compose pull) and restart (docker compose up -d). Database migrations are applied automatically at webapp startup.

Backups: schedule a daily PostgreSQL dump to external storage. A minimal cron: 0 3 * * * docker exec trigger-postgres-1 pg_dump -U postgres trigger | gzip > /opt/backups/trigger-$(date +%F).sql.gz.

Horizontal worker scalability

Isolate the workers from the web application on separate containers and limit their concurrency via the WORKER_CONCURRENCY variable (default: 10). For very CPU-hungry jobs, add a second VPS dedicated to workers pointing to the same PostgreSQL and Redis: you scale horizontally without touching the dashboard. Workers are stateless — they only need DATABASE_URL, REDIS_URL and ENCRYPTION_KEY to join the fleet. On a small VPS, start with WORKER_CONCURRENCY=3 to avoid memory saturation during job spikes.

Writing and deploying your first Trigger.dev job

A Trigger.dev job is a TypeScript function exported from a trigger/ file in your project. Here is a minimal example of a deferred email send:

import { task } from "@trigger.dev/sdk/v3";

export const sendWelcomeEmail = task({
  id: "send-welcome-email",
  run: async (payload: { userId: string; email: string }) => {
    // your sending logic here
    await sendEmail(payload.email, "Welcome!");
    return { sent: true };
  },
});

Deploy with npx trigger.dev@latest deploy. The dashboard then shows your job under the Tasks tab. Trigger a test run from the dashboard or from your code: await sendWelcomeEmail.trigger({ userId: "u1", email: "[email protected]" }). Automatic retries apply on error — configurable with the retry option on task().

For long jobs (CSV import, image processing), use wait.for() to suspend execution and avoid blocking a worker during network pauses: Trigger.dev resumes the job where it left off, even after a container restart.

Use cases best suited to self-hosting

  • ETL processing: import, transform and load large data volumes without timeouts.
  • Mass transactional sends: email or notification campaigns with controlled throttling.
  • AI pipelines: chained calls to OpenAI, Anthropic or Hugging Face with retry handling on rate-limits.
  • Third-party integration sync: Stripe webhooks, Shopify, HubSpot — without depending on an intermediary SaaS.
  • Critical scheduled tasks: replace fragile crons with jobs that have an execution history and alerts.
  • PDF report generation or heavy exports: multi-minute jobs with no duration limit.

Trigger.dev is completely free when self-hosted: no commercial licence is required for private or professional use on your own infrastructure. The MIT licence covers the open-source code. Only the proprietary code of the Enterprise version (SAML SSO, advanced audit logs) is excluded — for the vast majority of teams, the self-hosted community version is more than sufficient.

Deploy Trigger.dev in a few minutes

A ServOrbit Cloud VPS preconfigured with Docker, a domain and a ready SSL certificate lets you launch your self-hosted task orchestrator without tedious system configuration.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab