Varlock: schema-driven, type-safe environment variable management
Varlock is an open-source tool that replaces the .env file with a schema-first approach to environment variable management. Instead of storing secrets as plaintext on disk, it defines the shape and rules of configuration in a .env.schema file that is safe to commit to version control, then resolves actual values at runtime, either from defaults or from external secret managers like 1Password, AWS Secrets Manager, or Azure Key Vault.
Problems with traditional .env files
The standard .env workflow has several well-known failure modes. New developers cloning a repository need to track down a copy of the file, typically over a chat message or email, because the file is excluded from version control. An .env.example file helps, but it's a manually maintained document that frequently falls out of sync with what the application actually needs.
More critically, .env files store API keys, database credentials, and tokens as plaintext on disk. A single accidental git add can expose secrets in the repository's history. A compromised developer machine gives an attacker direct access to every secret in every project.
A newer concern is AI coding assistants. Tools like GitHub Copilot and Cursor read open files for context. A .env file open in the editor is visible to these tools, and its contents can end up in suggestions, logs, or training data. Varlock addresses this by design: the .env.schema file that developers work with directly contains no sensitive values.
The .env.schema file
The .env.schema file is the single source of truth for a project's configuration. It defines variable names, types, defaults, and validation rules using a DSL called @env-spec, which extends standard .env syntax with special decorator comments.
Because it contains no secrets, .env.schema is committed to version control. Developers cloning a repository immediately have a complete, authoritative description of what configuration the application requires.
Installation and initialization
Install Varlock as a development dependency:
Initialize it in the project root:
This scans for any existing .env or .env.example files and generates an initial .env.schema. If neither is found, it creates an empty schema file.
Defining variables and using decorators
A basic variable definition looks identical to a standard .env entry:
Running varlock load against this schema produces:
Varlock treats every variable as sensitive by default. The value is hidden even though it appears plainly in the schema file. This "secure by default" behavior prevents accidental exposure when running validation commands.
Root decorators
Root decorators appear at the top of the file and apply globally. The @defaultSensitive=false decorator changes the default so variables are visible unless explicitly marked sensitive. The # --- line conventionally separates the decorator block from variable definitions.
Running varlock load now shows the value:
Item decorators
Item decorators apply to the variable immediately below them. They add type enforcement, required validation, and sensitivity flags:
@type=string enforces that the value is a string. @required ensures the variable has a value. @sensitive overrides the global @defaultSensitive=false for this variable. @type=port validates that the value is a valid network port number.
Type validation
If a variable's value does not match its declared type, varlock load produces an immediate error rather than letting the mismatch surface at runtime:
This catches misconfigurations during development rather than in production.
Runtime injection with varlock run
varlock run wraps any command, resolves all variables from .env.schema, and injects them as standard environment variables into the spawned process. The application reads them through normal means like process.env.PORT and has no dependency on Varlock itself.
Varlock resolves PORT from the schema and injects it before Vite starts:
The -- separator distinguishes Varlock's own arguments from the arguments of the command being wrapped.
Integrating with secret managers
Fetching secrets from an external manager at runtime eliminates plaintext values from the local filesystem entirely. The 1Password integration illustrates how this works across all supported providers.
1Password setup
Create a service account in your 1Password account and grant it access only to the specific vault containing the secrets this project needs, not to personal or other sensitive vaults. The service account token is what Varlock uses to authenticate.
Install the 1Password plugin:
Configuring the plugin in .env.schema
@plugin(@varlock/1password-plugin) loads the plugin. @initOp(token=$OP_TOKEN) initializes it using the value of OP_TOKEN, referenced with $ syntax. @type=opServiceAccountToken is a type provided by the plugin that marks the token as sensitive and validates its format.
Fetching a secret
With the plugin configured, secrets are referenced using a URI function call syntax. For a secret stored in a vault named "Test", in an item named "openai", with a field named "credential":
Running varlock load fetches the value from 1Password and confirms it resolved correctly:
The actual key value never exists on disk. It is fetched at runtime and injected directly into the process environment.
Additional features
Automatic type generation: The @generateTypes(lang=ts, path='./env.d.ts') root decorator generates a TypeScript declaration file from the schema, providing autocompletion and type safety for process.env across the codebase.
Runtime leak prevention: The @redactLogs and @preventLeaks decorators automatically redact sensitive values from console.log output and scan outgoing HTTP responses for secrets.
Modular configuration: The @import decorator splits configuration across multiple schema files. Imports can be conditional based on the current environment, which is useful for separating base configuration from environment-specific overrides.
Ecosystem tooling: An official VS Code extension provides syntax highlighting and autocompletion for @env-spec. A GitHub Actions integration validates the schema on every push, catching missing or invalid configuration before it reaches production.
Practical considerations
Fetching secrets from cloud providers requires an internet connection. Offline development needs a fallback strategy, typically a separate local schema file with safe placeholder values.
There is a small startup latency when Varlock makes network requests to resolve secrets. For most applications this is a few seconds and only occurs at process start, but it is a tradeoff worth accounting for in time-sensitive startup paths.
Varlock injects variables into the process environment, but variables already set in the shell environment may take precedence depending on the operating system and how the process is launched. Keeping the local shell free of project-specific variables and letting Varlock manage them entirely avoids precedence conflicts.
Final thoughts
Varlock's @env-spec DSL is the key design decision that makes its approach work. By making the schema safe to commit, it solves the onboarding and synchronization problems of traditional .env files without requiring a shared secrets database or manual documentation. The secret manager integrations go further, removing plaintext values from local development entirely.
The tool is most valuable on projects where multiple developers share configuration, secrets rotate regularly, or compliance requirements mandate that credentials never touch developer machines. The Varlock documentation covers the full decorator reference and all supported provider integrations.