From 74e9246bfa862cc0b14ddd12e0d76677face490f Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:49:44 +0000 Subject: [PATCH] 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. --- .changeset/great-pillows-look.md | 5 ++ packages/cli-v3/src/commands/deploy.ts | 46 ++++++++++- packages/cli-v3/src/deploy/buildImage.ts | 99 +++++++++++++++++++++++- 3 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 .changeset/great-pillows-look.md diff --git a/.changeset/great-pillows-look.md b/.changeset/great-pillows-look.md new file mode 100644 index 000000000..72a0589b2 --- /dev/null +++ b/.changeset/great-pillows-look.md @@ -0,0 +1,5 @@ +--- +"trigger.dev": minor +--- + +feat(cli): enable zstd compression for deployment images diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 818051d68..f4de03281 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -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; @@ -157,6 +161,40 @@ export function configureDeployCommand(program: Command) { "If provided, will save logs even for successful builds" ).hideHelp() ) + .addOption( + new CommandOption( + "--compression ", + "Compression algorithm for image layers: zstd or gzip (default: zstd)" + ) + .choices(["zstd", "gzip"]) + .hideHelp() + ) + .addOption( + new CommandOption( + "--cache-compression ", + "Compression algorithm for build cache: zstd or gzip (default: zstd)" + ) + .choices(["zstd", "gzip"]) + .hideHelp() + ) + .addOption( + new CommandOption( + "--compression-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); diff --git a/packages/cli-v3/src/deploy/buildImage.ts b/packages/cli-v3/src/deploy/buildImage.ts index e325b58d8..d6957bf97 100644 --- a/packages/cli-v3/src/deploy/buildImage.ts +++ b/packages/cli-v3/src/deploy/buildImage.ts @@ -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; + compression?: "zstd" | "gzip"; + compressionLevel?: number; + forceCompression?: boolean; onLog?: (log: string) => void; } @@ -180,6 +198,14 @@ async function remoteBuildImage(options: DepotBuildImageOptions): Promise 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 void; } async function localBuildImage(options: SelfHostedBuildImageOptions): Promise { - 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