Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3f6c446
remove registry proxy
nicktrn May 29, 2025
0e6bd60
remove --self-hosted flag
nicktrn May 29, 2025
9090350
automatically set network build flag
nicktrn May 29, 2025
9dda081
update syncEnvVars debug log
nicktrn May 29, 2025
3901a04
improve switch command
nicktrn May 29, 2025
f4261a2
always display deploy errors if they exist
nicktrn May 30, 2025
6712571
fix stuck deploy command after finalize error
nicktrn May 30, 2025
f18eacb
webapp-driven deploys, multi-platform support, lots of fixes
nicktrn May 30, 2025
898fe14
Merge remote-tracking branch 'origin/main' into feat/self-hosted-dete…
nicktrn May 30, 2025
be73c59
add worker deployment migration
nicktrn May 30, 2025
2e46524
rename image platform env var
nicktrn May 30, 2025
61357d3
only try to sync parent env vars for preview deployments
nicktrn May 30, 2025
c51c7f1
add KEEP_TMP_DIRS
nicktrn May 30, 2025
07a3988
supervisor: docker api version lock, auth, multi-platform
nicktrn May 31, 2025
ffa725f
set image ref on create, validate digest
nicktrn May 31, 2025
8c85535
use metadata for digest, fix local multi-platform builds
nicktrn May 31, 2025
4db1de2
print git meta branch before commit
nicktrn May 31, 2025
47a1eca
improve push and load flag handling
nicktrn May 31, 2025
c4718de
make runs after local builds compatible with load and push
nicktrn Jun 2, 2025
726b9c8
small improvement for platform overrides
nicktrn Jun 2, 2025
7eaf50b
add image platform to dequeued message
nicktrn Jun 2, 2025
475d16a
Merge remote-tracking branch 'origin/main' into feat/detect-self-hosted
nicktrn Jun 2, 2025
d00ed33
remove deprecated init request body fields
nicktrn Jun 2, 2025
f44c86a
Merge remote-tracking branch 'origin/main' into feat/detect-self-hosted
nicktrn Jun 2, 2025
2241270
fix fail deployment id param
nicktrn Jun 2, 2025
c9b626b
Merge remote-tracking branch 'origin/main' into feat/detect-self-hosted
nicktrn Jun 2, 2025
078c675
remove build debug logs
nicktrn Jun 2, 2025
c6fc24b
pass report merge with no tests
nicktrn Jun 2, 2025
96d0263
Merge remote-tracking branch 'origin/main' into feat/detect-self-hosted
nicktrn Jun 2, 2025
2e5925c
structured run debug logs
nicktrn Jun 3, 2025
5b7ea14
add required env var for tests
nicktrn Jun 3, 2025
9c90301
should not be an error log
nicktrn Jun 3, 2025
dffbf8b
Merge remote-tracking branch 'origin/main' into feat/detect-self-hosted
nicktrn Jun 4, 2025
71de319
add changeset
nicktrn Jun 4, 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
11 changes: 11 additions & 0 deletions .changeset/ninety-games-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---

- Resolve issue where CLI could get stuck during deploy finalization
- Unify local and remote build logic, with multi-platform build support
- Improve switch command; now accepts profile name as an argument
- Registry configuration is now fully managed by the webapp
- The deploy `--self-hosted` flag is no longer required
- Enhance deployment error reporting and image digest retrieval
9 changes: 2 additions & 7 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,9 @@ CLOUD_SLACK_CLIENT_SECRET=
PROVIDER_SECRET=provider-secret # generate the actual secret with `openssl rand -hex 32`
COORDINATOR_SECRET=coordinator-secret # generate the actual secret with `openssl rand -hex 32`

# Uncomment the following line to enable the registry proxy
# ENABLE_REGISTRY_PROXY=true
# DEPOT_ORG_ID=<Depot org id>
# DEPOT_TOKEN=<Depot org token>
# DEPOT_PROJECT_ID=<Depot project id>
# DEPLOY_REGISTRY_HOST=${APP_ORIGIN} # This is the host that the deploy CLI will use to push images to the registry
# CONTAINER_REGISTRY_ORIGIN=<Container registry origin e.g. https://registry.digitalocean.com>
# CONTAINER_REGISTRY_USERNAME=<Container registry username e.g. Digital ocean email address>
# CONTAINER_REGISTRY_PASSWORD=<Container registry password e.g. Digital ocean PAT>
DEPLOY_REGISTRY_HOST=${APP_ORIGIN} # This is the host that the deploy CLI will use to push images to the registry
# DEV_OTEL_EXPORTER_OTLP_ENDPOINT="http://0.0.0.0:4318"
# These are needed for the object store (for handling large payloads/outputs)
# OBJECT_STORE_BASE_URL="https://{bucket}.{accountId}.r2.cloudflarestorage.com"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests-internal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,4 @@ jobs:
merge-multiple: true

