Skip to content

Commit 5f8edb7

Browse files
committed
Make the image builds deterministic
1 parent b71bf89 commit 5f8edb7

File tree

9 files changed

+73
-45
lines changed

9 files changed

+73
-45
lines changed

packages/cli-v3/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@
9292
"@opentelemetry/resources": "2.0.1",
9393
"@opentelemetry/sdk-trace-node": "2.0.1",
9494
"@opentelemetry/semantic-conventions": "1.36.0",
95+
"@s2-dev/streamstore": "^0.17.6",
9596
"@trigger.dev/build": "workspace:4.2.0",
9697
"@trigger.dev/core": "workspace:4.2.0",
9798
"@trigger.dev/schema-to-json": "workspace:4.2.0",
98-
"@s2-dev/streamstore": "^0.17.6",
9999
"ansi-escapes": "^7.0.0",
100100
"braces": "^3.0.3",
101101
"c12": "^1.11.1",
@@ -117,6 +117,7 @@
117117
"import-in-the-middle": "1.11.0",
118118
"import-meta-resolve": "^4.1.0",
119119
"ini": "^5.0.0",
120+
"json-stable-stringify": "^1.3.0",
120121
"jsonc-parser": "3.2.1",
121122
"magicast": "^0.3.4",
122123
"minimatch": "^10.0.1",

