Skip to content

Commit e7795a0

Browse files
Fix: fixes and prerequisites for v4 self-hosting (#2150)
* remove pgadmin * remove V3_ENABLED * v3 is always enabled * enfore docker machine presets by default * rename autoremove env var * prefix more k8s-specific env vars * same prefix for all docker settings * improve profile switcher copy * supervisor can load token from file * optional webapp worker group bootstrap * fix error message * fix app origin fallback for otlp endpoint * use pnpm cache for webapp docker builds * increase default org and env concurrency limit to 100 * optional machine preset overrides * improve s3 pre-signing errors * fix DOCKER_ENFORCE_MACHINE_PRESETS bool coercion * shard unit tests * fix for s3-compatible services * optional object store region * Update apps/supervisor/src/workerToken.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix DEPLOY_REGISTRY_HOST example * fix platform mock * remove remaining v3Enabled refs * fix error type.. bad bot --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 2b3ea69 commit e7795a0

36 files changed

+392
-172
lines changed

.changeset/smooth-planets-flow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
Update profile switcher

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ COORDINATOR_SECRET=coordinator-secret # generate the actual secret with `openssl
8282

8383
# DEPOT_ORG_ID=<Depot org id>
8484
# DEPOT_TOKEN=<Depot org token>
85-
DEPLOY_REGISTRY_HOST=${APP_ORIGIN} # This is the host that the deploy CLI will use to push images to the registry
85+
DEPLOY_REGISTRY_HOST=localhost:5000 # This is the host that the deploy CLI will use to push images to the registry
8686
# DEV_OTEL_EXPORTER_OTLP_ENDPOINT="http://0.0.0.0:4318"
8787
# These are needed for the object store (for handling large payloads/outputs)
8888
# OBJECT_STORE_BASE_URL="https://{bucket}.{accountId}.r2.cloudflarestorage.com"

CONTRIBUTING.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ branch are tagged into a release periodically.
6262
pnpm run docker
6363
```
6464

65-
This will also start and run a local instance of [pgAdmin](https://www.pgadmin.org/) on [localhost:5480](http://localhost:5480), preconfigured with email `[email protected]` and pwd `admin`. Then use `postgres` as the password to the Trigger.dev server.
66-
6765
9. Migrate the database
6866
```
6967
pnpm run db:migrate
@@ -94,13 +92,11 @@ We use the `<root>/references/v3-catalog` subdirectory as a staging ground for t
9492
9593
First, make sure you are running the webapp according to the instructions above. Then:
9694
97-
1. In Postgres go to the "Organizations" table and on your org set the `v3Enabled` column to `true`.
98-
99-
2. Visit http://localhost:3030 in your browser and create a new V3 project called "v3-catalog". If you don't see an option for V3, you haven't set the `v3Enabled` flag to true.
95+
1. Visit http://localhost:3030 in your browser and create a new V3 project called "v3-catalog".
10096
101-
3. In Postgres go to the "Projects" table and for the project you create change the `externalRef` to `yubjwjsfkxnylobaqvqz`.
97+
2. In Postgres go to the "Projects" table and for the project you create change the `externalRef` to `yubjwjsfkxnylobaqvqz`.
10298
103-
4. Build the CLI
99+
3. Build the CLI
104100
105101
```sh
106102
# Build the CLI
@@ -109,7 +105,7 @@ pnpm run build --filter trigger.dev
109105
pnpm i
110106
```
111107

112-
5. Change into the `<root>/references/v3-catalog` directory and authorize the CLI to the local server:
108+
4. Change into the `<root>/references/v3-catalog` directory and authorize the CLI to the local server:
113109

114110
```sh
115111
cd references/v3-catalog

apps/supervisor/.env.example

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,4 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:3030/otel
1414

1515
# Optional settings
1616
DEBUG=1
17-
ENFORCE_MACHINE_PRESETS=1
1817
TRIGGER_DEQUEUE_INTERVAL_MS=1000

apps/supervisor/src/env.ts

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ const Env = z.object({
1010

1111
// Required settings
1212
TRIGGER_API_URL: z.string().url(),
13-
TRIGGER_WORKER_TOKEN: z.string(),
13+
TRIGGER_WORKER_TOKEN: z.string(), // accepts file:// path to read from a file
1414
MANAGED_WORKER_SECRET: z.string(),
15+
OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url(), // set on the runners
1516

1617
// Workload API settings (coordinator mode) - the workload API is what the run controller connects to
1718
TRIGGER_WORKLOAD_API_ENABLED: BoolEnv.default("true"),
@@ -29,26 +30,6 @@ const Env = z.object({
2930
RUNNER_SNAPSHOT_POLL_INTERVAL_SECONDS: z.coerce.number().optional(),
3031
RUNNER_ADDITIONAL_ENV_VARS: AdditionalEnvVars, // optional (csv)
3132
RUNNER_PRETTY_LOGS: BoolEnv.default(false),
32-
RUNNER_DOCKER_AUTOREMOVE: BoolEnv.default(true),
33-
/**
34-
* Network mode to use for all runners. Supported standard values are: `bridge`, `host`, `none`, and `container:<name|id>`.
35-
* Any other value is taken as a custom network's name to which all runners should connect to.
36-
*
37-
* Accepts a list of comma-separated values to attach to multiple networks. Additional networks are interpreted as network names and will be attached after container creation.
38-
*
39-
* **WARNING**: Specifying multiple networks will slightly increase startup times.
40-
*
41-
* @default "host"
42-
*/
43-
RUNNER_DOCKER_NETWORKS: z.string().default("host"),
44-
45-
// Docker settings
46-
DOCKER_API_VERSION: z.string().default("v1.41"),
47-
DOCKER_PLATFORM: z.string().optional(), // e.g. linux/amd64, linux/arm64
48-
DOCKER_STRIP_IMAGE_DIGEST: BoolEnv.default(true),
49-
DOCKER_REGISTRY_USERNAME: z.string().optional(),
50-
DOCKER_REGISTRY_PASSWORD: z.string().optional(),
51-
DOCKER_REGISTRY_URL: z.string().optional(), // e.g. https://index.docker.io/v1
5233

5334
// Dequeue settings (provider mode)
5435
TRIGGER_DEQUEUE_ENABLED: BoolEnv.default("true"),
@@ -62,22 +43,39 @@ const Env = z.object({
6243
TRIGGER_CHECKPOINT_URL: z.string().optional(),
6344
TRIGGER_METADATA_URL: z.string().optional(),
6445

65-
// Used by the workload manager, e.g docker/k8s
66-
OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url(),
67-
ENFORCE_MACHINE_PRESETS: z.coerce.boolean().default(false),
68-
KUBERNETES_IMAGE_PULL_SECRETS: z.string().optional(), // csv
69-
7046
// Used by the resource monitor
7147
RESOURCE_MONITOR_ENABLED: BoolEnv.default(false),
7248
RESOURCE_MONITOR_OVERRIDE_CPU_TOTAL: z.coerce.number().optional(),
7349
RESOURCE_MONITOR_OVERRIDE_MEMORY_TOTAL_GB: z.coerce.number().optional(),
7450

75-
// Kubernetes specific settings
51+
// Docker settings
52+
DOCKER_API_VERSION: z.string().default("v1.41"),
53+
DOCKER_PLATFORM: z.string().optional(), // e.g. linux/amd64, linux/arm64
54+
DOCKER_STRIP_IMAGE_DIGEST: BoolEnv.default(true),
55+
DOCKER_REGISTRY_USERNAME: z.string().optional(),
56+
DOCKER_REGISTRY_PASSWORD: z.string().optional(),
57+
DOCKER_REGISTRY_URL: z.string().optional(), // e.g. https://index.docker.io/v1
58+
DOCKER_ENFORCE_MACHINE_PRESETS: BoolEnv.default(true),
59+
DOCKER_AUTOREMOVE_EXITED_CONTAINERS: BoolEnv.default(true),
60+
/**
61+
* Network mode to use for all runners. Supported standard values are: `bridge`, `host`, `none`, and `container:<name|id>`.
62+
* Any other value is taken as a custom network's name to which all runners should connect to.
63+
*
64+
* Accepts a list of comma-separated values to attach to multiple networks. Additional networks are interpreted as network names and will be attached after container creation.
65+
*
66+
* **WARNING**: Specifying multiple networks will slightly increase startup times.
67+
*
68+
* @default "host"
69+
*/
70+
DOCKER_RUNNER_NETWORKS: z.string().default("host"),
71+
72+
// Kubernetes settings
7673
KUBERNETES_FORCE_ENABLED: BoolEnv.default(false),
7774
KUBERNETES_NAMESPACE: z.string().default("default"),
7875
KUBERNETES_WORKER_NODETYPE_LABEL: z.string().default("v4-worker"),
79-
EPHEMERAL_STORAGE_SIZE_LIMIT: z.string().default("10Gi"),
80-
EPHEMERAL_STORAGE_SIZE_REQUEST: z.string().default("2Gi"),
76+
KUBERNETES_IMAGE_PULL_SECRETS: z.string().optional(), // csv
77+
KUBERNETES_EPHEMERAL_STORAGE_SIZE_LIMIT: z.string().default("10Gi"),
78+
KUBERNETES_EPHEMERAL_STORAGE_SIZE_REQUEST: z.string().default("2Gi"),
8179

8280
// Metrics
8381
METRICS_ENABLED: BoolEnv.default(true),

apps/supervisor/src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { collectDefaultMetrics } from "prom-client";
2424
import { register } from "./metrics.js";
2525
import { PodCleaner } from "./services/podCleaner.js";
2626
import { FailedPodHandler } from "./services/failedPodHandler.js";
27+
import { getWorkerToken } from "./workerToken.js";
2728

2829
if (env.METRICS_COLLECT_DEFAULTS) {
2930
collectDefaultMetrics({ register });
@@ -67,7 +68,7 @@ class ManagedSupervisor {
6768
heartbeatIntervalSeconds: env.RUNNER_HEARTBEAT_INTERVAL_SECONDS,
6869
snapshotPollIntervalSeconds: env.RUNNER_SNAPSHOT_POLL_INTERVAL_SECONDS,
6970
additionalEnvVars: env.RUNNER_ADDITIONAL_ENV_VARS,
70-
dockerAutoremove: env.RUNNER_DOCKER_AUTOREMOVE,
71+
dockerAutoremove: env.DOCKER_AUTOREMOVE_EXITED_CONTAINERS,
7172
} satisfies WorkloadManagerOptions;
7273

7374
this.resourceMonitor = env.RESOURCE_MONITOR_ENABLED
@@ -119,7 +120,7 @@ class ManagedSupervisor {
119120
}
120121

121122
this.workerSession = new SupervisorSession({
122-
workerToken: env.TRIGGER_WORKER_TOKEN,
123+
workerToken: getWorkerToken(),
123124
apiUrl: env.TRIGGER_API_URL,
124125
instanceName: env.TRIGGER_WORKER_INSTANCE_NAME,
125126
managedWorkerSecret: env.MANAGED_WORKER_SECRET,

apps/supervisor/src/workerToken.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { readFileSync } from "fs";
2+
import { env } from "./env.js";
3+
4+
export function getWorkerToken() {
5+
if (!env.TRIGGER_WORKER_TOKEN.startsWith("file://")) {
6+
return env.TRIGGER_WORKER_TOKEN;
7+
}
8+
9+
const tokenPath = env.TRIGGER_WORKER_TOKEN.replace("file://", "");
10+
11+
console.debug(
12+
JSON.stringify({
13+
message: "🔑 Reading worker token from file",
14+
tokenPath,
15+
})
16+
);
17+
18+
try {
19+
const token = readFileSync(tokenPath, "utf8").trim();
20+
return token;
21+
} catch (error) {
22+
console.error(`Failed to read worker token from file: ${tokenPath}`, error);
23+
throw new Error(
24+
`Unable to read worker token from file: ${
25+
error instanceof Error ? error.message : "Unknown error"
26+
}`
27+
);
28+
}
29+
}

apps/supervisor/src/workloadManager/docker.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class DockerWorkloadManager implements WorkloadManager {
2828
});
2929
}
3030

31-
this.runnerNetworks = env.RUNNER_DOCKER_NETWORKS.split(",");
31+
this.runnerNetworks = env.DOCKER_RUNNER_NETWORKS.split(",");
3232

3333
this.platformOverride = env.DOCKER_PLATFORM;
3434
if (this.platformOverride) {
@@ -61,6 +61,7 @@ export class DockerWorkloadManager implements WorkloadManager {
6161

6262
// Build environment variables
6363
const envVars: string[] = [
64+
`OTEL_EXPORTER_OTLP_ENDPOINT=${env.OTEL_EXPORTER_OTLP_ENDPOINT}`,
6465
`TRIGGER_DEQUEUED_AT_MS=${opts.dequeuedAt.getTime()}`,
6566
`TRIGGER_POD_SCHEDULED_AT_MS=${Date.now()}`,
6667
`TRIGGER_ENV_ID=${opts.envId}`,
@@ -70,8 +71,9 @@ export class DockerWorkloadManager implements WorkloadManager {
7071
`TRIGGER_SUPERVISOR_API_PORT=${this.opts.workloadApiPort}`,
7172
`TRIGGER_SUPERVISOR_API_DOMAIN=${this.opts.workloadApiDomain ?? getDockerHostDomain()}`,
7273
`TRIGGER_WORKER_INSTANCE_NAME=${env.TRIGGER_WORKER_INSTANCE_NAME}`,
73-
`OTEL_EXPORTER_OTLP_ENDPOINT=${env.OTEL_EXPORTER_OTLP_ENDPOINT}`,
7474
`TRIGGER_RUNNER_ID=${runnerId}`,
75+
`TRIGGER_MACHINE_CPU=${opts.machine.cpu}`,
76+
`TRIGGER_MACHINE_MEMORY=${opts.machine.memory}`,
7577
`PRETTY_LOGS=${env.RUNNER_PRETTY_LOGS}`,
7678
];
7779

@@ -110,10 +112,7 @@ export class DockerWorkloadManager implements WorkloadManager {
110112
// - If there are multiple networks to attach, this will ensure the runner won't also be connected to the bridge network
111113
hostConfig.NetworkMode = firstNetwork;
112114

113-
if (env.ENFORCE_MACHINE_PRESETS) {
114-
envVars.push(`TRIGGER_MACHINE_CPU=${opts.machine.cpu}`);
115-
envVars.push(`TRIGGER_MACHINE_MEMORY=${opts.machine.memory}`);
116-
115+
if (env.DOCKER_ENFORCE_MACHINE_PRESETS) {
117116
hostConfig.NanoCpus = opts.machine.cpu * 1e9;
118117
hostConfig.Memory = opts.machine.memory * 1024 * 1024 * 1024;
119118
}

apps/supervisor/src/workloadManager/kubernetes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,13 @@ export class KubernetesWorkloadManager implements WorkloadManager {
236236

237237
get #defaultResourceRequests(): ResourceQuantities {
238238
return {
239-
"ephemeral-storage": env.EPHEMERAL_STORAGE_SIZE_REQUEST,
239+
"ephemeral-storage": env.KUBERNETES_EPHEMERAL_STORAGE_SIZE_REQUEST,
240240
};
241241
}
242242

243243
get #defaultResourceLimits(): ResourceQuantities {
244244
return {
245-
"ephemeral-storage": env.EPHEMERAL_STORAGE_SIZE_LIMIT,
245+
"ephemeral-storage": env.KUBERNETES_EPHEMERAL_STORAGE_SIZE_LIMIT,
246246
};
247247
}
248248

apps/webapp/app/bootstrap.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { mkdir, writeFile } from "fs/promises";
2+
import { prisma } from "./db.server";
3+
import { env } from "./env.server";
4+
import { WorkerGroupService } from "./v3/services/worker/workerGroupService.server";
5+
import { dirname } from "path";
6+
import { tryCatch } from "@trigger.dev/core";
7+
8+
export async function bootstrap() {
9+
if (env.TRIGGER_BOOTSTRAP_ENABLED !== "1") {
10+
return;
11+
}
12+
13+
if (env.TRIGGER_BOOTSTRAP_WORKER_GROUP_NAME) {
14+
const [error] = await tryCatch(createWorkerGroup());
15+
if (error) {
16+
console.error("Failed to create worker group", { error });
17+
}
18+
}
19+
}
20+
21+
async function createWorkerGroup() {
22+
const workerGroupName = env.TRIGGER_BOOTSTRAP_WORKER_GROUP_NAME;
23+
const tokenPath = env.TRIGGER_BOOTSTRAP_WORKER_TOKEN_PATH;
24+
25+
const existingWorkerGroup = await prisma.workerInstanceGroup.findFirst({
26+
where: {
27+
name: workerGroupName,
28+
},
29+
});
30+
31+
if (existingWorkerGroup) {
32+
console.warn(`[bootstrap] Worker group ${workerGroupName} already exists`);
33+
return;
34+
}
35+
36+
const service = new WorkerGroupService();
37+
const { token, workerGroup } = await service.createWorkerGroup({
38+
name: workerGroupName,
39+
});
40+
41+
console.log(`
42+
==========================
43+
Trigger.dev Bootstrap - Worker Token
44+
45+
WARNING: This will only be shown once. Save it now!
46+
47+
Worker group:
48+
${workerGroup.name}
49+
50+
Token:
51+
${token.plaintext}
52+
53+
If using docker compose, set:
54+
TRIGGER_WORKER_TOKEN=${token.plaintext}
55+
56+
${
57+
tokenPath
58+
? `Or, if using a file:
59+
TRIGGER_WORKER_TOKEN=file://${tokenPath}`
60+
: ""
61+
}
62+
63+
==========================
64+
`);
65+
66+
if (tokenPath) {
67+
const dir = dirname(tokenPath);
68+
await mkdir(dir, { recursive: true });
69+
await writeFile(tokenPath, token.plaintext, {
70+
mode: 0o600,
71+
});
72+
73+
console.log(`[bootstrap] Worker token saved to ${tokenPath}`);
74+
}
75+
}

apps/webapp/app/entry.server.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
OperatingSystemPlatform,
1616
} from "./components/primitives/OperatingSystemProvider";
1717
import { singleton } from "./utils/singleton";
18+
import { bootstrap } from "./bootstrap";
1819

1920
const ABORT_DELAY = 30000;
2021

@@ -177,6 +178,10 @@ Worker.init().catch((error) => {
177178
logError(error);
178179
});
179180

181+
bootstrap().catch((error) => {
182+
logError(error);
183+
});
184+
180185
function logError(error: unknown, request?: Request) {
181186
console.error(error);
182187

apps/webapp/app/env.server.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,8 @@ const EnvironmentSchema = z.object({
214214
PUBSUB_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"),
215215
PUBSUB_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"),
216216

217-
DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT: z.coerce.number().int().default(10),
218-
DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT: z.coerce.number().int().default(10),
217+
DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT: z.coerce.number().int().default(100),
218+
DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT: z.coerce.number().int().default(100),
219219
DEFAULT_DEV_ENV_EXECUTION_ATTEMPTS: z.coerce.number().int().positive().default(1),
220220

221221
TUNNEL_HOST: z.string().optional(),
@@ -260,7 +260,6 @@ const EnvironmentSchema = z.object({
260260
INGEST_EVENT_RATE_LIMIT_MAX: z.coerce.number().int().optional(),
261261

262262
//v3
263-
V3_ENABLED: z.string().default("false"),
264263
PROVIDER_SECRET: z.string().default("provider-secret"),
265264
COORDINATOR_SECRET: z.string().default("coordinator-secret"),
266265
DEPOT_TOKEN: z.string().optional(),
@@ -278,6 +277,8 @@ const EnvironmentSchema = z.object({
278277
OBJECT_STORE_BASE_URL: z.string().optional(),
279278
OBJECT_STORE_ACCESS_KEY_ID: z.string().optional(),
280279
OBJECT_STORE_SECRET_ACCESS_KEY: z.string().optional(),
280+
OBJECT_STORE_REGION: z.string().optional(),
281+
OBJECT_STORE_SERVICE: z.string().default("s3"),
281282
EVENTS_BATCH_SIZE: z.coerce.number().int().default(100),
282283
EVENTS_BATCH_INTERVAL: z.coerce.number().int().default(1000),
283284
EVENTS_DEFAULT_LOG_RETENTION: z.coerce.number().int().default(7),
@@ -787,6 +788,14 @@ const EnvironmentSchema = z.object({
787788
RUN_REPLICATION_KEEP_ALIVE_ENABLED: z.string().default("1"),
788789
RUN_REPLICATION_KEEP_ALIVE_IDLE_SOCKET_TTL_MS: z.coerce.number().int().optional(),
789790
RUN_REPLICATION_MAX_OPEN_CONNECTIONS: z.coerce.number().int().default(10),
791+
792+
// Bootstrap
793+
TRIGGER_BOOTSTRAP_ENABLED: z.string().default("0"),
794+
TRIGGER_BOOTSTRAP_WORKER_GROUP_NAME: z.string().optional(),
795+
TRIGGER_BOOTSTRAP_WORKER_TOKEN_PATH: z.string().optional(),
796+
797+
// Machine presets
798+
MACHINE_PRESETS_OVERRIDE_PATH: z.string().optional(),
790799
});
791800

792801
export type Environment = z.infer<typeof EnvironmentSchema>;

0 commit comments

Comments
 (0)