├── .env.example ├── .gitignore ├── README.md ├── docker-compose.webapp.yml ├── docker-compose.worker.yml ├── docker-compose.yml ├── lib.sh ├── start.sh ├── stop.sh ├── tunnel.sh └── update.sh /.env.example: -------------------------------------------------------------------------------- 1 | PORT=3030 2 | REMIX_APP_PORT=3030 3 | 4 | NODE_ENV=production 5 | RUNTIME_PLATFORM=docker-compose 6 | 7 | V3_ENABLED=true 8 | # TRIGGER_TELEMETRY_DISABLED=1 9 | INTERNAL_OTEL_TRACE_DISABLED=1 10 | INTERNAL_OTEL_TRACE_LOGGING_ENABLED=0 11 | 12 | POSTGRES_USER=postgres 13 | POSTGRES_PASSWORD=postgres 14 | POSTGRES_DB=postgres 15 | DATABASE_HOST=postgres:5432 16 | DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DATABASE_HOST}/${POSTGRES_DB} 17 | 18 | # This sets the URL used for direct connections to the database and should only be needed in limited circumstances 19 | # See: https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#fields:~:text=the%20shadow%20database.-,directUrl,-No 20 | DIRECT_URL=${DATABASE_URL} 21 | 22 | REDIS_HOST=redis 23 | REDIS_PORT=6379 24 | REDIS_TLS_DISABLED=true 25 | 26 | # If this is set, emails that are not specified won't be able to log in 27 | # WHITELISTED_EMAILS="authorized@yahoo\.com|authorized@gmail\.com" 28 | 29 | # Accounts with these emails will become admins when signing up and get access to the admin panel 30 | # ADMIN_EMAILS="admin@example\.com|another-admin@example\.com" 31 | 32 | # If this is set, your users will be able to log in via GitHub 33 | # AUTH_GITHUB_CLIENT_ID= 34 | # AUTH_GITHUB_CLIENT_SECRET= 35 | 36 | # E-mail settings 37 | # 38 | # - Ensure the FROM_EMAIL matches what you setup with Resend.com 39 | # - If these are not set, emails will be printed to the console 40 | # 41 | # FROM_EMAIL= 42 | # REPLY_TO_EMAIL= 43 | # RESEND_API_KEY= 44 | 45 | # Alert E-mail settings 46 | # - Ensure the ALERT_FROM_EMAIL matches what you setup with Resend.com 47 | # - If these are not set, email alerts will not work. 48 | # 49 | # ALERT_FROM_EMAIL= 50 | # ALERT_RESEND_API_KEY= 51 | 52 | # Concurrency limits 53 | # 54 | # - If these are too high, you may run out of resources on your worker. A simple fix is to either re-deploy 55 | # with lower limits, or set appropriate limits in your trigger.config.ts or directly on your tasks. 56 | # 57 | DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT=300 58 | DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT=100 59 | 60 | # Secrets 61 | # 62 | # - You MUST change these in production! 63 | # 64 | # generate these with `openssl rand -hex 16` 65 | MAGIC_LINK_SECRET=secret # used to encrypt magic link tokens 66 | SESSION_SECRET=secret # used to encrypt session cookies 67 | ENCRYPTION_KEY=ae13021afef0819c3a307ad487071c06 # used to encrypt permanent data 68 | # 69 | # generate these with `openssl rand -hex 32` 70 | PROVIDER_SECRET=provider-secret 71 | COORDINATOR_SECRET=coordinator-secret 72 | 73 | # Worker settings 74 | # 75 | HTTP_SERVER_PORT=9020 76 | COORDINATOR_HOST=127.0.0.1 77 | COORDINATOR_PORT=${HTTP_SERVER_PORT} 78 | REGISTRY_HOST=${DEPLOY_REGISTRY_HOST} 79 | REGISTRY_NAMESPACE=${DEPLOY_REGISTRY_NAMESPACE} 80 | # FORCE_CHECKPOINT_SIMULATION=0 # only uncomment if you are willing to try EXPERIMENTAL docker features - expect bugs 81 | 82 | # Docker 83 | # 84 | # RESTART_POLICY= 85 | # WEBAPP_PUBLISH_IP= 86 | # TRIGGER_IMAGE_TAG= 87 | # POSTGRES_IMAGE_TAG= 88 | # REDIS_IMAGE_TAG= 89 | # ELECTRIC_IMAGE_TAG= 90 | 91 | # Registry settings 92 | # 93 | # - Images will be pushed to: host/namespace/project:version 94 | # 95 | # DEPLOY_REGISTRY_HOST=docker.io 96 | # DEPLOY_REGISTRY_NAMESPACE=trigger # you should change this, for example to your Docker Hub username 97 | 98 | # Domain settings 99 | # 100 | # - Should be uncommented unless you're just testing locally 101 | # - Required for the split webapp / worker setup 102 | # 103 | # TRIGGER_PROTOCOL=https 104 | # TRIGGER_DOMAIN=.ngrok-free.app -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .env.* 3 | !.env.example 4 | !.env.*.example -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trigger.dev Self-Hosting Docker 2 | 3 | If you want to run the Trigger.dev platform yourself, instead of using [our cloud product](https://trigger.dev), you can use this repository to get started. 4 | 5 | It's highly recommended you read our [self-hosting guide](https://trigger.dev/docs/open-source-self-hosting), which contains more detailed instructions and will be more up-to-date. 6 | 7 | ## Local development 8 | 9 | If you want to self-host the Trigger.dev platform, when you're developing your web app locally you'll need to run the Trigger.dev platform locally as well. 10 | 11 | ### Initial setup 12 | 13 | 1. Clone this repository and navigate to it: 14 | 15 | ```sh 16 | git clone https://github.com/triggerdotdev/docker.git 17 | cd docker 18 | ``` 19 | 20 | 2. Run the start script and follow the prompts 21 | 22 | ```bash 23 | ./start.sh # hint: you can append -d to run in detached mode 24 | ``` 25 | 26 | 3. Populate any missing .env file values. (See the .env.example file for more instructions) 27 | 28 | 4. The ports in the `docker-compose.yml` file are set so they are less likely to clash with your local webapp – the platform runs on 3040 and the database is on 5433. If you need to change these ports, you'll need to update the `LOGIN_ORIGIN`, `APP_ORIGIN` and `DATABASE_HOST` environment variables. 29 | 30 | ### Stopping the Docker containers 31 | 32 | 1. Run the stop script 33 | 34 | ```bash 35 | ./stop.sh 36 | ``` 37 | 38 | ### Getting started with using Trigger.dev 39 | 40 | You should now be able to access the Trigger.dev dashboard at [http://localhost:3040](http://localhost:3040/). 41 | 42 | To create an account, login using "Magic Link" and the email with the sign-in link will be printing to the console output in the running `triggerdotdev` container. 43 | 44 | Our main docs are at [docs.trigger.dev](https://docs.trigger.dev/). 45 | 46 | Note, you'll need to ensure that you configure the SDK to point at your self-hosted instance via the `TRIGGER_API_URL` environment variable. With the default settings and running everything locally you'd set it to `http://localhost:3040`. 47 | -------------------------------------------------------------------------------- /docker-compose.webapp.yml: -------------------------------------------------------------------------------- 1 | x-env: &webapp-env 2 | LOGIN_ORIGIN: https://${TRIGGER_DOMAIN:?Please set this in your .env file} 3 | APP_ORIGIN: https://${TRIGGER_DOMAIN} 4 | DEV_OTEL_EXPORTER_OTLP_ENDPOINT: https://${TRIGGER_DOMAIN}/otel 5 | ELECTRIC_ORIGIN: http://electric:3000 6 | 7 | volumes: 8 | postgres-data: 9 | redis-data: 10 | 11 | networks: 12 | default: 13 | 14 | services: 15 | webapp: 16 | image: ghcr.io/triggerdotdev/trigger.dev:${TRIGGER_IMAGE_TAG:-v3} 17 | restart: ${RESTART_POLICY:-unless-stopped} 18 | env_file: 19 | - .env 20 | environment: 21 | <<: *webapp-env 22 | ports: 23 | - ${WEBAPP_PUBLISH_IP:-127.0.0.1}:3040:3030 24 | depends_on: 25 | - postgres 26 | - redis 27 | networks: 28 | - default 29 | 30 | postgres: 31 | image: postgres:${POSTGRES_IMAGE_TAG:-16} 32 | restart: ${RESTART_POLICY:-unless-stopped} 33 | volumes: 34 | - postgres-data:/var/lib/postgresql/data/ 35 | env_file: 36 | - .env 37 | networks: 38 | - default 39 | ports: 40 | - ${DOCKER_PUBLISH_IP:-127.0.0.1}:5433:5432 41 | command: 42 | - -c 43 | - wal_level=logical 44 | 45 | redis: 46 | image: redis:${REDIS_IMAGE_TAG:-7} 47 | restart: ${RESTART_POLICY:-unless-stopped} 48 | volumes: 49 | - redis-data:/data 50 | networks: 51 | - default 52 | ports: 53 | - ${DOCKER_PUBLISH_IP:-127.0.0.1}:6389:6379 54 | 55 | electric: 56 | image: electricsql/electric:${ELECTRIC_IMAGE_TAG:-latest} 57 | restart: ${RESTART_POLICY:-unless-stopped} 58 | environment: 59 | DATABASE_URL: $DATABASE_URL 60 | networks: 61 | - default 62 | depends_on: 63 | - postgres 64 | ports: 65 | - ${DOCKER_PUBLISH_IP:-127.0.0.1}:3061:3000 66 | -------------------------------------------------------------------------------- /docker-compose.worker.yml: -------------------------------------------------------------------------------- 1 | x-env: &worker-env 2 | PLATFORM_HOST: ${TRIGGER_DOMAIN:?Please set this in your .env file} 3 | PLATFORM_WS_PORT: 443 4 | SECURE_CONNECTION: "true" 5 | OTEL_EXPORTER_OTLP_ENDPOINT: https://${TRIGGER_DOMAIN}/otel 6 | 7 | networks: 8 | default: 9 | 10 | services: 11 | docker-provider: 12 | image: ghcr.io/triggerdotdev/provider/docker:${TRIGGER_IMAGE_TAG:-v3} 13 | restart: ${RESTART_POLICY:-unless-stopped} 14 | volumes: 15 | - /var/run/docker.sock:/var/run/docker.sock 16 | user: root 17 | networks: 18 | - default 19 | ports: 20 | - ${DOCKER_PUBLISH_IP:-127.0.0.1}:9021:9020 21 | env_file: 22 | - .env 23 | environment: 24 | <<: *worker-env 25 | PLATFORM_SECRET: $PROVIDER_SECRET 26 | 27 | coordinator: 28 | image: ghcr.io/triggerdotdev/coordinator:${TRIGGER_IMAGE_TAG:-v3} 29 | restart: ${RESTART_POLICY:-unless-stopped} 30 | volumes: 31 | - /var/run/docker.sock:/var/run/docker.sock 32 | user: root 33 | networks: 34 | - default 35 | ports: 36 | - ${DOCKER_PUBLISH_IP:-127.0.0.1}:9020:9020 37 | env_file: 38 | - .env 39 | environment: 40 | <<: *worker-env 41 | PLATFORM_SECRET: $COORDINATOR_SECRET 42 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | x-webapp-env: &webapp-env 2 | LOGIN_ORIGIN: &trigger-url ${TRIGGER_PROTOCOL:-http}://${TRIGGER_DOMAIN:-localhost:3040} 3 | APP_ORIGIN: *trigger-url 4 | DEV_OTEL_EXPORTER_OTLP_ENDPOINT: &trigger-otel ${TRIGGER_PROTOCOL:-http}://${TRIGGER_DOMAIN:-localhost:3040}/otel 5 | ELECTRIC_ORIGIN: http://electric:3000 6 | 7 | x-worker-env: &worker-env 8 | PLATFORM_HOST: webapp 9 | PLATFORM_WS_PORT: 3030 10 | SECURE_CONNECTION: "false" 11 | OTEL_EXPORTER_OTLP_ENDPOINT: *trigger-otel 12 | 13 | volumes: 14 | postgres-data: 15 | redis-data: 16 | 17 | networks: 18 | webapp: 19 | 20 | services: 21 | webapp: 22 | image: ghcr.io/triggerdotdev/trigger.dev:${TRIGGER_IMAGE_TAG:-v3} 23 | restart: ${RESTART_POLICY:-unless-stopped} 24 | env_file: 25 | - .env 26 | environment: 27 | <<: *webapp-env 28 | ports: 29 | - ${WEBAPP_PUBLISH_IP:-127.0.0.1}:3040:3030 30 | depends_on: 31 | - postgres 32 | - redis 33 | networks: 34 | - webapp 35 | 36 | postgres: 37 | image: postgres:${POSTGRES_IMAGE_TAG:-16} 38 | restart: ${RESTART_POLICY:-unless-stopped} 39 | volumes: 40 | - postgres-data:/var/lib/postgresql/data/ 41 | env_file: 42 | - .env 43 | networks: 44 | - webapp 45 | ports: 46 | - ${DOCKER_PUBLISH_IP:-127.0.0.1}:5433:5432 47 | command: 48 | - -c 49 | - wal_level=logical 50 | 51 | redis: 52 | image: redis:${REDIS_IMAGE_TAG:-7} 53 | restart: ${RESTART_POLICY:-unless-stopped} 54 | volumes: 55 | - redis-data:/data 56 | networks: 57 | - webapp 58 | ports: 59 | - ${DOCKER_PUBLISH_IP:-127.0.0.1}:6389:6379 60 | 61 | docker-provider: 62 | image: ghcr.io/triggerdotdev/provider/docker:${TRIGGER_IMAGE_TAG:-v3} 63 | restart: ${RESTART_POLICY:-unless-stopped} 64 | volumes: 65 | - /var/run/docker.sock:/var/run/docker.sock 66 | user: root 67 | networks: 68 | - webapp 69 | depends_on: 70 | - webapp 71 | ports: 72 | - ${DOCKER_PUBLISH_IP:-127.0.0.1}:9021:9020 73 | env_file: 74 | - .env 75 | environment: 76 | <<: *worker-env 77 | PLATFORM_SECRET: $PROVIDER_SECRET 78 | 79 | coordinator: 80 | image: ghcr.io/triggerdotdev/coordinator:${TRIGGER_IMAGE_TAG:-v3} 81 | restart: ${RESTART_POLICY:-unless-stopped} 82 | volumes: 83 | - /var/run/docker.sock:/var/run/docker.sock 84 | user: root 85 | networks: 86 | - webapp 87 | depends_on: 88 | - webapp 89 | ports: 90 | - ${DOCKER_PUBLISH_IP:-127.0.0.1}:9020:9020 91 | env_file: 92 | - .env 93 | environment: 94 | <<: *worker-env 95 | PLATFORM_SECRET: $COORDINATOR_SECRET 96 | 97 | electric: 98 | image: electricsql/electric:${ELECTRIC_IMAGE_TAG:-latest} 99 | restart: ${RESTART_POLICY:-unless-stopped} 100 | environment: 101 | DATABASE_URL: ${DATABASE_URL}?sslmode=disable 102 | networks: 103 | - webapp 104 | depends_on: 105 | - postgres 106 | ports: 107 | - ${DOCKER_PUBLISH_IP:-127.0.0.1}:3061:3000 108 | -------------------------------------------------------------------------------- /lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker_compose() { 4 | if docker compose >/dev/null 2>&1; then 5 | set -x 6 | docker compose "$@" 7 | elif command -v docker-compose >/dev/null 2>&1; then 8 | set -x 9 | docker-compose "$@" 10 | else 11 | echo Please install docker compose: https://docs.docker.com/compose/install/ 12 | fi 13 | 14 | set +x 15 | } 16 | 17 | sed_cmd() { 18 | # Determine the sed in-place flag based on the operating system 19 | if [ "$(uname)" = "Darwin" ]; then 20 | sed -i '' "$@" 21 | else 22 | sed -i "$@" 23 | fi 24 | } 25 | 26 | write_secrets() { 27 | env_file="$1" 28 | 29 | sed_cmd \ 30 | -e "s|^MAGIC_LINK_SECRET=.*|MAGIC_LINK_SECRET=$MAGIC_LINK_SECRET|" \ 31 | -e "s|^SESSION_SECRET=.*|SESSION_SECRET=$SESSION_SECRET|" \ 32 | -e "s|^ENCRYPTION_KEY=.*|ENCRYPTION_KEY=$ENCRYPTION_KEY|" \ 33 | -e "s|^PROVIDER_SECRET=.*|PROVIDER_SECRET=$PROVIDER_SECRET|" \ 34 | -e "s|^COORDINATOR_SECRET=.*|COORDINATOR_SECRET=$COORDINATOR_SECRET|" \ 35 | "$env_file" 36 | 37 | if [ $? -ne 0 ]; then 38 | return 1 39 | fi 40 | 41 | echo "Wrote secrets to $(basename "$env_file")" 42 | } 43 | 44 | generate_secrets() { 45 | env_file=$1 46 | 47 | echo "Generated secrets:" 48 | 49 | MAGIC_LINK_SECRET=$(openssl rand -hex 16) 50 | echo MAGIC_LINK_SECRET="$MAGIC_LINK_SECRET" 51 | 52 | SESSION_SECRET=$(openssl rand -hex 16) 53 | echo SESSION_SECRET="$SESSION_SECRET" 54 | 55 | ENCRYPTION_KEY=$(openssl rand -hex 16) 56 | echo ENCRYPTION_KEY="$ENCRYPTION_KEY" 57 | 58 | PROVIDER_SECRET=$(openssl rand -hex 32) 59 | echo PROVIDER_SECRET="$PROVIDER_SECRET" 60 | 61 | COORDINATOR_SECRET=$(openssl rand -hex 32) 62 | echo COORDINATOR_SECRET="$COORDINATOR_SECRET" 63 | 64 | if [ -z "$env_file" ]; then 65 | return 66 | fi 67 | 68 | if [ ! -f "$env_file" ]; then 69 | read -p "No $(basename "$env_file") file found, would you like to create one? [Y/n] " yn 70 | case $yn in 71 | [nN]* ) 72 | echo "Skipping .env file creation." 73 | return 74 | ;; 75 | * ) 76 | env_example_file=$(dirname "$env_file")/.env.example 77 | cp -v "$env_example_file" "$env_file" 78 | 79 | echo "Writing secrets to $(basename "$env_file")" 80 | if ! write_secrets "$env_file"; then 81 | return 1 82 | fi 83 | ;; 84 | esac 85 | fi 86 | 87 | read -p "Would you like to replace your current secrets in $(basename "$env_file")? [Y/n] " yn 88 | 89 | case $yn in 90 | [nN]* ) 91 | echo "Skipped writing secrets. You may want to add them manually to $(basename "$env_file")" 92 | ;; 93 | * ) 94 | if ! cp "$env_file" "$env_file.backup"; then 95 | echo "Failed to backup $(basename "$env_file")" 96 | return 1 97 | fi 98 | echo "Wrote backup to $(basename "$env_file").backup" 99 | 100 | echo "Overwriting secrets in $(basename "$env_file")" 101 | if ! write_secrets "$env_file"; then 102 | return 1 103 | fi 104 | ;; 105 | esac 106 | } 107 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | script_dir=$(dirname "$0") 4 | 5 | . "$script_dir"/lib.sh 6 | 7 | kind=$1 8 | 9 | # shift if we have to 10 | if [ "$kind" = "full" ] || [ "$kind" = "webapp" ] || [ "$kind" = "worker" ]; then 11 | shift 12 | fi 13 | 14 | echo $kind 15 | 16 | # default to full 17 | if [ -z "$kind" ] || ( [ "$kind" != "webapp" ] && [ "$kind" != "worker" ] ); then 18 | kind="full" 19 | fi 20 | 21 | env_file=$script_dir/.env 22 | env_example_file=$script_dir/.env.example 23 | 24 | if [ ! -f "$env_file" ]; then 25 | read -p "No .env file found, would you like to create one? [Y/n] " yn 26 | case $yn in 27 | [nN]* ) 28 | echo "Skipping .env file creation. The next steps will likely fail." 29 | ;; 30 | * ) 31 | cp -v "$env_example_file" "$env_file" 32 | 33 | read -p "Would you also like to generate fresh secrets? [Y/n] " yn 34 | case $yn in 35 | [nN]* ) 36 | echo "Skipping secret generation. You should really not skip this step." 37 | ;; 38 | * ) 39 | if ! generate_secrets "$env_file"; then 40 | echo "Failed to generate secrets. Exiting." 41 | exit 1 42 | fi 43 | sleep 2 44 | ;; 45 | esac 46 | ;; 47 | esac 48 | fi 49 | 50 | if [ "$kind" = "full" ]; then 51 | compose_file=$script_dir/docker-compose.yml 52 | extra_args="-p=trigger" 53 | else 54 | compose_file=$script_dir/docker-compose.$kind.yml 55 | extra_args="-p=trigger-$kind" 56 | fi 57 | 58 | docker_compose -f "$compose_file" "$extra_args" up "$@" 59 | -------------------------------------------------------------------------------- /stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | script_dir=$(dirname "$0") 4 | 5 | . "$script_dir"/lib.sh 6 | 7 | kind=$1 8 | 9 | # shift if we have to 10 | if [ "$kind" = "full" ] || [ "$kind" = "webapp" ] || [ "$kind" = "worker" ]; then 11 | shift 12 | fi 13 | 14 | echo $kind 15 | 16 | # default to full 17 | if [ -z "$kind" ] || ( [ "$kind" != "webapp" ] && [ "$kind" != "worker" ] ); then 18 | kind="full" 19 | fi 20 | 21 | if [ "$kind" = "full" ]; then 22 | compose_file=$script_dir/docker-compose.yml 23 | extra_args="-p=trigger" 24 | else 25 | compose_file=$script_dir/docker-compose.$kind.yml 26 | extra_args="-p=trigger-$kind" 27 | fi 28 | 29 | docker_compose -f "$compose_file" "$extra_args" down "$@" -------------------------------------------------------------------------------- /tunnel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | domain=$1 4 | port=3040 5 | 6 | if ! command -v ngrok >/dev/null 2>&1; then 7 | echo "Please install ngrok: https://ngrok.com/download" 8 | exit 1 9 | fi 10 | 11 | if [ -n "$domain" ]; then 12 | ngrok http --domain="$domain" "$port" 13 | else 14 | ngrok http "$port" 15 | fi 16 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | script_dir=$(dirname "$0") 4 | 5 | . "$script_dir"/lib.sh 6 | 7 | kind=$1 8 | 9 | # shift if we have to 10 | if [ "$kind" = "full" ] || [ "$kind" = "webapp" ] || [ "$kind" = "worker" ]; then 11 | shift 12 | fi 13 | 14 | echo $kind 15 | 16 | # default to full 17 | if [ -z "$kind" ] || ( [ "$kind" != "webapp" ] && [ "$kind" != "worker" ] ); then 18 | kind="full" 19 | fi 20 | 21 | if [ "$kind" = "full" ]; then 22 | compose_file=$script_dir/docker-compose.yml 23 | extra_args="-p=trigger" 24 | else 25 | compose_file=$script_dir/docker-compose.$kind.yml 26 | extra_args="-p=trigger-$kind" 27 | fi 28 | 29 | docker_compose -f "$compose_file" "$extra_args" pull "$@" --------------------------------------------------------------------------------