Skip to content

Zero-trust encrypted secret sync for .env files. PBKDF2 key derivation, AES-256-GCM, Cloudflare R2 storage, fully open source.

Notifications You must be signed in to change notification settings

shahnoorgit/PushEnv

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

27 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“¦ PushEnv

The modern dotenv with validation, type safety, and team sync.

npm version npm downloads license

PushEnv turns your .env into 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.

🎯 Before vs After

Before (dotenv)

require('dotenv').config();
const port = process.env.PORT;      // string | undefined ⚠️
const dbUrl = process.env.DB_URL;   // Could be missing! πŸ’₯

After (PushEnv)

// 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.


🎯 Two Ways to Use PushEnv

1️⃣ As a Library (dotenv alternative)

  • πŸ“š Drop-in replacement for dotenv with better features
  • βœ… Built-in Zod validation β€” catch config errors at startup
  • 🎨 TypeScript-first with full type safety
  • πŸ”§ Auto TypeScript type generation β€” no manual .d.ts files
  • πŸš€ Zero dependencies on external services

2️⃣ As a CLI (team sync tool)

  • πŸ” 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!

PushEnv vs dotenv

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 βœ… βœ…

When to Use What?

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.


πŸš€ Features

Library Features (dotenv alternative)

  • πŸ“š Drop-in dotenv replacement β€” use pushenv.config() instead of dotenv.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.ts files from Zod schemas
  • πŸ”„ Compatible API β€” supports path, override, debug options like dotenv
  • 🚫 Better error messages β€” clear validation errors with helpful suggestions

CLI Features (team sync)

Core Security

  • πŸ” 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)

Environment Management

  • 🌲 Multi-environment support β€” manage development, staging, production separately
  • βž• Add stages on-the-fly β€” add new environments without reinitializing (add-stage command)
  • πŸ›‘οΈ Smart file naming β€” automatic .env.{stage} suggestions prevent accidental secret mixing
  • πŸ“‹ Stage overview β€” list all configured stages and their status

Version Control & History

  • πŸ“œ 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 .env with 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

Advanced CLI Features

  • πŸš€ Zero-file execution β€” run commands with secrets injected directly into memory, no .env files ever written to disk
  • πŸ“„ Example file generation β€” create safe .env.example files 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

πŸ”§ Installation

npm install pushenv          # Local (library)
npm install -g pushenv       # Global (CLI)

Note: Zod is included - just import { z } from 'pushenv'

Migration from dotenv

npm uninstall dotenv
npm install pushenv

Then 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(),
  })
});

πŸ“š Library Usage

Type-Safe Validation πŸ”₯

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.PORT is a real number, not string
  • βœ… Validated once at startup - crashes early if config is wrong
  • βœ… Full TypeScript autocomplete everywhere
  • βœ… Import from one place across your entire app

Or: Just Load .env (No Validation)

import { config } from 'pushenv';

config();  // Like dotenv - loads .env, no validation
config({ path: '.env.production', override: true });

Advanced Options

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.ts

API Reference

config(options)

Load 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(options)

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[];
}

validateOrThrow(schema)

Convenience function that validates and returns typed data or throws.

function validateOrThrow<T extends z.ZodObject<any>>(
  schema: T
): z.infer<T>;

validateEnv(options) πŸ”₯

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

generateTypes(options)

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

πŸ›  CLI Usage (Team Sync)

Use the CLI to securely sync .env files across your team with end-to-end encryption.

☁️ PushEnv's Managed Cloud (Zero-Knowledge)

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

🀝 Who is this for?

  • 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:

CLI Quick Start

1️⃣ Initialize

pushenv init

You'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-stage

This adds new stages without losing your existing configuration or project ID.


2️⃣ Push encrypted .env files

pushenv push
pushenv push --stage staging
pushenv push --stage production

PushEnv will:

  • Read your .env
  • Encrypt locally
  • Upload the encrypted blob to cloud

Secrets never leave your machine unencrypted.


3️⃣ Teammates pull & decrypt

pushenv pull
pushenv pull -s production

After entering passphrase once:

  • AES key is derived
  • Encrypted blob downloaded
  • Decrypted locally only
  • .env file written to your configured path

Note: PushEnv will prompt for confirmation when pushing/pulling production environments for safety.


4️⃣ Compare local vs remote

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 staging

Shows:

  • 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

5️⃣ Browse history & roll back (versioning)

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 3

This 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 pushenv usage)

6️⃣ Generate example .env file

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.example

What it does:

  • Downloads and decrypts remote stage
  • Replaces all secret values with placeholders
  • Creates .env.{stage}.example file
  • 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

πŸš€ Zero-File Execution (Advanced)

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 .env files
  • Docker containers for cleaner images
  • Extra-paranoid security workflows
  • When you want secrets to vanish when process exits

Benefits:

  • No .env file to accidentally commit
  • No residual secret files on disk
  • Secrets only exist in process memory
  • Perfect for production deployments

πŸ”’ Security Model

βœ” 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 Structure

project/
  .env.development
  .env.staging
  .env.production
  .pushenv/
    config.json
~/.pushenv/
  keys.json

πŸ“– Commands

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-stages
pushenv 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-types
pushenv 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


πŸ“œ License

MIT β€” open-source, commercially friendly.


πŸ™‹ Author

Shahnoor Mujawar
Founder of Dtrue
Backend + Infra + AI engineer


⭐ If you like PushEnv, star the repo!
Your star helps other developers discover it.

About

Zero-trust encrypted secret sync for .env files. PBKDF2 key derivation, AES-256-GCM, Cloudflare R2 storage, fully open source.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published