PushEnv turns your
.envinto a typed, validated, auto-documented configuration system.
Drop-in dotenv replacement with Zod validation, automatic TypeScript types, and encrypted team sync. Use as a library for local dev, CLI for team collaboration.
require('dotenv').config();
const port = process.env.PORT; // string | undefined β οΈ
const dbUrl = process.env.DB_URL; // Could be missing! π₯// config/env.ts - Define once
import { validateEnv, z } from 'pushenv';
export const env = validateEnv({
schema: z.object({
PORT: z.coerce.number(),
DB_URL: z.string().url(),
})
});
// server.ts - Use everywhere with full types!
import { env } from './config/env';
env.PORT; // number β Fully typed!
env.DB_URL; // string β Validated URL!One config file β full type safety everywhere. Catch errors at startup, not in production.
- π Drop-in replacement for
dotenvwith better features - β Built-in Zod validation β catch config errors at startup
- π¨ TypeScript-first with full type safety
- π§ Auto TypeScript type generation β no manual
.d.tsfiles - π Zero dependencies on external services
- π Zero-knowledge encryption β We can't see your secrets, only you can decrypt them
- βοΈ PushEnv's managed cloud included β No setup, no AWS/S3 config needed
- π Version control for your environment variables
- π End-to-end encrypted β Encrypted on your machine before upload
- π― No accounts required β just install and go
Why PushEnv? Get the power of Doppler/Vault without SaaS lock-in. Your secrets are encrypted client-side with your passphrase - we never see them in plaintext!
| Feature | dotenv | PushEnv |
|---|---|---|
.env loading |
β | β |
| Zod validation | β | β |
| TypeScript type generation | β | β |
| Catch missing vars at startup | β | β |
Type-safe process.env |
β | β |
| CLI for team sync | β | β |
| PushEnv's managed cloud (zero-knowledge) | β | β |
| Encrypted cloud backup | β | β |
| Version control | β | β |
Auto .gitignore |
β | β |
| Zero config | β | β |
| Use Case | Library | CLI |
|---|---|---|
Load .env files locally |
β | β |
| Validate env vars with schema | β | β |
| Type-safe environment config | β | β |
| Generate TypeScript types | β | β |
| Sync secrets across team | β | β |
| Encrypted cloud backup | β | β |
| Version control for secrets | β | β |
| CI/CD secret injection | β Both | β |
Pro tip: Use library for local dev, CLI for team sync! They work great together.
- π Drop-in dotenv replacement β use
pushenv.config()instead ofdotenv.config() - β Zod validation β validate env vars with schemas, catch errors at startup
- π¨ Full TypeScript support β get type-safe environment variables
- π§ Auto TypeScript type generation β generate
.d.tsfiles from Zod schemas - π Compatible API β supports
path,override,debugoptions like dotenv - π« Better error messages β clear validation errors with helpful suggestions
- π AES-256-GCM end-to-end encryption β secrets encrypted before leaving your machine
- π PBKDF2 passphrase-derived keys β passphrase never stored, only derived key
- π Secrets never sent in plaintext β encrypted end-to-end
- π₯ One-time passphrase per machine β enter once, key stored securely
- π» Per-device keyring β
~/.pushenv/keys.json(private, never commit)
- π² Multi-environment support β manage
development,staging,productionseparately - β Add stages on-the-fly β add new environments without reinitializing (
add-stagecommand) - π‘οΈ Smart file naming β automatic
.env.{stage}suggestions prevent accidental secret mixing - π Stage overview β list all configured stages and their status
- π Built-in version history β every push creates a new, timestamped version with an optional message (like Git for your
.env) - π Diff any version β compare your local
.envwith the latest remote or with a specific historical version before you pull or roll back - βͺ Safe rollbacks β restore any previous version of a stage with a single command (with extra guardrails for production)
- π Version messages β annotate each push with custom messages for better tracking
- π Zero-file execution β run commands with secrets injected directly into memory, no
.envfiles ever written to disk - π Example file generation β create safe
.env.examplefiles with placeholders for version control - βοΈ PushEnv's managed cloud β built-in storage, no AWS/S3 setup needed
- π Zero-knowledge encryption β your passphrase never leaves your machine, we can't decrypt your secrets
- π Per-project configuration β
.pushenv/config.json(safe to commit) - π Fully open-source, no vendor lock-in
npm install pushenv # Local (library)
npm install -g pushenv # Global (CLI)Note: Zod is included - just import { z } from 'pushenv'
npm uninstall dotenv
npm install pushenvThen change your code:
- import dotenv from 'dotenv';
- dotenv.config();
+ import pushenv from 'pushenv';
+ pushenv.config();Or use named imports:
- require('dotenv').config();
+ import { config } from 'pushenv';
+ config();That's it! Your existing code works as-is. Now you can optionally add validation:
import { validateEnv, z } from 'pushenv';
export const env = validateEnv({
schema: z.object({
PORT: z.coerce.number(),
DATABASE_URL: z.string().url(),
})
});Want type-safe, validated envs? Use validateEnv() - it does everything in one call.
Create a central config file:
// src/config/env.ts
import { validateEnv, z } from 'pushenv';
export const env = validateEnv({
schema: z.object({
// Server
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
// Database
DATABASE_URL: z.string().url(),
DB_POOL_SIZE: z.coerce.number().default(10),
// Cache & Features
REDIS_URL: z.string().url().optional(),
ENABLE_CACHE: z.coerce.boolean().default(false),
// Secrets
JWT_SECRET: z.string().min(32),
API_KEY: z.string().optional(),
// Logging
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
}),
generateTypes: process.env.NODE_ENV !== 'production',
});
// Optional: Export type for use in other files
export type Env = typeof env;Then use throughout your application:
// src/server.ts
import { env } from './config/env';
app.listen(env.PORT, () => {
console.log(`Server running on port ${env.PORT}`);
});
// src/database.ts
import { env } from './config/env';
const db = connectDB({
url: env.DATABASE_URL,
poolSize: env.DB_POOL_SIZE,
});
// src/cache.ts
import { env } from './config/env';
if (env.ENABLE_CACHE && env.REDIS_URL) {
initRedis(env.REDIS_URL);
}Benefits:
- β
env.PORTis a realnumber, not string - β Validated once at startup - crashes early if config is wrong
- β Full TypeScript autocomplete everywhere
- β Import from one place across your entire app
import { config } from 'pushenv';
config(); // Like dotenv - loads .env, no validation
config({ path: '.env.production', override: true });Non-throwing validation (warnings instead of crashes)
import { config, validate, z } from 'pushenv';
config();
const result = validate({
schema: z.object({ DATABASE_URL: z.string().url() }),
throwOnError: false,
});
if (!result.success) {
console.warn('β οΈ Using defaults');
}Separate validation steps (more control)
import { config, validateOrThrow, z } from 'pushenv';
config();
const env = validateOrThrow(z.object({
PORT: z.coerce.number(),
}));Generate types via CLI
pushenv generate-types
pushenv generate-types --env-path .env.production --output env.d.tsLoad and parse a .env file into process.env.
interface ConfigOptions {
path?: string; // .env file path (default: ".env")
override?: boolean; // override existing process.env (default: false)
debug?: boolean; // log debug info (default: false)
encoding?: string; // file encoding (default: "utf8")
schema?: z.ZodObject<any>; // Zod schema for validation
generateTypes?: boolean | Partial<GenerateTypesOptions>; // Auto-generate types
}
interface ConfigResult {
parsed?: { [key: string]: string };
error?: Error;
}Validate environment variables against a Zod schema.
interface ValidateOptions {
schema: z.ZodObject<any>;
throwOnError?: boolean; // throw or log warnings (default: true)
debug?: boolean; // show debug info (default: false)
}
interface ValidateResult {
success: boolean;
data?: any;
errors?: ValidationError[];
}Convenience function that validates and returns typed data or throws.
function validateOrThrow<T extends z.ZodObject<any>>(
schema: T
): z.infer<T>;Recommended! All-in-one function that loads .env, validates, and generates types.
interface ValidateEnvOptions<T extends z.ZodObject<any>> {
schema: T;
path?: string; // .env file path (default: ".env")
override?: boolean; // override existing process.env (default: false)
debug?: boolean; // log debug info (default: false)
generateTypes?: boolean | Partial<GenerateTypesOptions>; // Auto-generate types (default: true)
}
function validateEnv<T extends z.ZodObject<any>>(
options: ValidateEnvOptions<T>
): z.infer<T>;Perfect for:
- TypeScript projects wanting full type safety
- One-liner setup with validation + types
- Production apps that need startup validation
Generate TypeScript type definitions from a Zod schema.
interface GenerateTypesOptions {
schema: z.ZodObject<any>;
output?: string; // Output file path (default: "pushenv-env.d.ts")
addToGitignore?: boolean; // Add to .gitignore (default: true)
silent?: boolean; // Suppress console output (default: false)
}
interface GenerateTypesResult {
success: boolean;
outputPath?: string;
error?: Error;
}Features:
- Infers proper TypeScript types from Zod schemas
- Distinguishes required vs optional fields
- Supports enums, unions, literals, and more
- Automatically adds output file to
.gitignore
Use the CLI to securely sync .env files across your team with end-to-end encryption.
No setup required! PushEnv provides its own managed cloud storage. Just install and start using push/pull commands immediately.
- β Zero config β no AWS/S3 setup, works out of the box
- β No accounts β no signup, no API keys, no dashboards
- β Zero-knowledge encryption β your secrets are encrypted on your machine before upload
- π We can't decrypt your secrets β only your passphrase can decrypt them
- β End-to-end encrypted β AES-256-GCM with PBKDF2 key derivation
- Solo developers who want better secret hygiene without running another SaaS dashboard
- Small teams who just want a simple "push / pull" workflow that works across laptops and CI
- Teams who want encrypted secret storage without vendor lock-in
You can get from "zero" to "secure .env synced for the whole team" in under 5 minutes:
pushenv initYou'll choose:
- environments (dev, staging, prod)
- file paths for each env (defaults to
.env.{stage}for safety) - passphrase (team secret)
Safety feature: If you try to use plain .env for a specific stage, pushenv will:
- Warn you about the risks
- Offer to automatically rename it to
.env.{stage} - Help you avoid accidentally pushing wrong secrets to wrong environments
Creates:
.pushenv/config.json # safe to commit
~/.pushenv/keys.json # device keyring (private)
π‘ Adding stages later:
Already initialized but need to add production or staging? No problem!
pushenv add-stageThis adds new stages without losing your existing configuration or project ID.
pushenv push
pushenv push --stage staging
pushenv push --stage productionPushEnv will:
- Read your
.env - Encrypt locally
- Upload the encrypted blob to cloud
Secrets never leave your machine unencrypted.
pushenv pull
pushenv pull -s productionAfter entering passphrase once:
- AES key is derived
- Encrypted blob downloaded
- Decrypted locally only
.envfile written to your configured path
Note: PushEnv will prompt for confirmation when pushing/pulling production environments for safety.
See what's different between your local .env and the remote version before pulling or rolling back:
# Compare development (default)
pushenv diff
# Compare specific stage
pushenv diff --stage production
pushenv diff -s stagingShows:
- Added variables (in remote, not local)
- Removed variables (in local, not remote)
- Changed values (same key, different value)
- Unchanged count
Safety features:
- Verifies local file stage matches command parameter
- Warns if stage mismatch detected
- Handles files without PushEnv headers
Every pushenv push creates a new version with a timestamp and message:
# Show version history for a stage
pushenv history
pushenv history --stage production
# Push with a custom message (great for rollouts)
pushenv push -m "Add STRIPE_WEBHOOK_SECRET for billing rollout"
pushenv push --stage staging -m "Rotate JWT_SECRET"
# Diff against a specific historical version before rolling back
pushenv diff --stage production --version 3
# Roll back production to a previous version (creates a new version with rollback message)
pushenv rollback --stage production --version 3This makes it easy to:
- Track how your secrets changed across rollouts
- Safely undo a bad deploy by restoring a known-good
.env - Audit who changed what (when paired with Git history around
pushenvusage)
Create a safe example .env file with placeholder values that can be committed to Git:
# Generate example for development (default)
pushenv example
# Generate example for specific stage
pushenv example --stage production
pushenv example -s staging
# Specify custom output path
pushenv example --stage production -o .env.production.exampleWhat it does:
- Downloads and decrypts remote stage
- Replaces all secret values with placeholders
- Creates
.env.{stage}.examplefile - Safe to commit to version control
Use cases:
- Document required environment variables
- Onboard new team members
- CI/CD setup documentation
- Share variable structure without secrets
Optional feature: Run commands with secrets injected directly into process memory β no .env file written to disk.
# Run with development secrets (default)
pushenv run "npm start"
# Run with production secrets
pushenv run -s production "npm start"
pushenv run --stage production "npm start"
# Preview what would be injected (dry run)
pushenv run --dry-run -s production "npm start"
# Show variable names being injected
pushenv run -v "npm start"
pushenv run --verbose "npm start"
# Combine options
pushenv run -s production -v --dry-run "npm start"When to use:
- CI/CD pipelines where you don't want
.envfiles - Docker containers for cleaner images
- Extra-paranoid security workflows
- When you want secrets to vanish when process exits
Benefits:
- No
.envfile to accidentally commit - No residual secret files on disk
- Secrets only exist in process memory
- Perfect for production deployments
β Zero-knowledge encryption β PushEnv (and our cloud) never sees your secrets in plaintext
β Client-side encryption β Secrets encrypted on your machine before upload
β Passphrase-based β Your passphrase never leaves your machine, never stored anywhere
β AES-256-GCM β Industry-standard authenticated encryption
β PBKDF2 key derivation β Secure key generation from passphrase
β Encrypted blobs in cloud β PushEnv's cloud stores only encrypted data (we can't decrypt it!)
β Local decryption only β Secrets decrypted on your machine when you pull
β Per-device keyring β Derived keys stored in ~/.pushenv/keys.json (never commit!)
Zero-trust model: Even if PushEnv's cloud is compromised, your secrets remain encrypted. Only your passphrase can decrypt them.
project/
.env.development
.env.staging
.env.production
.pushenv/
config.json
~/.pushenv/
keys.json
| Command | Description |
|---|---|
pushenv init |
Initialize project (configure stages and passphrase) |
pushenv add-stage |
Add a new stage/environment to existing project (no reinit needed) |
pushenv push |
Encrypt & upload .env (default: development stage, creates a new version) |
pushenv push -s <stage>pushenv push --stage <stage> |
Encrypt & upload specific stage (creates a new version) |
pushenv push -m "<message>" |
Push with a custom version message |
pushenv pull |
Download & decrypt .env (default: development stage) |
pushenv pull -s <stage>pushenv pull --stage <stage> |
Download & decrypt specific stage |
pushenv run <command> |
Run command with secrets in memory (default: development stage) |
pushenv run -s <stage> <command>pushenv run --stage <stage> <command> |
Run with specific stage secrets |
pushenv run --dry-run <command> |
Preview what would be injected without running |
pushenv run -v <command>pushenv run --verbose <command> |
Show variable names being injected |
pushenv list-stagespushenv ls |
List all configured stages and their status |
pushenv diff |
Compare local .env with latest remote (default: development stage) |
pushenv diff -s <stage>pushenv diff --stage <stage> |
Compare specific stage (latest) |
pushenv diff --stage <stage> --version <N> |
Compare local .env with a specific historical version |
pushenv history |
Show version history for the default stage |
pushenv history -s <stage>pushenv history --stage <stage> |
Show version history for a specific stage |
pushenv rollback --stage <stage> --version <N> |
Create a new version that restores a previous one (safe rollback) |
pushenv example |
Generate example .env file with placeholders (default: development stage) |
pushenv example -s <stage>pushenv example --stage <stage> |
Generate example for specific stage |
pushenv example -o <path>pushenv example --output <path> |
Specify output file path |
pushenv generate-typespushenv types |
Generate TypeScript type definitions from .env file |
pushenv generate-types --env-path <path> |
Generate types from specific .env file |
pushenv generate-types --output <path> |
Specify output .d.ts file path |
MIT β open-source, commercially friendly.
Shahnoor Mujawar
Founder of Dtrue
Backend + Infra + AI engineer
β If you like PushEnv, star the repo!
Your star helps other developers discover it.