Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/split-image-dependency-layers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---

Deployed images now ship dependencies and bundled task code as separate layers. Repeat deploys with unchanged dependencies typically push and pull far less data, making deploys and worker image pulls faster.
49 changes: 49 additions & 0 deletions packages/cli-v3/src/deploy/buildImage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,53 @@ describe("generateContainerfile", () => {

expect(containerfile).toContain(`FROM ${image} AS base`);
});

it.each(["node", "bun"] as BuildRuntime[])(
"splits node_modules and app code into separate layers for %s",
async (runtime) => {
const containerfile = await generateContainerfile({
runtime,
build: {},
image: undefined,
indexScript: "index.js",
entrypoint: "entrypoint.js",
});

const user = runtime === "bun" ? "bun:bun" : "node:node";

expect(containerfile).toContain("FROM build AS code");
expect(containerfile).toContain(
`COPY --from=build --chown=${user} /app/node_modules ./node_modules`
);
expect(containerfile).toContain(`COPY --from=code --chown=${user} /app ./`);
// copying all of /app from build would duplicate node_modules across two layers
expect(containerfile).not.toContain(`COPY --from=build --chown=${user} /app ./`);
}
);

it.each(["node", "bun"] as BuildRuntime[])(
"orders post-install commands, the node_modules guard, and the code stage for %s",
async (runtime) => {
const containerfile = await generateContainerfile({
runtime,
build: { commands: ["echo post-install"] },
image: undefined,
indexScript: "index.js",
entrypoint: "entrypoint.js",
});

const postInstall = containerfile.indexOf("RUN echo post-install");
// guard after post-install so a command that prunes node_modules can't break the COPY
const mkdirGuard = containerfile.indexOf("RUN mkdir -p node_modules");
const codeStage = containerfile.indexOf("FROM build AS code");
const rmNodeModules = containerfile.indexOf(
"RUN chmod -R u+rwX node_modules && rm -rf node_modules"
);

expect(postInstall).toBeGreaterThan(-1);
expect(mkdirGuard).toBeGreaterThan(postInstall);
expect(codeStage).toBeGreaterThan(mkdirGuard);
expect(rmNodeModules).toBeGreaterThan(codeStage);
}
);
});
28 changes: 24 additions & 4 deletions packages/cli-v3/src/deploy/buildImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,14 @@ COPY --chown=bun:bun . .

${postInstallCommands}

# node_modules may not exist when there are no dependencies to install
RUN mkdir -p node_modules

FROM build AS code

# u+rwX first: non-root rm fails on read-only or non-traversable directories
RUN chmod -R u+rwX node_modules && rm -rf node_modules

FROM build AS indexer

USER bun
Expand Down Expand Up @@ -831,8 +839,10 @@ ENV TRIGGER_PROJECT_ID=\${TRIGGER_PROJECT_ID} \
NODE_EXTRA_CA_CERTS=\${NODE_EXTRA_CA_CERTS} \
NODE_ENV=production

# Copy the files from the build stage
COPY --from=build --chown=bun:bun /app ./
# Unchanged dependencies produce an identical layer that repeat deploys skip
COPY --from=build --chown=bun:bun /app/node_modules ./node_modules

COPY --from=code --chown=bun:bun /app ./

# Copy the index.json file from the indexer stage
COPY --from=indexer --chown=bun:bun /app/index.json ./
Expand Down Expand Up @@ -888,6 +898,14 @@ ${postInstallCommands}
# IMPORTANT: Doing this again to fix an issue with prisma generate removing the files in node_modules/trigger.dev for some reason...
COPY --chown=node:node . .

# node_modules may not exist when there are no dependencies to install
RUN mkdir -p node_modules

FROM build AS code

# u+rwX first: non-root rm fails on read-only or non-traversable directories
RUN chmod -R u+rwX node_modules && rm -rf node_modules

FROM build AS indexer

USER node
Expand Down Expand Up @@ -941,8 +959,10 @@ ENV TRIGGER_PROJECT_ID=\${TRIGGER_PROJECT_ID} \
NODE_EXTRA_CA_CERTS=\${NODE_EXTRA_CA_CERTS} \
NODE_ENV=production

# Copy the files from the install stage
COPY --from=build --chown=node:node /app ./
# Unchanged dependencies produce an identical layer that repeat deploys skip
COPY --from=build --chown=node:node /app/node_modules ./node_modules
Comment thread
myftija marked this conversation as resolved.

COPY --from=code --chown=node:node /app ./

# Copy the index.json file from the indexer stage
COPY --from=indexer --chown=node:node /app/index.json ./
Expand Down
Loading