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.
This commit is contained in:
nicktrn
2025-12-10 14:49:44 +00:00
committed by GitHub
parent 07a31d3732
commit 74e9246bfa
3 changed files with 145 additions and 5 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": minor
---
feat(cli): enable zstd compression for deployment images
+44 -2
View File
@@ -71,7 +71,7 @@ const DeployCommandOptions = CommonCommandOptions.extend({
saveLogs: z.boolean().default(false),
skipUpdateCheck: z.boolean().default(false),
skipPromotion: z.boolean().default(false),
noCache: z.boolean().default(false),
cache: z.boolean().default(true),
envFile: z.string().optional(),
// Local build options
forceLocalBuild: z.boolean().optional(),
@@ -83,6 +83,10 @@ const DeployCommandOptions = CommonCommandOptions.extend({
nativeBuildServer: z.boolean().default(false),
detach: z.boolean().default(false),
plain: z.boolean().default(false),
compression: z.enum(["zstd", "gzip"]).default("zstd"),
cacheCompression: z.enum(["zstd", "gzip"]).default("zstd"),
compressionLevel: z.number().optional(),
forceCompression: z.boolean().default(true),
});
type DeployCommandOptions = z.infer<typeof DeployCommandOptions>;
@@ -157,6 +161,40 @@ export function configureDeployCommand(program: Command) {
"If provided, will save logs even for successful builds"
).hideHelp()
)
.addOption(
new CommandOption(
"--compression <algorithm>",
"Compression algorithm for image layers: zstd or gzip (default: zstd)"
)
.choices(["zstd", "gzip"])
.hideHelp()
)
.addOption(
new CommandOption(
"--cache-compression <algorithm>",
"Compression algorithm for build cache: zstd or gzip (default: zstd)"
)
.choices(["zstd", "gzip"])
.hideHelp()
)
.addOption(
new CommandOption(
"--compression-level <level>",
"The compression level to use when building the image."
).hideHelp()
)
.addOption(
new CommandOption(
"--force-compression",
"Force recompression of all layers. Enabled by default when using zstd."
).hideHelp()
)
.addOption(
new CommandOption(
"--no-force-compression",
"Disable forced recompression of layers."
).hideHelp()
)
// Local build options
.addOption(
new CommandOption("--force-local-build", "Deprecated alias for --local-build").implies({
@@ -480,7 +518,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
const buildResult = await buildImage({
isLocalBuild,
useRegistryCache: options.useRegistryCache,
noCache: options.noCache,
noCache: !options.cache,
deploymentId: deployment.id,
deploymentVersion: deployment.version,
imageTag: deployment.imageTag,
@@ -499,6 +537,10 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
authAccessToken: authorization.auth.accessToken,
compilationPath: destination.path,
buildEnvVars: buildManifest.build.env,
compression: options.compression,
cacheCompression: options.cacheCompression,
compressionLevel: options.compressionLevel,
forceCompression: options.forceCompression,
onLog: (logMessage) => {
if (options.plain || isCI) {
console.log(logMessage);
+96 -3
View File
@@ -20,6 +20,10 @@ export interface BuildImageOptions {
imagePlatform: string;
noCache?: boolean;
load?: boolean;
compression?: "zstd" | "gzip";
cacheCompression?: "zstd" | "gzip";
compressionLevel?: number;
forceCompression?: boolean;
// Local build options
push?: boolean;
@@ -79,6 +83,10 @@ export async function buildImage(options: BuildImageOptions): Promise<BuildImage
buildEnvVars,
network,
builder,
compression,
cacheCompression,
compressionLevel,
forceCompression,
onLog,
} = options;
@@ -105,6 +113,10 @@ export async function buildImage(options: BuildImageOptions): Promise<BuildImage
buildEnvVars,
network,
builder,
compression,
cacheCompression,
compressionLevel,
forceCompression,
onLog,
});
}
@@ -134,6 +146,9 @@ export async function buildImage(options: BuildImageOptions): Promise<BuildImage
apiKey,
branchName,
buildEnvVars,
compression,
compressionLevel,
forceCompression,
onLog,
});
}
@@ -157,6 +172,9 @@ export interface DepotBuildImageOptions {
noCache?: boolean;
extraCACerts?: string;
buildEnvVars?: Record<string, string | undefined>;
compression?: "zstd" | "gzip";
compressionLevel?: number;
forceCompression?: boolean;
onLog?: (log: string) => void;
}
@@ -180,6 +198,14 @@ async function remoteBuildImage(options: DepotBuildImageOptions): Promise<BuildI
.filter(([key, value]) => value)
.flatMap(([key, value]) => ["--build-arg", `${key}=${value}`]);
const outputOptions = getOutputOptions({
imageTag: undefined, // This is already handled via the --save flag
push: true, // We always push the image to the registry
compression: options.compression,
compressionLevel: options.compressionLevel,
forceCompression: options.forceCompression,
});
const args = [
"build",
"-f",
@@ -214,6 +240,8 @@ async function remoteBuildImage(options: DepotBuildImageOptions): Promise<BuildI
"plain",
".",
"--save",
"--output",
outputOptions.join(","),
].filter(Boolean) as string[];
logger.debug(`depot ${args.join(" ")}`, { cwd: options.cwd });
@@ -316,11 +344,25 @@ interface SelfHostedBuildImageOptions {
network?: string;
builder: string;
load?: boolean;
compression?: "zstd" | "gzip";
cacheCompression?: "zstd" | "gzip";
compressionLevel?: number;
forceCompression?: boolean;
onLog?: (log: string) => void;
}
async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<BuildImageResults> {
const { builder, imageTag, deploymentId, apiClient, useRegistryCache } = options;
const {
builder,
imageTag,
deploymentId,
apiClient,
useRegistryCache,
compression,
cacheCompression,
compressionLevel,
forceCompression,
} = options;
// Ensure multi-platform build is supported on the local machine
let builderExists = false;
@@ -489,6 +531,14 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
const projectCacheRef = getProjectCacheRefFromImageTag(imageTag);
const outputOptions = getOutputOptions({
imageTag,
push,
compression,
compressionLevel,
forceCompression,
});
const args = [
"buildx",
"build",
@@ -500,16 +550,19 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
...(useRegistryCache
? [
"--cache-to",
`type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=${projectCacheRef}`,
`type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=${projectCacheRef}${
cacheCompression === "zstd" ? ",compression=zstd" : ""
}`,
"--cache-from",
`type=registry,ref=${projectCacheRef}`,
]
: []),
"--output",
outputOptions.join(","),
"--platform",
options.imagePlatform,
options.network ? `--network=${options.network}` : undefined,
addHost ? `--add-host=${addHost}` : undefined,
push ? "--push" : undefined,
load ? "--load" : undefined,
"--provenance",
"false",
@@ -1072,3 +1125,43 @@ function shouldLoad(load?: boolean, push?: boolean) {
}
}
}
function getOutputOptions({
imageTag,
push,
compression,
compressionLevel,
forceCompression,
}: {
imageTag?: string;
push?: boolean;
compression?: "zstd" | "gzip";
compressionLevel?: number;
forceCompression?: boolean;
}): string[] {
// Always use OCI media types for compatibility
const outputOptions: string[] = ["type=image", "oci-mediatypes=true"];
if (imageTag) {
outputOptions.push(`name=${imageTag}`);
}
if (push) {
outputOptions.push("push=true");
}
// Only add compression args when using zstd (gzip is the default, no args needed)
if (compression === "zstd") {
outputOptions.push("compression=zstd");
if (compressionLevel !== undefined) {
outputOptions.push(`compression-level=${compressionLevel}`);
}
}
if (forceCompression) {
outputOptions.push("force-compression=true");
}
return outputOptions;
}