packages/cli-v3/src/build/buildWorker.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { join, relative, sep } from "node:path";
1212
import { generateContainerfile } from "../deploy/buildImage.js";
1313
import { writeFile } from "node:fs/promises";
1414
import { buildManifestToJSON } from "../utilities/buildManifest.js";
15-
import { readPackageJSON, writePackageJSON } from "pkg-types";
15+
import { readPackageJSON } from "pkg-types";
1616
import { writeJSONFile } from "../utilities/fileSystem.js";
1717
import { isWindows } from "std-env";
1818
import { pathToFileURL } from "node:url";
@@ -192,20 +192,23 @@ async function writeDeployFiles({
192192
) ?? {};
193193

194194
// Step 3: Write the resolved dependencies to the package.json file
195-
await writePackageJSON(join(outputPath, "package.json"), {
196-
...packageJson,
197-
name: packageJson.name ?? "trigger-project",
198-
dependencies: {
199-
...dependencies,
195+
await writeJSONFile(
196+
join(outputPath, "package.json"),
197+
{
198+
...packageJson,
199+
name: packageJson.name ?? "trigger-project",
200+
dependencies: {
201+
...dependencies,
202+
},
203+
trustedDependencies: Object.keys(dependencies).sort(),
204+
devDependencies: {},
205+
peerDependencies: {},
206+
scripts: {},
200207
},
201-
trustedDependencies: Object.keys(dependencies),
202-
devDependencies: {},
203-
peerDependencies: {},
204-
scripts: {},
205-
});
208+
true
209+
);
206210

207211
await writeJSONFile(join(outputPath, "build.json"), buildManifestToJSON(buildManifest));
208-
await writeJSONFile(join(outputPath, "metafile.json"), bundleResult.metafile);
209212
await writeContainerfile(outputPath, buildManifest);
210213
}
211214

packages/cli-v3/src/build/bundle.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,9 @@ export async function createBuildManifestFromBundle({
414414
otelImportHook: {
415415
include: resolvedConfig.instrumentedPackageNames ?? [],
416416
},
417-
outputHashes: bundle.outputHashes,
417+
// `outputHashes` is only needed for dev builds for the deduplication mechanism during rebuilds.
418+
// For deploys builds, we omit it to ensure deterministic builds
419+
outputHashes: target === "dev" ? bundle.outputHashes : {},
418420
};
419421

420422
if (!workerDir) {

packages/cli-v3/src/deploy/buildImage.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,12 @@ async function remoteBuildImage(options: DepotBuildImageOptions): Promise<BuildI
193193
"--metadata-file",
194194
"metadata.json",
195195
"--build-arg",
196+
`SOURCE_DATE_EPOCH=0`,
197+
"--build-arg",
196198
`TRIGGER_PROJECT_ID=${options.projectId}`,
197199
"--build-arg",
198200
`TRIGGER_DEPLOYMENT_ID=${options.deploymentId}`,
199201
"--build-arg",
200-
`TRIGGER_DEPLOYMENT_VERSION=${options.deploymentVersion}`,
201-
"--build-arg",
202202
`TRIGGER_CONTENT_HASH=${options.contentHash}`,
203203
"--build-arg",
204204
`TRIGGER_PROJECT_REF=${options.projectRef}`,
@@ -210,6 +210,8 @@ async function remoteBuildImage(options: DepotBuildImageOptions): Promise<BuildI
210210
`TRIGGER_SECRET_KEY=${options.apiKey}`,
211211
...(buildArgs || []),
212212
...(options.extraCACerts ? ["--build-arg", `NODE_EXTRA_CA_CERTS=${options.extraCACerts}`] : []),
213+
"--output",
214+
"type=image,rewrite-timestamp=true",
213215
"--progress",
214216
"plain",
215217
".",
@@ -509,19 +511,17 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
509511
options.imagePlatform,
510512
options.network ? `--network=${options.network}` : undefined,
511513
addHost ? `--add-host=${addHost}` : undefined,
512-
push ? "--push" : undefined,
513-
load ? "--load" : undefined,
514514
"--provenance",
515515
"false",
516516
"--metadata-file",
517517
"metadata.json",
518518
"--build-arg",
519+
`SOURCE_DATE_EPOCH=0`,
520+
"--build-arg",
519521
`TRIGGER_PROJECT_ID=${options.projectId}`,
520522
"--build-arg",
521523
`TRIGGER_DEPLOYMENT_ID=${options.deploymentId}`,
522524
"--build-arg",
523-
`TRIGGER_DEPLOYMENT_VERSION=${options.deploymentVersion}`,
524-
"--build-arg",
525525
`TRIGGER_CONTENT_HASH=${options.contentHash}`,
526526
"--build-arg",
527527
`TRIGGER_PROJECT_REF=${options.projectRef}`,
@@ -533,10 +533,12 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
533533
`TRIGGER_SECRET_KEY=${options.apiKey}`,
534534
...(buildArgs || []),
535535
...(options.extraCACerts ? ["--build-arg", `NODE_EXTRA_CA_CERTS=${options.extraCACerts}`] : []),
536+
"--output",
537+
`type=image,name=${imageTag},rewrite-timestamp=true${push ? ",push=true" : ""}${
538+
load ? ",load=true" : ""
539+
}`,
536540
"--progress",
537541
"plain",
538-
"-t",
539-
imageTag,
540542
".", // The build context
541543
].filter(Boolean) as string[];
542544

@@ -761,15 +763,11 @@ USER bun
761763
WORKDIR /app
762764
763765
ARG TRIGGER_PROJECT_ID
764-
ARG TRIGGER_DEPLOYMENT_ID
765-
ARG TRIGGER_DEPLOYMENT_VERSION
766766
ARG TRIGGER_CONTENT_HASH
767767
ARG TRIGGER_PROJECT_REF
768768
ARG NODE_EXTRA_CA_CERTS
769769
770770
ENV TRIGGER_PROJECT_ID=\${TRIGGER_PROJECT_ID} \
771-
TRIGGER_DEPLOYMENT_ID=\${TRIGGER_DEPLOYMENT_ID} \
772-
TRIGGER_DEPLOYMENT_VERSION=\${TRIGGER_DEPLOYMENT_VERSION} \
773771
TRIGGER_CONTENT_HASH=\${TRIGGER_CONTENT_HASH} \
774772
TRIGGER_PROJECT_REF=\${TRIGGER_PROJECT_REF} \
775773
UV_USE_IO_URING=0 \
@@ -875,15 +873,11 @@ USER node
875873
WORKDIR /app
876874
877875
ARG TRIGGER_PROJECT_ID
878-
ARG TRIGGER_DEPLOYMENT_ID
879-
ARG TRIGGER_DEPLOYMENT_VERSION
880876
ARG TRIGGER_CONTENT_HASH
881877
ARG TRIGGER_PROJECT_REF
882878
ARG NODE_EXTRA_CA_CERTS
883879
884880
ENV TRIGGER_PROJECT_ID=\${TRIGGER_PROJECT_ID} \
885-
TRIGGER_DEPLOYMENT_ID=\${TRIGGER_DEPLOYMENT_ID} \
886-
TRIGGER_DEPLOYMENT_VERSION=\${TRIGGER_DEPLOYMENT_VERSION} \
887881
TRIGGER_CONTENT_HASH=\${TRIGGER_CONTENT_HASH} \
888882
TRIGGER_PROJECT_REF=\${TRIGGER_PROJECT_REF} \
889883
UV_USE_IO_URING=0 \

packages/cli-v3/src/entryPoints/managed-index-controller.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { CliApiClient } from "../apiClient.js";
1010
import { indexWorkerManifest } from "../indexing/indexWorkerManifest.js";
1111
import { resolveSourceFiles } from "../utilities/sourceFiles.js";
1212
import { execOptionsForRuntime } from "@trigger.dev/core/v3/build";
13+
import { writeJSONFile } from "../utilities/fileSystem.js";
1314

1415
async function loadBuildManifest() {
1516
const manifestContents = await readFile("./build.json", "utf-8");
@@ -88,7 +89,8 @@ async function indexDeployment({
8889

8990
console.log("Writing index.json", process.cwd());
9091

91-
await writeFile(join(process.cwd(), "index.json"), JSON.stringify(workerManifest, null, 2));
92+
const { timings, ...manifestWithoutTimings } = workerManifest;
93+
await writeJSONFile(join(process.cwd(), "index.json"), manifestWithoutTimings, true);
9294

9395
const sourceFiles = resolveSourceFiles(buildManifest.sources, workerManifest.tasks);
9496

packages/cli-v3/src/entryPoints/managed/env.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ const DateEnv = z
1212
const Env = z.object({
1313
// Set at build time
1414
TRIGGER_CONTENT_HASH: z.string(),
15-
TRIGGER_DEPLOYMENT_ID: z.string(),
16-
TRIGGER_DEPLOYMENT_VERSION: z.string(),
1715
TRIGGER_PROJECT_ID: z.string(),
1816
TRIGGER_PROJECT_REF: z.string(),
1917
NODE_ENV: z.string().default("production"),
2018
NODE_EXTRA_CA_CERTS: z.string().optional(),
2119
UV_USE_IO_URING: z.string().optional(),
2220

2321
// Set at runtime
22+
TRIGGER_DEPLOYMENT_ID: z.string(),
23+
TRIGGER_DEPLOYMENT_VERSION: z.string(),
2424
TRIGGER_WORKLOAD_CONTROLLER_ID: z.string().default(`controller_${randomUUID()}`),
2525
TRIGGER_ENV_ID: z.string(),
2626
OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url(),

packages/cli-v3/src/utilities/buildManifest.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { BuildManifest } from "@trigger.dev/core/v3/schemas";
22

33
export function buildManifestToJSON(manifest: BuildManifest): BuildManifest {
4-
const { deploy, build, ...rest } = manifest;
4+
const { deploy, build, externals, ...rest } = manifest;
55

66
return {
77
...rest,
8+
// sort externals for deterministic builds
9+
externals: externals?.slice().sort((a, b) => a.name.localeCompare(b.name)),
810
deploy: {},
911
build: {},
1012
};

packages/cli-v3/src/utilities/fileSystem.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fsSync from "fs";
2+
import stringify from "json-stable-stringify";
23
import fsModule, { writeFile } from "fs/promises";
34
import fs from "node:fs";
45
import { homedir, tmpdir } from "node:os";
@@ -159,7 +160,7 @@ export async function safeReadJSONFile(path: string) {
159160
}
160161

161162
export async function writeJSONFile(path: string, json: any, pretty = false) {
162-
await safeWriteFile(path, JSON.stringify(json, undefined, pretty ? 2 : undefined));
163+
await safeWriteFile(path, stringify(json, pretty ? { space: 2 } : undefined) ?? "");
163164
}
164165

165166
// Will create the directory if it doesn't exist

pnpm-lock.yaml

Lines changed: 33 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)