Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
a46da52
cli: add offline deploy phases
ConProgramming Jul 23, 2025
d443417
Remove local indexing
ConProgramming Jul 24, 2025
f984ea3
Fix local dev to fit contributing guide
ConProgramming Jul 24, 2025
6213545
For lightweight build and deploy with pnpm exec trigger deploy --prof…
ConProgramming Jul 24, 2025
f046771
Test
ConProgramming Jul 25, 2025
8501e3f
Testing More
ConProgramming Jul 25, 2025
b418b8f
WIP
ConProgramming Jul 25, 2025
dc77039
Possibly Successfully Extracted WebApp hits from build
ConProgramming Jul 25, 2025
eddafaa
Note
ConProgramming Jul 25, 2025
8788701
WIP
ConProgramming Jul 25, 2025
63b1464
No Errors?
ConProgramming Jul 25, 2025
848ffc1
Remove test folder
ConProgramming Jul 25, 2025
bcd677a
WIP
ConProgramming Jul 25, 2025
2feab27
WIP
ConProgramming Jul 25, 2025
713e587
WIP
ConProgramming Jul 25, 2025
a77b2a0
WIP
ConProgramming Jul 25, 2025
43b45c8
WIP
ConProgramming Jul 25, 2025
719b591
Worked???
ConProgramming Jul 25, 2025
7b4f197
Wow yeah
ConProgramming Jul 25, 2025
eee84b0
Push
ConProgramming Aug 4, 2025
82acb03
Push
ConProgramming Aug 4, 2025
05180c5
Push
ConProgramming Aug 4, 2025
2d6c953
Fix
ConProgramming Aug 4, 2025
5eec96b
Custom base image
ConProgramming Aug 5, 2025
481d649
Support custom container files
ConProgramming Aug 5, 2025
84ccf1c
Add skip digest option
ConProgramming Aug 5, 2025
1cfd809
Update tarball
ConProgramming Aug 5, 2025
8067288
Push
ConProgramming Aug 5, 2025
ec20edb
Push
ConProgramming Aug 9, 2025
a7fff89
Uupdate worker pod configuration: Introduce service account and autom…
ConProgramming Aug 14, 2025
5a66cff
WIP
ConProgramming Sep 29, 2025
320b853
WIP
ConProgramming Sep 29, 2025
203338e
WIP
ConProgramming Sep 29, 2025
0f46e1d
WIP
ConProgramming Sep 29, 2025
3b62dd8
Merge branch 'codex/add-two-phase-deploy-for-trigger.dev-cli' of http…
ConProgramming Sep 29, 2025
2c00345
WIP
ConProgramming Sep 29, 2025
03a4d7d
WIP
ConProgramming Oct 17, 2025
544061d
Update to add suffix to version
ConProgramming Oct 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ APP_ORIGIN=http://localhost:3030
ELECTRIC_ORIGIN=http://localhost:3060
NODE_ENV=development

CLICKHOUSE_URL=http://default:password@localhost:8123

# Set this to UTC because Node.js uses the system timezone
TZ="UTC"

Expand Down
3 changes: 2 additions & 1 deletion apps/supervisor/Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm fetch -
FROM deps-fetcher AS dev-deps
ENV NODE_ENV development

RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile --offline --ignore-scripts
# TEMP --no-frozen-lockfile and remove --offline for overrides for CVEs
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --no-frozen-lockfile --ignore-scripts

FROM base AS builder

