Elysia
Elysia needs no integration package. Import varlock/auto-load and read values through the typed ENV proxy, the same as any other JavaScript project.
The part worth reading is deploying. Elysia has no build step that inlines env values, so unlike Next.js or Vite there is nothing to bake your config into. varlock freeze fills that gap.
Check out the Elysia example project for a working reference.
-
Install varlock
Terminal window npm install varlockTerminal window pnpm add varlockTerminal window bun add varlockTerminal window yarn add varlockTerminal window vlt install varlock -
Run
varlock initto set up your.env.schemaTerminal window npm exec -- varlock initTerminal window pnpm exec -- varlock initTerminal window bunx varlock initTerminal window vlx -- varlock initTerminal window yarn exec -- varlock init -
Turn off Bun’s own
.envloadingBun loads
.envfiles itself based onNODE_ENV/BUN_ENV, which feeds values into varlock behind its back. Let varlock own env loading:bunfig.toml env = falseSee the Bun integration docs for the other ways to disable it, including for compiled binaries.
-
Import
varlock/auto-loadfirst in your entrypointsrc/server.ts import 'varlock/auto-load';import { ENV } from 'varlock/env';import { Elysia } from 'elysia';new Elysia().get('/', () => ({ message: ENV.PUBLIC_MESSAGE })).listen(ENV.PORT);The import must come first, so env is loaded and validated before anything else runs. If you would rather keep it out of app code, drop the import and launch with
varlock run -- bun src/server.tsinstead.
Leak prevention
Section titled “Leak prevention”Because varlock knows which items are @sensitive, both protections work in Elysia with no extra setup:
.get('/log-demo', () => { console.log('SOME_API_KEY =', ENV.SOME_API_KEY); // logged redacted return 'ok';})
.get('/leak-demo', () => { return { oops: ENV.SOME_API_KEY }; // blocked, request fails with a 500})Elysia builds responses with the standard Response API, which varlock patches, so a sensitive value returned from a handler is caught before it reaches the client. See leak prevention.
Deploying
Section titled “Deploying”Locally, varlock resolves your .env files on every boot. In a deploy that is usually the wrong default: it means config lives in your platform’s settings rather than in the release, so it can’t change atomically with your code and doesn’t roll back with it. It also makes every boot depend on your secret backend being reachable.
varlock freeze resolves everything once at deploy time and writes an encrypted file that ships inside your deploy artifact. Your app boots from that file.
-
Generate a key, once
Terminal window npm exec -- varlock generate-keyTerminal window pnpm exec -- varlock generate-keyTerminal window bunx varlock generate-keyTerminal window vlx -- varlock generate-keyTerminal window yarn exec -- varlock generate-keySet the result as
_VARLOCK_ENV_KEYin your deploy pipeline and your runtime environment, and set_VARLOCK_USE_FROZEN_ENV=1in the runtime environment next to it so the app boots from the frozen file. -
Bundle and freeze at deploy time
Terminal window bun build ./src/server.ts --target=bun --outdir distAPP_ENV=production varlock freezeRun
freezewhere your.envfiles and secret backends are reachable, usually a CI job. It writes.varlock-frozen-envand prints which environment it captured. -
Ship the frozen file with your code
Dockerfile FROM oven/bun:1WORKDIR /appCOPY dist/server.js bunfig.toml ./COPY .varlock-frozen-env ./ENV _VARLOCK_USE_FROZEN_ENV=1CMD ["bun", "server.js"]Copy it into the image rather than mounting it at runtime. Mounting it separately puts config back outside the release, which is the thing this avoids.
-
Boot normally
Terminal window bun server.js
That image needs nothing else: no .env files, no node_modules, and no varlock CLI. Values, coerced types, and sensitivity all travel inside the frozen file, so ENV.PORT is still a number and log redaction still works.
If your platform assigns PORT to each instance, mark it @dynamic=boot in your schema. Otherwise the frozen value wins over the one the platform sets.
The file is only read when _VARLOCK_USE_FROZEN_ENV is set, so a missing frozen file is a hard error instead of a silent fall back to normal resolution, and a leftover file in your dev checkout changes nothing.
Standalone binaries
Section titled “Standalone binaries”bun build --compile works the same way. With _VARLOCK_USE_FROZEN_ENV=1 the binary reads .varlock-frozen-env from its working directory, so ship the two together:
bun build ./src/server.ts --compile --outfile server --no-compile-autoload-dotenvAPP_ENV=production varlock freeze--no-compile-autoload-dotenv stops the compiled binary from doing Bun’s own .env loading, matching the bunfig.toml setting above.
import 'varlock/auto-load' is what the binary needs, and it bundles cleanly. It never shells out to the varlock CLI when it has a frozen file or a trusted blob to boot from, so a distroless image with no shell and no node_modules works. Keep the import first in your entrypoint.
Passing the payload as an env var
Section titled “Passing the payload as an env var”If you cannot get a file into the image (a Compose file that pulls a tag it does not rebuild, for instance), varlock freeze --out - writes the same payload to stdout and you carry it in one variable:
export __VARLOCK_ENV=$(APP_ENV=production varlock freeze --out -)services: app: environment: __VARLOCK_ENV: ${__VARLOCK_ENV:?run `varlock freeze --out -` first} _VARLOCK_USE_INJECTED_ENV: "1" _VARLOCK_ENV_KEY: ${_VARLOCK_ENV_KEY:?}_VARLOCK_USE_INJECTED_ENV=1 is required: without it varlock tries to verify the payload against .env files the image does not have, and falls back to running the CLI. See carrying the payload in an env var for what this trades away versus shipping a file.
Choosing an environment
Section titled “Choosing an environment”The environment comes from whatever your schema’s @currentEnv reads, so set that when you freeze. The summary names the environment it captured, since freezing the wrong one is the easiest mistake to make and the hardest to notice:
APP_ENV=production varlock freezeOther options
Section titled “Other options”freeze is not the only way to deploy. If the varlock CLI, your .env files, and your resolver credentials are all available in the runtime environment, varlock run -- bun server.js works too and re-resolves on every boot. See related approaches for the comparison.