Single-binary / distroless deployments: document `load()` or ship a glibc build
Maintainers usually reply within 1 day
@theoephraim is already working on this.
Since Sep 7, 2026.
Assessment
This issue has not been assessed yet.
Description
I'm deploying a Bun app with Elysiajs and compiled with bun build --compile on
gcr.io/distroless/base-debian13:nonroot
(glibc, no shell, no node_modules). I wanted varlock to validate the env
inside the container at boot, and ran into three things.
1. The ghcr binary is musl-only
ghcr.io/dmno-dev/varlock:1.18.0
ships a binary dynamically linked against musl and libstdc++:
$ ldd /usr/local/bin/varlock
/lib/ld-musl-x86_64.so.1
libstdc++.so.6 => /usr/lib/libstdc++.so.6
libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1
Copied into distroless base-debian13 it fails before starting:
exec /varlock: no such file or directory
On a bare alpine:3.20 it fails on libstdc++.so.6 / libgcc_s.so.1.
So it only runs on Alpine with libstdc++ installed. The Docker integration
guide presents COPY --from=varlock /usr/local/bin/varlock as usable in
distroless images without saying that.
2. The documented paths for compiled executables need the CLI as a process
Both varlock/auto-load and varlock run -- shell out to the CLI
(auto-load walks up the tree looking for node_modules/.bin/varlock).
Since the ghcr binary can't run here, my first attempt was to compile the
CLI from node_modules for glibc in the builder stage and use it as the
entrypoint:
FROM oven/bun:1.4.2-slim AS builder
# ... bun ci, COPY src, .env.schema ...
RUN bun build --compile --minify --target bun-linux-x64 src/index.ts --outfile dist/server \
&& bun build --compile --minify --target bun-linux-x64 node_modules/varlock/bin/cli.js --outfile dist/varlock
FROM gcr.io/distroless/base-debian13:nonroot
WORKDIR /app
COPY --from=builder /app/dist/server /app/dist/varlock ./
COPY --from=builder /app/.env.schema ./.env.schema
ENTRYPOINT ["/app/varlock", "run", "--"]
CMD ["/app/server"]
This works: validation runs at boot, missing vars are reported and the
container exits 1. But every bun build --compile embeds the whole Bun
runtime, so the image goes from 158 MB to 278 MB (server 82 MB + varlock
82 MB) just to validate a .env.schema.
3. What I ended up doing
The varlock package exports load(), which is what auto-load does
minus the execSync. Using it as the entry of the compiled binary gives a
single process and a single binary:
// src/index.ts, the bun build --compile entry
import { load } from "varlock";
try {
await load();
} catch (error) {
const formatted =
error instanceof Error && "getFormattedOutput" in error
? (error as { getFormattedOutput: () => string }).getFormattedOutput()
: String(error);
process.stderr.write(`${formatted}\n`);
process.exit(1);
}
await import("@/modules/app"); // the Elysia app, which imports { ENV } from "varlock/env"
FROM gcr.io/distroless/base-debian13:nonroot
WORKDIR /app
COPY --from=builder /app/dist/server ./server
COPY --from=builder /app/.env.schema ./.env.schema
ENV NODE_ENV=production
ENTRYPOINT ["/app/server"]
Image is back to 158 MB, the server is PID 1, ENV is typed and coerced
from @generateTsTypes, and a bad env prints the usual varlock report and
exits 1. For bun run dev / bun test I keep preload = ["varlock/auto-load"]
in bunfig.toml and point those scripts at the app module, not the entry.
The catch is that load() appears in index.d.mts but not in the docs,
so I don't know whether it's meant to be stable.
Would you consider documenting load() as the recommended path for
single-binary / distroless deployments, and either a glibc or static build
of the CLI, or a note in the Docker guide that the ghcr binary is
Alpine-only?
varlock 1.18.0, Bun 1.4.2, linux/amd64.
- Dominant language
- TypeScript
- Stars
- 4.9k
- Forks
- 140
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 35
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dmno-dev/varlock
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
dmno-dev/varlock#1279 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 69/100
dmno-dev/varlock#1100 · 3 comments ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 78/100
dmno-dev/varlock#1278 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 72/100
dmno-dev/varlock#1275 · 1 comment ·
Maintainers usually reply within 1 day
-
core:varlock RFC
Difficulty 5/5 Over a week Newbie friendliness 28/100
dmno-dev/varlock#1258 · 1 comment ·
Maintainers usually reply within 1 day
All issues in dmno-dev/varlock
Similar issues
-
documentation good first issue roadmap
Difficulty 1/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
resend/react-email#3876 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
siyuan-note/siyuan#21324 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
himynameisdave/bsky-profanity-counter#59 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100