Expand Down
2 changes: 2 additions & 0 deletions apps/supervisor/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ const Env = z.object({
KUBERNETES_FORCE_ENABLED: BoolEnv.default(false),
KUBERNETES_NAMESPACE: z.string().default("default"),
KUBERNETES_WORKER_NODETYPE_LABEL: z.string().default("v4-worker"),
KUBERNETES_WORKER_SERVICE_ACCOUNT: z.string().optional(), // Service account for worker pods
KUBERNETES_WORKER_AUTOMOUNT_SERVICE_ACCOUNT_TOKEN: BoolEnv.default(false), // Whether to mount SA token
KUBERNETES_IMAGE_PULL_SECRETS: z.string().optional(), // csv
KUBERNETES_EPHEMERAL_STORAGE_SIZE_LIMIT: z.string().default("10Gi"),
KUBERNETES_EPHEMERAL_STORAGE_SIZE_REQUEST: z.string().default("2Gi"),
Expand Down
23 changes: 22 additions & 1 deletion apps/supervisor/src/workloadManager/kubernetes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export class KubernetesWorkloadManager implements WorkloadManager {
"app.kubernetes.io/part-of": "trigger-worker",
"app.kubernetes.io/component": "create",
},
annotations: {
"com.palantir.rubix.service/pod-cert": "{}",
},
},
spec: {
...this.#defaultPodSpec,
Expand All @@ -60,6 +63,14 @@ export class KubernetesWorkloadManager implements WorkloadManager {
},
],
resources: this.#getResourcesForMachine(opts.machine),
securityContext: {
runAsNonRoot: true,
runAsUser: 1000,
allowPrivilegeEscalation: false,
capabilities: {
drop: ["ALL"],
},
},
env: [
{
name: "TRIGGER_DEQUEUED_AT_MS",
Expand Down Expand Up @@ -226,8 +237,18 @@ export class KubernetesWorkloadManager implements WorkloadManager {
get #defaultPodSpec(): Omit<k8s.V1PodSpec, "containers"> {
return {
restartPolicy: "Never",
automountServiceAccountToken: false,
// Explicit control over service account token mounting (defaults to false for security)
automountServiceAccountToken: env.KUBERNETES_WORKER_AUTOMOUNT_SERVICE_ACCOUNT_TOKEN,
imagePullSecrets: this.getImagePullSecrets(),
// Optionally specify a service account for the worker pods
...(env.KUBERNETES_WORKER_SERVICE_ACCOUNT
? { serviceAccountName: env.KUBERNETES_WORKER_SERVICE_ACCOUNT }
: {}),
securityContext: {
runAsNonRoot: true,
runAsUser: 1000,
fsGroup: 1000,
},
...(env.KUBERNETES_WORKER_NODETYPE_LABEL
? {
nodeSelector: {
Expand Down
1 change: 1 addition & 0 deletions apps/webapp/app/env.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ const EnvironmentSchema = z.object({
DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN: z.string().optional(),
DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID: z.string().optional(),
DEPLOY_IMAGE_PLATFORM: z.string().default("linux/amd64"),
DEPLOY_VERSION_SUFFIX: z.string().optional(), // Optional suffix for deployment versions, e.g., "hardened"
DEPLOY_TIMEOUT_MS: z.coerce
.number()
.int()
Expand Down
11 changes: 9 additions & 2 deletions apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ WHERE
wd."projectId" = ${project.id}
AND wd."environmentId" = ${environment.id}
ORDER BY
string_to_array(wd."version", '.')::int[] DESC
string_to_array(split_part(wd."version", '-', 1), '.')::int[] DESC,
split_part(wd."version", '-', 2) DESC
LIMIT ${pageSize} OFFSET ${pageSize * (page - 1)};`;

return {
Expand Down Expand Up @@ -224,7 +225,13 @@ LIMIT ${pageSize} OFFSET ${pageSize * (page - 1)};`;
FROM ${sqlDatabaseSchema}."WorkerDeployment"
WHERE "projectId" = ${project.id}
AND "environmentId" = ${environment.id}
AND string_to_array(version, '.')::int[] > string_to_array(${version}, '.')::int[]
AND (
string_to_array(split_part(version, '-', 1), '.')::int[] > string_to_array(split_part(${version}, '-', 1), '.')::int[]
OR (
string_to_array(split_part(version, '-', 1), '.')::int[] = string_to_array(split_part(${version}, '-', 1), '.')::int[]
AND split_part(version, '-', 2) > split_part(${version}, '-', 2)
)
)
`;

const count = Number(deploymentsSinceVersion[0].count);
Expand Down
6 changes: 5 additions & 1 deletion apps/webapp/app/presenters/v3/TestPresenter.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ export class TestPresenter extends BasePresenter {
>`WITH workers AS (
SELECT
bw.*,
ROW_NUMBER() OVER(ORDER BY string_to_array(bw.version, '.')::int[] DESC) AS rn
ROW_NUMBER() OVER(
ORDER BY
string_to_array(split_part(bw.version, '-', 1), '.')::int[] DESC,
split_part(bw.version, '-', 2) DESC
) AS rn
FROM
${sqlDatabaseSchema}."BackgroundWorker" bw
WHERE "runtimeEnvironmentId" = ${envId}
Expand Down
23 changes: 6 additions & 17 deletions apps/webapp/app/v3/marqs/devQueueConsumer.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { FailedTaskRunService } from "../failedTaskRun.server";
import { CancelDevSessionRunsService } from "../services/cancelDevSessionRuns.server";
import { CompleteAttemptService } from "../services/completeAttempt.server";
import { attributesFromAuthenticatedEnv, tracer } from "../tracer.server";
import { compareDeploymentVersions } from "../utils/deploymentVersions";
import { DevSubscriber, devPubSub } from "./devPubSub.server";

const MessageBody = z.discriminatedUnion("type", [
Expand Down Expand Up @@ -589,7 +590,7 @@ export class DevQueueConsumer {
}

// Get the latest background worker based on the version.
// Versions are in the format of 20240101.1 and 20240101.2, or even 20240101.10, 20240101.11, etc.
// Versions are in the format of YYYYMMDD.N (e.g., 20240101.1) with optional suffix (e.g., 20240101.1-hardened)
#getLatestBackgroundWorker() {
const workers = Array.from(this._backgroundWorkers.values());

Expand All @@ -598,22 +599,10 @@ export class DevQueueConsumer {
}

return workers.reduce((acc, curr) => {
const accParts = acc.version.split(".").map(Number);
const currParts = curr.version.split(".").map(Number);

// Compare the major part
if (accParts[0] < currParts[0]) {
return curr;
} else if (accParts[0] > currParts[0]) {
return acc;
}

// Compare the minor part (assuming all versions have two parts)
if (accParts[1] < currParts[1]) {
return curr;
} else {
return acc;
}
// Use compareDeploymentVersions to properly handle versions with suffixes
const comparison = compareDeploymentVersions(acc.version, curr.version);
// If curr is newer (comparison returns -1), use curr, otherwise use acc
return comparison < 0 ? curr : acc;
});
}
}
3 changes: 2 additions & 1 deletion apps/webapp/app/v3/services/createBackgroundWorker.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { BackgroundWorkerId } from "@trigger.dev/core/v3/isomorphic";
import type { BackgroundWorker, TaskQueue, TaskQueueType } from "@trigger.dev/database";
import cronstrue from "cronstrue";
import { Prisma, PrismaClientOrTransaction } from "~/db.server";
import { env } from "~/env.server";
import { sanitizeQueueName } from "~/models/taskQueue.server";
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { logger } from "~/services/logger.server";
Expand Down Expand Up @@ -64,7 +65,7 @@ export class CreateBackgroundWorkerService extends BaseService {
return latestBackgroundWorker;
}

const nextVersion = calculateNextBuildVersion(project.backgroundWorkers[0]?.version);
const nextVersion = calculateNextBuildVersion(project.backgroundWorkers[0]?.version, env.DEPLOY_VERSION_SUFFIX);

logger.debug(`Creating background worker`, {
nextVersion,
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/v3/services/initializeDeployment.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class InitializeDeploymentService extends BaseService {
take: 1,
});

const nextVersion = calculateNextBuildVersion(latestDeployment?.version);
const nextVersion = calculateNextBuildVersion(latestDeployment?.version, env.DEPLOY_VERSION_SUFFIX);

if (payload.selfHosted && remoteBuildsEnabled()) {
throw new ServiceValidationError(
Expand Down
16 changes: 11 additions & 5 deletions apps/webapp/app/v3/utils/calculateNextBuildVersion.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
// Calculate next build version based on the previous version
// Version formats are YYYYMMDD.1, YYYYMMDD.2, etc.
// With optional suffix: YYYYMMDD.1-suffix
// If there is no previous version, start at Todays date and .1
export function calculateNextBuildVersion(latestVersion?: string | null): string {
export function calculateNextBuildVersion(latestVersion?: string | null, suffix?: string): string {
const today = new Date();
const year = today.getFullYear();
const month = today.getMonth() + 1;
const day = today.getDate();
const todayFormatted = `${year}${month < 10 ? "0" : ""}${month}${day < 10 ? "0" : ""}${day}`;

if (!latestVersion) {
return `${todayFormatted}.1`;
const baseVersion = `${todayFormatted}.1`;
return suffix ? `${baseVersion}-${suffix}` : baseVersion;
}

const [date, buildNumber] = latestVersion.split(".");
// Extract base version and suffix from latest version
const [baseVersion, existingSuffix] = latestVersion.split("-");
const [date, buildNumber] = baseVersion.split(".");

if (date === todayFormatted) {
const nextBuildNumber = parseInt(buildNumber, 10) + 1;
return `${date}.${nextBuildNumber}`;
const newBaseVersion = `${date}.${nextBuildNumber}`;
return suffix ? `${newBaseVersion}-${suffix}` : newBaseVersion;
}

return `${todayFormatted}.1`;
const newBaseVersion = `${todayFormatted}.1`;
return suffix ? `${newBaseVersion}-${suffix}` : newBaseVersion;
}
24 changes: 22 additions & 2 deletions apps/webapp/app/v3/utils/deploymentVersions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
// Compares two versions of a deployment, like 20250208.1 and 20250208.2
// Also handles versions with suffixes like 20250208.1-hardened
// Returns -1 if versionA is older than versionB, 0 if they are the same, and 1 if versionA is newer than versionB
export function compareDeploymentVersions(versionA: string, versionB: string) {
const [dateA, numberA] = versionA.split(".");
const [dateB, numberB] = versionB.split(".");
// Extract base versions and suffixes
const [baseVersionA, suffixA = ""] = versionA.split("-");
const [baseVersionB, suffixB = ""] = versionB.split("-");

const [dateA, numberA] = baseVersionA.split(".");
const [dateB, numberB] = baseVersionB.split(".");

if (dateA < dateB) {
return -1;
Expand All @@ -24,5 +29,20 @@ export function compareDeploymentVersions(versionA: string, versionB: string) {
return 1;
}

// Base versions are equal, compare suffixes alphabetically
// Versions without suffixes should come before versions with suffixes
if (suffixA === "" && suffixB !== "") {
return -1;
}
if (suffixA !== "" && suffixB === "") {
return 1;
}
if (suffixA < suffixB) {
return -1;
}
if (suffixA > suffixB) {
return 1;
}

return 0;
}
4 changes: 4 additions & 0 deletions hosting/k8s/helm/templates/supervisor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ spec:
value: {{ .Values.supervisor.config.kubernetes.forceEnabled | quote }}
- name: KUBERNETES_WORKER_NODETYPE_LABEL
value: {{ .Values.supervisor.config.kubernetes.workerNodetypeLabel | quote }}
- name: KUBERNETES_WORKER_SERVICE_ACCOUNT
value: {{ .Values.supervisor.config.kubernetes.workerServiceAccount | quote }}
- name: KUBERNETES_WORKER_AUTOMOUNT_SERVICE_ACCOUNT_TOKEN
value: {{ .Values.supervisor.config.kubernetes.workerAutomountServiceAccountToken | quote }}
{{- $registryAuthEnabled := false }}
{{- if .Values.registry.deploy }}
{{- $registryAuthEnabled = .Values.registry.auth.enabled }}
Expand Down
61 changes: 61 additions & 0 deletions hosting/k8s/helm/templates/worker-serviceaccount.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{{- if .Values.worker }}
{{- if .Values.worker.serviceAccount }}
{{- if .Values.worker.serviceAccount.create }}
---
# Service Account for worker pods
# Note: By default, the service account token is NOT mounted into pods (automountServiceAccountToken: false)
# This follows the principle of least privilege. Enable token mounting only if pods need K8s API access.
# For cloud IAM integration (AWS IRSA, GCP Workload Identity), you typically don't need the token mounted.
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.worker.serviceAccount.name | default "trigger-worker" }}
namespace: {{ default .Release.Namespace .Values.supervisor.config.kubernetes.namespace }}
labels:
{{- include "trigger-v4.labels" . | nindent 4 }}
app.kubernetes.io/component: worker
{{- with .Values.worker.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- if .Values.worker.rbac.create }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ .Values.worker.serviceAccount.name | default "trigger-worker" }}
namespace: {{ default .Release.Namespace .Values.supervisor.config.kubernetes.namespace }}
labels:
{{- include "trigger-v4.labels" . | nindent 4 }}
app.kubernetes.io/component: worker
rules:
# Add any permissions your worker pods need
# For example, if they need to read ConfigMaps:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
# Or if they need to read secrets:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ .Values.worker.serviceAccount.name | default "trigger-worker" }}
namespace: {{ default .Release.Namespace .Values.supervisor.config.kubernetes.namespace }}
labels:
{{- include "trigger-v4.labels" . | nindent 4 }}
app.kubernetes.io/component: worker
subjects:
- kind: ServiceAccount
name: {{ .Values.worker.serviceAccount.name | default "trigger-worker" }}
namespace: {{ default .Release.Namespace .Values.supervisor.config.kubernetes.namespace }}
roleRef:
kind: Role
name: {{ .Values.worker.serviceAccount.name | default "trigger-worker" }}
apiGroup: rbac.authorization.k8s.io
{{- end }}
{{- end }}
{{- end }}
{{- end }}
14 changes: 14 additions & 0 deletions hosting/k8s/helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ supervisor:
forceEnabled: true
namespace: "" # Default: uses release namespace
workerNodetypeLabel: "" # When set, runs will only be scheduled on nodes with "nodetype=<label>"
workerServiceAccount: "trigger-worker" # Service account name for worker pods (e.g. "trigger-worker")
workerAutomountServiceAccountToken: true # Whether to mount the SA token in worker pods. Keep false unless pods need K8s API access
ephemeralStorageSizeLimit: "" # Default: 10Gi
ephemeralStorageSizeRequest: "" # Default: 2Gi´
podCleaner:
Expand Down Expand Up @@ -354,6 +356,18 @@ supervisor:
tolerations: []
affinity: {}

# Worker pod configuration
worker:
# Service account for worker pods
serviceAccount:
create: true # Set to true to create a service account for worker pods
name: "trigger-worker" # Name of the service account
annotations: {} # Annotations to add to the service account (e.g., for AWS IRSA, GCP Workload Identity)

# RBAC configuration for worker pods
rbac:
create: false # Set to true to create RBAC resources if worker pods need Kubernetes API access

# PostgreSQL configuration
# Subchart: https://github.com/bitnami/charts/tree/main/bitnami/postgresql
postgres:
Expand Down
15 changes: 10 additions & 5 deletions internal-packages/run-engine/src/engine/tests/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,23 +292,28 @@ export async function setupBackgroundWorker(
};
}

function calculateNextBuildVersion(latestVersion?: string | null): string {
function calculateNextBuildVersion(latestVersion?: string | null, suffix?: string): string {
const today = new Date();
const year = today.getFullYear();
const month = today.getMonth() + 1;
const day = today.getDate();
const todayFormatted = `${year}${month < 10 ? "0" : ""}${month}${day < 10 ? "0" : ""}${day}`;

if (!latestVersion) {
return `${todayFormatted}.1`;
const baseVersion = `${todayFormatted}.1`;
return suffix ? `${baseVersion}-${suffix}` : baseVersion;
}

const [date, buildNumber] = latestVersion.split(".");
// Extract base version and suffix from latest version
const [baseVersion, existingSuffix] = latestVersion.split("-");
const [date, buildNumber] = baseVersion.split(".");

if (date === todayFormatted) {
const nextBuildNumber = parseInt(buildNumber, 10) + 1;
return `${date}.${nextBuildNumber}`;
const newBaseVersion = `${date}.${nextBuildNumber}`;
return suffix ? `${newBaseVersion}-${suffix}` : newBaseVersion;
}

return `${todayFormatted}.1`;
const newBaseVersion = `${todayFormatted}.1`;
return suffix ? `${newBaseVersion}-${suffix}` : newBaseVersion;
}
Loading