Skip to content

Encrypted deployments

When deploying SSR applications to serverless platforms (like Vercel or Netlify) that don’t give you control over how the application boots, varlock bakes the fully resolved env into your server-side build output so it’s available at runtime without the CLI or filesystem access: the Next.js integration freezes it into the build, and the Vite-based integrations inject it with ssrInjectMode: 'resolved-env'. Cloudflare Workers is different: varlock-wrangler deploy uploads the blob as a runtime secret binding instead of baking it into the build output, so it never appears in a build artifact or sourcemap in the first place. See the Cloudflare Workers section below.

Encrypting the blob protects your secrets if the build output is exposed, for example via sourcemaps uploaded to an error tracking service.

  • Next.js always encrypts an env it freezes into the build, so all you need to do is set the key. The build fails until you do (or until you opt out with _VARLOCK_ALLOW_PLAINTEXT_FROZEN_ENV=1).
  • Vite-based integrations inject a plaintext blob by default. Enable encryption with the decorator below, then set the key.

For the Vite-based integrations, add the @encryptInjectedEnv root decorator to your .env.schema:

.env.schema
# @encryptInjectedEnv
# ---
SECRET_KEY= # @sensitive

You can also enable it conditionally for specific environments:

.env.schema
# @encryptInjectedEnv=forEnv(prod)
# ---
SECRET_KEY= # @sensitive
  1. At build time, varlock encrypts the serialized env graph with AES-256-GCM before injecting it
  2. The encrypted blob is prefixed with varlock:v1:, so you can verify encryption by inspecting your build output
  3. At runtime, initVarlockEnv() detects the encrypted prefix and decrypts using _VARLOCK_ENV_KEY from the runtime environment
  4. The key is never baked into the build. It must always come from the runtime environment

Decryption uses node:crypto where available; edge runtimes without it (e.g. Vercel Edge middleware) automatically fall back to asynchronous Web Crypto, with request handling gated until the env is ready.

The encryption key (_VARLOCK_ENV_KEY) is managed differently depending on your platform:

Nothing to set up. When @encryptInjectedEnv is enabled, the Vite plugin mints a temporary key in an early dev-server config hook. Frameworks that run the SSR dev runtime in a separate worker or process (Nitro and TanStack Start, Nuxt, vitest) spawn it later with a copy of the environment, so they inherit the key and the blob is encrypted in dev the same way it is in production. The key lives only in memory for that dev session and is never created for builds. If you export _VARLOCK_ENV_KEY yourself before starting the dev server, that key is used instead. vite preview never mints a key: it serves a finished build, so it needs the same real key the build used.

On Cloudflare targets, the dev blob is always plaintext: the workerd dev runtime reads process.env from bindings, so no host env var can reach it. Dev output is never deployed, so this is safe. If you use another runtime that cannot see host env vars, scope the decorator to production with @encryptInjectedEnv=forEnv(prod).

No setup needed. varlock-wrangler deploy auto-generates a key and uploads it alongside your encrypted env as a Cloudflare secret binding. The key persists across deploys.

You must set _VARLOCK_ENV_KEY as an environment variable on your platform. The key must be available at both build time (for encryption) and runtime (for decryption). It must be a real environment variable in both places: it’s a reserved _VARLOCK_* name that varlock never populates from .env files, and it can’t be baked in via vite’s define (the value has to come from the actual process environment at runtime, not a string substituted at build time).

  1. Generate and set the key

    Terminal window
    # set one key for preview
    varlock generate-key --plain | vercel env add _VARLOCK_ENV_KEY preview --sensitive
    # set one key for production
    varlock generate-key --plain | vercel env add _VARLOCK_ENV_KEY production --sensitive

    You can use the same key for both preview and production, but using separate keys is usually safer because it isolates environments and reduces blast radius if one key is exposed.

    Set it as a project environment variable like this (or in the dashboard). Vercel env vars apply to both builds and runtime by default. Don’t pass it with vercel deploy --env or --build-env: those reject names that start with _.

  2. Deploy your app

    The build fails with a clear error if _VARLOCK_ENV_KEY is not set when encryption is required: a Next.js build that freezes the env, or @encryptInjectedEnv with a Vite-based integration. If the key is missing at runtime, the server fails at boot rather than serving.

IntegrationHow it works
Next.jsEncrypts the env frozen into the build output. Always on: a frozen build fails without _VARLOCK_ENV_KEY, with or without the decorator
ViteEncrypts the env blob when using ssrInjectMode: 'resolved-env'
AstroUses the Vite integration; encryption applies to SSR adapter builds
SvelteKitUses the Vite integration; encryption applies to SSR builds
TanStack StartUses the Vite integration; encryption applies to SSR builds
Cloudflare WorkersEncrypts the __VARLOCK_ENV secret binding uploaded at deploy time
ExpoEncrypts the env blob used by the Metro bundler for server routes

After building with encryption enabled, you can inspect your build output to confirm the blob is encrypted:

Terminal window
# Look for the encrypted prefix in build output
grep -r 'varlock:v1:' dist/ .next/server/ 2>/dev/null

If encryption is working, you’ll see varlock:v1: followed by a base64-encoded ciphertext instead of raw JSON.

Alternative: manual key without the decorator

Section titled “Alternative: manual key without the decorator”

You can also enable encryption without the @encryptInjectedEnv decorator by setting _VARLOCK_ENV_KEY in your environment. When present, varlock will encrypt the blob, but it won’t enforce the key’s presence or auto-generate it.