Varlock: stop treating your .env files like the wild west
Every project has one. A .env file with 30 variables, a .env.example that’s three months out of date, and at least one developer who had to ask in Slack “hey, what’s the value for MAIL_HOST again?” — probably over a channel that’s not private. We’ve all been here, and we’ve all accepted it as just part of the workflow. Varlock is the tool that says it doesn’t have to be.
What is Varlock?
Varlock is an open-source, language-agnostic environment variable management tool built on top of the @env-spec specification. Written in TypeScript but usable in any project, it sits on top of your existing .env files and adds a schema layer via a .env.schema file that you actually commit to your repo.
That schema file becomes the single source of truth for your entire configuration. It defines variable names, types, validation rules, default values, and whether something is sensitive. From that, Varlock can validate your environment at startup, generate TypeScript types for IDE autocompletion, pull secrets from external providers at runtime, and prevent secrets from leaking into logs or AI context windows.
It supports integrations for Next.js, Vite, Astro, and anything else via varlock run. You can install it as a project dependency or as a standalone binary.
# As a project dependency
npx varlock init
# As a standalone binary
brew install dmno-dev/tap/varlock
# Or via cURL
curl -sSfL https://varlock.dev/install.sh | sh -s
The problems it solves
1. .env.example is always a lie
The standard pattern — commit .env.example, gitignore .env, hope everyone keeps them in sync — breaks constantly. Someone adds a new variable, forgets to update the example, and the next developer to clone the repo spends 20 minutes debugging a null pointer before realising they’re missing an env var entirely.
With Varlock, the .env.schema file replaces .env.example. It’s safe to commit because it contains no actual values, only the shape and rules. And because it’s machine-readable, Varlock can validate your running environment against it — surfacing missing or invalid variables at startup, not buried in a runtime stack trace.
2. Secrets sitting as plaintext on disk
A standard .env file with your AWS keys, database password, and Stripe secret key is just a text file sitting in your home directory. If your machine is compromised, or if you accidentally paste the wrong thing somewhere, those credentials are gone.
Varlock integrates with secret managers — 1Password, AWS Secrets Manager, HashiCorp Vault, Azure Key Vault — and resolves values at runtime instead of storing them locally. You can also use the exec() resolver to call any CLI tool:
# .env.schema
# @sensitive @required
DATABASE_URL=exec(`op read "op://Work/myapp-db/url"`)
# @sensitive @required
STRIPE_SECRET_KEY=exec(`op read "op://Work/stripe/secret_key"`)
Nothing lives in plaintext. Your .env file can contain only non-sensitive values for local overrides.
3. Config errors caught too late
Without validation, a misconfigured environment usually fails at the point where the variable is first used — deep inside a request handler, during a background job, or worse, in production after a deploy. Varlock validates the entire environment on load, so you get one clear error message early, not a cryptic failure later.
varlock load
# ✖ DATABASE_URL — required value is missing
# ✖ MAIL_PORT — expected number, got "smtp.mailhog.test"
# 2 errors found. Fix them before continuing.
4. No type safety on env vars
process.env.PORT is always a string, even if you expect a number. Type coercion bugs are subtle and annoying. Varlock generates TypeScript types from your schema so your IDE knows what type each variable is and can autocomplete variable names — with no manual type declaration files to maintain.
Why you should care now: the AI angle
This is the part that makes Varlock’s timing relevant. As AI coding assistants — Cursor, Claude Code, Copilot — become a normal part of development workflows, they create a new attack surface: your secrets ending up in context windows.
If your agent reads your .env file to help debug something, your API keys just got sent to an external API. Varlock’s .env.schema is designed to give AI agents the context they need — variable names, types, descriptions, validation rules — without exposing any actual values. Combined with varlock scan, which detects leaked secrets in code (including AI-generated code), it’s explicitly built for workflows where AI is in the loop.
# Pre-commit hook to catch leaked secrets in AI-generated code
varlock scan
This is forward-thinking tooling for a real problem that most teams haven’t addressed yet.
Example usage
Basic schema definition
# .env.schema
# The base URL for the application
# @required @type(url)
APP_URL=https://localhost:3000
# @required @sensitive
DATABASE_URL=
# @required @type(number) @default(3000)
PORT=
# @required @type(enum(development,staging,production)) @default(development)
APP_ENV=
# @sensitive @required
MAIL_PASSWORD=
Run varlock load and it validates all of the above before your app starts.
Multi-environment with local overrides
.env.schema # committed — shapes and rules
.env # gitignored — base values
.env.local # gitignored — local developer overrides
.env.production # gitignored or CI-injected — prod values
Varlock resolves values in precedence order: process.env → .env.local → .env.[environment] → .env → schema defaults. No custom loading logic needed.
Wrapping a command for environment injection
If your project isn’t using a framework integration, you can wrap any command with varlock run to inject validated variables into the process:
# Inject env into a Laravel artisan command
varlock run -- php artisan migrate
# Or a Docker build
varlock run -- docker compose up
# Or any script
varlock run -- node scripts/seed.js
The variables are available to the child process but never printed to the terminal.
GitHub Actions integration
# .github/workflows/deploy.yml
steps:
- uses: actions/checkout@v4
- name: Load environment variables
uses: dmno-dev/[email protected]
with:
fail-on-error: 'true'
show-summary: 'true'
- name: Run tests
run: npm test
Variables marked @sensitive in the schema are automatically masked in the Actions log. You catch missing config before the deploy, not after.
Is it worth adopting now?
It’s early tooling. The ecosystem is still growing, and if you’re not on a JS/TS-first stack, some of the integrations won’t apply directly. But the core pattern — commit a .env.schema, validate on load, pull secrets from a manager — works for any project via varlock run, and the CLI can be installed standalone without touching your existing stack.
The .env.example approach has been good enough for years, but it has real failure modes that compound as teams grow, environments multiply, and AI agents start touching your codebase. Varlock solves those problems without requiring a massive tooling migration.
The best time to set it up is on a new project before the configuration grows unwieldy. The second best time is now.
More dev notes at noukeosombath.com