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:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": minor
|
||||
---
|
||||
|
||||
feat(cli): enable zstd compression for deployment images
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user