docs: ship deno deps to runtime stage in multi-stage Docker example#3120
Closed
lunadogbot wants to merge 2 commits into
Closed
docs: ship deno deps to runtime stage in multi-stage Docker example#3120lunadogbot wants to merge 2 commits into
lunadogbot wants to merge 2 commits into
Conversation
The multi-stage Dockerfile example ran `deno install --entrypoint main.ts` in the build stage, but `deno install` populates Deno's global cache ($DENO_DIR), not the project directory. With only `COPY --from=builder /app .` in the runtime stage, the cached deps never made it across and the container re-downloaded them on first run. Point $DENO_DIR at /deno-dir in both stages and add a second COPY so the populated cache rides along with the app. Verified by building against denoland/deno:latest and running with `docker run --network none …`: the container now starts and prints output without re-fetching. Closes #2935
fibibot
reviewed
May 14, 2026
Contributor
fibibot
left a comment
There was a problem hiding this comment.
Two things to flag before this merges:
- PR #3030 (already APPROVED, same
Closes #2935) is the same fix from another angle — copies/deno-dirfrom the builder and adds the explanatory paragraph. It also rearranges the single-stage example to cache deps as a separate layer, which this PR doesn't do. Likely a coordination/duplicate situation worth resolving before either lands. - The new
ENV DENO_DIR=/deno-dirlines in both stages are redundant — the officialdenoland/denoimage already setsDENO_DIR=/deno-dir/, which is what makes the existing "Environment Variables" section at line 124 of this same file work. TheCOPY --from=builder /deno-dir /deno-diralone is sufficient. Not wrong, just extra.
- nit: substantive content change but
last_modified: 2025-12-16isn't bumped (style guide says it should be). - nit:
/deno-dir(no trailing slash) vs the existing/deno-dir/on line 124 — pick one for consistency.
Holding any verdict until lint and link check is green. The current failure is the same lume/jsx-runtime build error affecting other open PRs, not anything in this diff.
Contributor
Author
|
Closing in favor of #3030, which:
Deferring to #3030 to avoid duplicate work. Thanks @fibibot for catching the overlap. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The multi-stage Dockerfile example on
/runtime/reference/docker/runsdeno install --entrypoint main.tsin the build stage, butdeno installpopulates Deno's global cache (
$DENO_DIR), not the project directory.The runtime stage only does
COPY --from=builder /app ., so the cacheddeps never reach it and the container re-downloads them on first run
(which also breaks any deployment that runs with
--network noneorwithout outbound internet access).
This PR points
$DENO_DIRat/deno-dirin both stages and adds asecond
COPY --from=builder /deno-dir /deno-dirso the populated cacheships with the image. A short paragraph below the snippet explains the
why.
The single-stage snippet at the top of the page wasn't affected — it
never moves the app between stages, so the existing
deno installalready lives next to the runtime.
Why this approach over
--vendorThe reporter suggested
deno install --vendor. I tried it and confirmedthat the
--vendorflag alone is not sufficient: it does write depsinto
/app/vendor/, but Deno only uses that folder at runtime whenthe project has
"vendor": truein itsdeno.json(or an equivalentconfig). For a generic example that may not have a
deno.jsonyet, the$DENO_DIRroute is more reliable — it doesn't depend on the useradding any config and works for both jsr/https/npm specifiers
transparently.
Verification
Built the updated Dockerfile against
denoland/deno:latestwith a tinymain.tsthat imports a remote module from JSR, then ran the resultingimage with
docker run --network none …:main.ts:Build output (excerpt):
Offline run:
For comparison, the same
main.tsagainst the previous (unfixed) snippetfails offline:
Test plan
--network none→ re-download → fail)denoland/deno:latest(Deno 2.7.14)--network noneand confirmed the app starts and serves without re-fetchingCloses #2935
Closes bartlomieju/orchid-inbox#65