- name: Merge reports
run: pnpm dlx [email protected] run --merge-reports
run: pnpm dlx [email protected] run --merge-reports --pass-with-no-tests
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,4 @@ jobs:
merge-multiple: true

- name: Merge reports
run: pnpm dlx [email protected] run --merge-reports
run: pnpm dlx [email protected] run --merge-reports --pass-with-no-tests
3 changes: 2 additions & 1 deletion .github/workflows/unit-tests-webapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ jobs:
SESSION_SECRET: "secret"
MAGIC_LINK_SECRET: "secret"
ENCRYPTION_KEY: "secret"
DEPLOY_REGISTRY_HOST: "docker.io"

- name: Gather all reports
if: ${{ !cancelled() }}
Expand Down Expand Up @@ -133,4 +134,4 @@ jobs:
merge-multiple: true

- name: Merge reports
run: pnpm dlx [email protected] run --merge-reports
run: pnpm dlx [email protected] run --merge-reports --pass-with-no-tests
9 changes: 9 additions & 0 deletions apps/supervisor/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const Env = z.object({
RUNNER_HEARTBEAT_INTERVAL_SECONDS: z.coerce.number().optional(),
RUNNER_SNAPSHOT_POLL_INTERVAL_SECONDS: z.coerce.number().optional(),
RUNNER_ADDITIONAL_ENV_VARS: AdditionalEnvVars, // optional (csv)
RUNNER_PRETTY_LOGS: BoolEnv.default(false),
RUNNER_DOCKER_AUTOREMOVE: BoolEnv.default(true),
/**
* Network mode to use for all runners. Supported standard values are: `bridge`, `host`, `none`, and `container:<name|id>`.
Expand All @@ -41,6 +42,14 @@ const Env = z.object({
*/
RUNNER_DOCKER_NETWORKS: z.string().default("host"),

// Docker settings
DOCKER_API_VERSION: z.string().default("v1.41"),
DOCKER_PLATFORM: z.string().optional(), // e.g. linux/amd64, linux/arm64
DOCKER_STRIP_IMAGE_DIGEST: BoolEnv.default(true),
DOCKER_REGISTRY_USERNAME: z.string().optional(),
DOCKER_REGISTRY_PASSWORD: z.string().optional(),
DOCKER_REGISTRY_URL: z.string().optional(), // e.g. https://index.docker.io/v1

// Dequeue settings (provider mode)
TRIGGER_DEQUEUE_ENABLED: BoolEnv.default("true"),
TRIGGER_DEQUEUE_INTERVAL_MS: z.coerce.number().int().default(250),
Expand Down
7 changes: 3 additions & 4 deletions apps/supervisor/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
export function getDockerHostDomain() {
const isMacOs = process.platform === "darwin";
const isWindows = process.platform === "win32";
import { isMacOS, isWindows } from "std-env";

return isMacOs || isWindows ? "host.docker.internal" : "localhost";
export function getDockerHostDomain() {
return isMacOS || isWindows ? "host.docker.internal" : "localhost";
}

export function getRunnerId(runId: string, attemptNumber?: number) {
Expand Down
138 changes: 118 additions & 20 deletions apps/supervisor/src/workloadManager/docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ export class DockerWorkloadManager implements WorkloadManager {
private readonly docker: Docker;

private readonly runnerNetworks: string[];
private readonly auth?: Docker.AuthConfig;
private readonly platformOverride?: string;

constructor(private opts: WorkloadManagerOptions) {
this.docker = new Docker();
this.docker = new Docker({
version: env.DOCKER_API_VERSION,
});

if (opts.workloadApiDomain) {
this.logger.warn("⚠️ Custom workload API domain", {
Expand All @@ -25,6 +29,29 @@ export class DockerWorkloadManager implements WorkloadManager {
}

this.runnerNetworks = env.RUNNER_DOCKER_NETWORKS.split(",");

this.platformOverride = env.DOCKER_PLATFORM;
if (this.platformOverride) {
this.logger.info("🖥️ Platform override", {
targetPlatform: this.platformOverride,
hostPlatform: process.arch,
});
}

if (env.DOCKER_REGISTRY_USERNAME && env.DOCKER_REGISTRY_PASSWORD && env.DOCKER_REGISTRY_URL) {
this.logger.info("🐋 Using Docker registry credentials", {
username: env.DOCKER_REGISTRY_USERNAME,
url: env.DOCKER_REGISTRY_URL,
});

this.auth = {
username: env.DOCKER_REGISTRY_USERNAME,
password: env.DOCKER_REGISTRY_PASSWORD,
serveraddress: env.DOCKER_REGISTRY_URL,
};
} else {
this.logger.warn("🐋 No Docker registry credentials provided, skipping auth");
}
}

async create(opts: WorkloadManagerCreateOptions) {
Expand All @@ -45,6 +72,7 @@ export class DockerWorkloadManager implements WorkloadManager {
`TRIGGER_WORKER_INSTANCE_NAME=${env.TRIGGER_WORKER_INSTANCE_NAME}`,
`OTEL_EXPORTER_OTLP_ENDPOINT=${env.OTEL_EXPORTER_OTLP_ENDPOINT}`,
`TRIGGER_RUNNER_ID=${runnerId}`,
`PRETTY_LOGS=${env.RUNNER_PRETTY_LOGS}`,
];

if (this.opts.warmStartUrl) {
Expand Down Expand Up @@ -90,41 +118,103 @@ export class DockerWorkloadManager implements WorkloadManager {
hostConfig.Memory = opts.machine.memory * 1024 * 1024 * 1024;
}

let imageRef = opts.image;

if (env.DOCKER_STRIP_IMAGE_DIGEST) {
imageRef = opts.image.split("@")[0]!;
}

const containerCreateOpts: Docker.ContainerCreateOptions = {
Env: envVars,
name: runnerId,
Hostname: runnerId,
HostConfig: hostConfig,
Image: opts.image,
Image: imageRef,
AttachStdout: false,
AttachStderr: false,
AttachStdin: false,
};

try {
// Create container
const container = await this.docker.createContainer(containerCreateOpts);
if (this.platformOverride) {
containerCreateOpts.platform = this.platformOverride;
}

const logger = this.logger.child({ opts, containerCreateOpts });

const [inspectError, inspectResult] = await tryCatch(this.docker.getImage(imageRef).inspect());

let shouldPull = !!inspectError;
if (this.platformOverride) {
const imageArchitecture = inspectResult?.Architecture;

// When the image architecture doesn't match the platform, we need to pull the image
if (imageArchitecture && !this.platformOverride.includes(imageArchitecture)) {
shouldPull = true;
}
}

// If the image is not present, try to pull it
if (shouldPull) {
logger.info("Pulling image", {
error: inspectError,
image: opts.image,
targetPlatform: this.platformOverride,
imageArchitecture: inspectResult?.Architecture,
});

// If there are multiple networks to attach to we need to attach the remaining ones after creation
if (remainingNetworks.length > 0) {
await this.attachContainerToNetworks({
containerId: container.id,
networkNames: remainingNetworks,
});
// Ensure the image is present
const [createImageError, imageResponseReader] = await tryCatch(
this.docker.createImage(this.auth, {
fromImage: imageRef,
...(this.platformOverride ? { platform: this.platformOverride } : {}),
})
);
if (createImageError) {
logger.error("Failed to pull image", { error: createImageError });
return;
}

// Start container
const startResult = await container.start();
const [imageReadError, imageResponse] = await tryCatch(readAllChunks(imageResponseReader));
if (imageReadError) {
logger.error("failed to read image response", { error: imageReadError });
return;
}

logger.debug("pulled image", { image: opts.image, imageResponse });
} else {
// Image is present, so we can use it to create the container
}

// Create container
const [createContainerError, container] = await tryCatch(
this.docker.createContainer({
...containerCreateOpts,
// Add env vars here so they're not logged
Env: envVars,
})
);

this.logger.debug("create succeeded", {
opts,
startResult,
if (createContainerError) {
logger.error("Failed to create container", { error: createContainerError });
return;
}

// If there are multiple networks to attach to we need to attach the remaining ones after creation
if (remainingNetworks.length > 0) {
await this.attachContainerToNetworks({
containerId: container.id,
containerCreateOpts,
networkNames: remainingNetworks,
});
} catch (error) {
this.logger.error("create failed:", { opts, error, containerCreateOpts });
}

// Start container
const [startError, startResult] = await tryCatch(container.start());

if (startError) {
logger.error("Failed to start container", { error: startError, containerId: container.id });
return;
}

logger.debug("create succeeded", { startResult, containerId: container.id });
}

private async attachContainerToNetworks({
Expand Down Expand Up @@ -173,3 +263,11 @@ export class DockerWorkloadManager implements WorkloadManager {
});
}
}

async function readAllChunks(reader: NodeJS.ReadableStream) {
const chunks = [];
for await (const chunk of reader) {
chunks.push(chunk.toString());
}
return chunks;
}
2 changes: 1 addition & 1 deletion apps/webapp/app/components/GitMetadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export function GitMetadata({ git }: { git?: GitMetaLinks | null }) {
return (
<>
{git.pullRequestUrl && git.pullRequestNumber && <GitMetadataPullRequest git={git} />}
{git.shortSha && <GitMetadataCommit git={git} />}
{git.branchUrl && <GitMetadataBranch git={git} />}
{git.shortSha && <GitMetadataCommit git={git} />}
</>
);
}
Expand Down
8 changes: 7 additions & 1 deletion apps/webapp/app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,20 @@ export { apiRateLimiter } from "./services/apiRateLimit.server";
export { engineRateLimiter } from "./services/engineRateLimit.server";
export { socketIo } from "./v3/handleSocketIo.server";
export { wss } from "./v3/handleWebsockets.server";
export { registryProxy } from "./v3/registryProxy.server";
export { runWithHttpContext } from "./services/httpAsyncStorage.server";
import { eventLoopMonitor } from "./eventLoopMonitor.server";
import { env } from "./env.server";
import { logger } from "./services/logger.server";
import { Prisma } from "./db.server";
import { registerRunEngineEventBusHandlers } from "./v3/runEngineHandlers.server";
import { remoteBuildsEnabled } from "./v3/remoteImageBuilder.server";

if (env.EVENT_LOOP_MONITOR_ENABLED === "1") {
eventLoopMonitor.enable();
}

if (remoteBuildsEnabled()) {
console.log("🏗️ Remote builds enabled");
} else {
console.log("🏗️ Local builds enabled");
}
8 changes: 2 additions & 6 deletions apps/webapp/app/env.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,17 +264,13 @@ const EnvironmentSchema = z.object({
PROVIDER_SECRET: z.string().default("provider-secret"),
COORDINATOR_SECRET: z.string().default("coordinator-secret"),
DEPOT_TOKEN: z.string().optional(),
DEPOT_PROJECT_ID: z.string().optional(),
DEPOT_ORG_ID: z.string().optional(),
DEPOT_REGION: z.string().default("us-east-1"),
CONTAINER_REGISTRY_ORIGIN: z.string().optional(),
CONTAINER_REGISTRY_USERNAME: z.string().optional(),
CONTAINER_REGISTRY_PASSWORD: z.string().optional(),
ENABLE_REGISTRY_PROXY: z.string().optional(),
DEPLOY_REGISTRY_HOST: z.string().optional(),
DEPLOY_REGISTRY_HOST: z.string(),
DEPLOY_REGISTRY_USERNAME: z.string().optional(),
DEPLOY_REGISTRY_PASSWORD: z.string().optional(),
DEPLOY_REGISTRY_NAMESPACE: z.string().default("trigger"),
DEPLOY_IMAGE_PLATFORM: z.string().default("linux/amd64"),
DEPLOY_TIMEOUT_MS: z.coerce
.number()
.int()
Expand Down
2 changes: 2 additions & 0 deletions apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export class DeploymentPresenter {
version: true,
errorData: true,
imageReference: true,
imagePlatform: true,
externalBuildData: true,
projectId: true,
type: true,
Expand Down Expand Up @@ -157,6 +158,7 @@ export class DeploymentPresenter {
sdkVersion: deployment.worker?.sdkVersion,
cliVersion: deployment.worker?.cliVersion,
imageReference: deployment.imageReference,
imagePlatform: deployment.imagePlatform,
externalBuildData:
externalBuildData && externalBuildData.success ? externalBuildData.data : undefined,
projectId: deployment.projectId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ export default function Page() {
<Property.Value>{deployment.imageReference}</Property.Value>
</Property.Item>
)}
<Property.Item>
<Property.Label>Platform</Property.Label>
<Property.Value>{deployment.imagePlatform}</Property.Value>
</Property.Item>
{deployment.externalBuildData && (
<Property.Item>
<Property.Label>Build Server</Property.Label>
Expand Down Expand Up @@ -230,7 +234,9 @@ export default function Page() {
</Property.Table>
</div>

{deployment.tasks ? (
{deployment.errorData && <DeploymentError errorData={deployment.errorData} />}

{deployment.tasks && (
<div className="divide-y divide-charcoal-800 overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
<Table variant="bright">
<TableHeader>
Expand Down Expand Up @@ -260,9 +266,7 @@ export default function Page() {
</TableBody>
</Table>
</div>
) : deployment.errorData ? (
<DeploymentError errorData={deployment.errorData} />
) : null}
)}
</div>
</div>
</div>
Expand Down
Loading
Loading