Skip to content

Commit 74e9246

Browse files
authored
feat(cli): enable zstd compression for deployment images (#2773)
This will speed up ice cold starts (*) for two reasons: - better compression ratio - faster decompression This is a minor release because zstd compression will now be enabled by default for all deployments. (*) ice cold starts happen when deploy images are not cached on the worker node yet. These cold start durations are highly dependent on image size and as it turns out, also the type of compression used.
1 parent 07a31d3 commit 74e9246

File tree

3 files changed

+145
-5
lines changed

3 files changed

+145
-5
lines changed

.changeset/great-pillows-look.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": minor
3+
---
4+
5+
feat(cli): enable zstd compression for deployment images

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

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const DeployCommandOptions = CommonCommandOptions.extend({
7171
saveLogs: z.boolean().default(false),
7272
skipUpdateCheck: z.boolean().default(false),
7373
skipPromotion: z.boolean().default(false),
74-
noCache: z.boolean().default(false),
74+
cache: z.boolean().default(true),
7575
envFile: z.string().optional(),
7676
// Local build options
7777
forceLocalBuild: z.boolean().optional(),
@@ -83,6 +83,10 @@ const DeployCommandOptions = CommonCommandOptions.extend({
8383
nativeBuildServer: z.boolean().default(false),
8484
detach: z.boolean().default(false),
8585
plain: z.boolean().default(false),
86+
compression: z.enum(["zstd", "gzip"]).default("zstd"),
87+
cacheCompression: z.enum(["zstd", "gzip"]).default("zstd"),
88+
compressionLevel: z.number().optional(),
89+
forceCompression: z.boolean().default(true),
8690
});
8791

8892
type DeployCommandOptions = z.infer<typeof DeployCommandOptions>;
@@ -157,6 +161,40 @@ export function configureDeployCommand(program: Command) {
157161
"If provided, will save logs even for successful builds"
158162
).hideHelp()
159163
)
164+
.addOption(
165+
new CommandOption(
166+
"--compression <algorithm>",
167+
"Compression algorithm for image layers: zstd or gzip (default: zstd)"
168+
)
169+
.choices(["zstd", "gzip"])
170+
.hideHelp()
171+
)
172+
.addOption(
173+
new CommandOption(
174+
"--cache-compression <algorithm>",
175+
"Compression algorithm for build cache: zstd or gzip (default: zstd)"
176+
)
177+
.choices(["zstd", "gzip"])
178+
.hideHelp()
179+
)
180+
.addOption(
181+
new CommandOption(
182+
"--compression-level <level>",
183+
"The compression level to use when building the image."
184+
).hideHelp()
185+
)
186+
.addOption(
187+
new CommandOption(
188+
"--force-compression",
189+
"Force recompression of all layers. Enabled by default when using zstd."
190+
).hideHelp()
191+
)
192+
.addOption(
193+
new CommandOption(
194+
"--no-force-compression",
195+
"Disable forced recompression of layers."
196+
).hideHelp()
197+
)
160198
// Local build options
161199
.addOption(
162200
new CommandOption("--force-local-build", "Deprecated alias for --local-build").implies({
@@ -480,7 +518,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
480518
const buildResult = await buildImage({
481519
isLocalBuild,
482520
useRegistryCache: options.useRegistryCache,
483-
noCache: options.noCache,
521+
noCache: !options.cache,
484522
deploymentId: deployment.id,
485523
deploymentVersion: deployment.version,
486524
imageTag: deployment.imageTag,
@@ -499,6 +537,10 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
499537
authAccessToken: authorization.auth.accessToken,
500538
compilationPath: destination.path,
501539
buildEnvVars: buildManifest.build.env,
540+
compression: options.compression,
541+
cacheCompression: options.cacheCompression,
542+
compressionLevel: options.compressionLevel,
543+
forceCompression: options.forceCompression,
502544
onLog: (logMessage) => {
503545
if (options.plain || isCI) {
504546
console.log(logMessage);

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

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export interface BuildImageOptions {
2020
imagePlatform: string;
2121
noCache?: boolean;
2222
load?: boolean;
23+
compression?: "zstd" | "gzip";
24+
cacheCompression?: "zstd" | "gzip";
25+
compressionLevel?: number;
26+
forceCompression?: boolean;
2327

2428
// Local build options
2529
push?: boolean;
@@ -79,6 +83,10 @@ export async function buildImage(options: BuildImageOptions): Promise<BuildImage
7983
buildEnvVars,
8084
network,
8185
builder,
86+
compression,
87+
cacheCompression,
88+
compressionLevel,
89+
forceCompression,
8290
onLog,
8391
} = options;
8492

@@ -105,6 +113,10 @@ export async function buildImage(options: BuildImageOptions): Promise<BuildImage
105113
buildEnvVars,
106114
network,
107115
builder,
116+
compression,
117+
cacheCompression,
118+
compressionLevel,
119+
forceCompression,
108120
onLog,
109121
});
110122
}
@@ -134,6 +146,9 @@ export async function buildImage(options: BuildImageOptions): Promise<BuildImage
134146
apiKey,
135147
branchName,
136148
buildEnvVars,
149+
compression,
150+
compressionLevel,
151+
forceCompression,
137152
onLog,
138153
});
139154
}
@@ -157,6 +172,9 @@ export interface DepotBuildImageOptions {
157172
noCache?: boolean;
158173
extraCACerts?: string;
159174
buildEnvVars?: Record<string, string | undefined>;
175+
compression?: "zstd" | "gzip";
176+
compressionLevel?: number;
177+
forceCompression?: boolean;
160178
onLog?: (log: string) => void;
161179
}
162180

@@ -180,6 +198,14 @@ async function remoteBuildImage(options: DepotBuildImageOptions): Promise<BuildI
180198
.filter(([key, value]) => value)
181199
.flatMap(([key, value]) => ["--build-arg", `${key}=${value}`]);
182200

201+
const outputOptions = getOutputOptions({
202+
imageTag: undefined, // This is already handled via the --save flag
203+
push: true, // We always push the image to the registry
204+
compression: options.compression,
205+
compressionLevel: options.compressionLevel,
206+
forceCompression: options.forceCompression,
207+
});
208+
183209
const args = [
184210
"build",
185211
"-f",
@@ -214,6 +240,8 @@ async function remoteBuildImage(options: DepotBuildImageOptions): Promise<BuildI
214240
"plain",
215241
".",
216242
"--save",
243+
"--output",
244+
outputOptions.join(","),
217245
].filter(Boolean) as string[];
218246

219247
logger.debug(`depot ${args.join(" ")}`, { cwd: options.cwd });
@@ -316,11 +344,25 @@ interface SelfHostedBuildImageOptions {
316344
network?: string;
317345
builder: string;
318346
load?: boolean;
347+
compression?: "zstd" | "gzip";
348+
cacheCompression?: "zstd" | "gzip";
349+
compressionLevel?: number;
350+
forceCompression?: boolean;
319351
onLog?: (log: string) => void;
320352
}
321353

322354
async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<BuildImageResults> {
323-
const { builder, imageTag, deploymentId, apiClient, useRegistryCache } = options;
355+
const {
356+
builder,
357+
imageTag,
358+
deploymentId,
359+
apiClient,
360+
useRegistryCache,
361+
compression,
362+
cacheCompression,
363+
compressionLevel,
364+
forceCompression,
365+
} = options;
324366

325367
// Ensure multi-platform build is supported on the local machine
326368
let builderExists = false;
@@ -489,6 +531,14 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
489531

490532
const projectCacheRef = getProjectCacheRefFromImageTag(imageTag);
491533

534+
const outputOptions = getOutputOptions({
535+
imageTag,
536+
push,
537+
compression,
538+
compressionLevel,
539+
forceCompression,
540+
});
541+
492542
const args = [
493543
"buildx",
494544
"build",
@@ -500,16 +550,19 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
500550
...(useRegistryCache
501551
? [
502552
"--cache-to",
503-
`type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=${projectCacheRef}`,
553+
`type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=${projectCacheRef}${
554+
cacheCompression === "zstd" ? ",compression=zstd" : ""
555+
}`,
504556
"--cache-from",
505557
`type=registry,ref=${projectCacheRef}`,
506558
]
507559
: []),
560+
"--output",
561+
outputOptions.join(","),
508562
"--platform",
509563
options.imagePlatform,
510564
options.network ? `--network=${options.network}` : undefined,
511565
addHost ? `--add-host=${addHost}` : undefined,
512-
push ? "--push" : undefined,
513566
load ? "--load" : undefined,
514567
"--provenance",
515568
"false",
@@ -1072,3 +1125,43 @@ function shouldLoad(load?: boolean, push?: boolean) {
10721125
}
10731126
}
10741127
}
1128+
1129+
function getOutputOptions({
1130+
imageTag,
1131+
push,
1132+
compression,
1133+
compressionLevel,
1134+
forceCompression,
1135+
}: {
1136+
imageTag?: string;
1137+
push?: boolean;
1138+
compression?: "zstd" | "gzip";
1139+
compressionLevel?: number;
1140+
forceCompression?: boolean;
1141+
}): string[] {
1142+
// Always use OCI media types for compatibility
1143+
const outputOptions: string[] = ["type=image", "oci-mediatypes=true"];
1144+
1145+
if (imageTag) {
1146+
outputOptions.push(`name=${imageTag}`);
1147+
}
1148+
1149+
if (push) {
1150+
outputOptions.push("push=true");
1151+
}
1152+
1153+
// Only add compression args when using zstd (gzip is the default, no args needed)
1154+
if (compression === "zstd") {
1155+
outputOptions.push("compression=zstd");
1156+
1157+
if (compressionLevel !== undefined) {
1158+
outputOptions.push(`compression-level=${compressionLevel}`);
1159+
}
1160+
}
1161+
1162+
if (forceCompression) {
1163+
outputOptions.push("force-compression=true");
1164+
}
1165+
1166+
return outputOptions;
1167+
}

0 commit comments

Comments
 (0)