Keep
.envfiles validated and in sync across your team — without updating docs every time a key changes.
Every project uses .env files. Every team has the same friction:
- New developer joins → nobody knows which keys are required
- Someone adds
STRIPE_WEBHOOK_SECRET→ half the team breaks silently - Docs go stale within a week
- Copy-pasting values over Slack/Notion is error-prone and insecure
Envify solves this by making your schema the source of truth and syncing shared values through an encrypted remote store.
.envsync.yml → defines your schema (required keys, types, scope)
.env.shared → team values (synced via remote, encrypted)
.env.local → personal overrides (never synced, never pushed)
~/.envify/<proj>.key → your encryption key (never in the repo)
Keys marked scope: shared are pushed/pulled to the remote.
Keys marked scope: personal are validated locally but never leave your machine.
git clone https://github.com/your-org/envify.git
cd envify/sdk-node
npm install
npm run build
npm link- Node.js >= 20.0.0
- pnpm >= 9
# Generate a new key and scaffold .envsync.yml
envsync init --project my-app
# Or import an existing team keyprintf"%s""<hex-key>"| envsync init --stdin-key --project my-app# .envsync.ymlproject: my-appkeyfile: autofiles:
- path: .env.sharedschema:
PORT:
required: truetype: intdefault: 3000scope: sharedNODE_ENV:
required: truetype: enumallowed: [development, test, production]scope: sharedAPI_URL:
required: truetype: stringscope: sharedAPI_KEY:
required: truetype: stringscope: sharedsecret: trueDEV_SERVER:
required: falsetype: stringscope: personalremote:
type: filepath: .envsync-remoteenvsync check
# ✔ .env.shared — all variables are validenvsync push
# ✔ Encrypted and pushed 1 file(s) to remoteprintf"%s""<hex-key>"| envsync init --stdin-key --project my-app
envsync pull
# ✔ Updated .env.shared# ✔ Pulled and decrypted 1 file(s) from remoteenvsync diff
# + STRIPE_WEBHOOK_SECRET=...# ~ API_URL: https://old.com → https://new.com| Command | Description |
|---|---|
envsync init | Scaffold .envsync.yml and generate or import a key |
envsync check | Validate local .env files against the schema |
envsync diff | Compare local state to the remote |
envsync push | Encrypt and push to the remote store |
envsync pull | Pull and decrypt from the remote store |
envsync key fingerprint | Print the key fingerprint for verification |
envsync doctor | Diagnose key, permissions, and remote issues |
| Option | Description |
|---|---|
--config | Path to .envsync.yml (default: .envsync.yml) |
--redact | Hide secret values in output |
--dry-run | Preview changes without writing |
--force | Overwrite existing key or config |
The encryption key lives at ~/.envify/.<project>.key on each machine.
It is never committed to the repository.
Preferred (no shell history):
# Teammate A: print the key
cat ~/.envify/.my-app.key
# Teammate B: import itprintf"%s""<hex-from-teammate-a>"| envsync init --stdin-key --project my-appVerify you share the same key:
envsync key fingerprint
# envsync:v1:3a7c2f91b4 ← must match on both machinesexport ENVSYNC_KEYFILE=/run/secrets/envsync.key
envsync check
envsync pushOr pass a key file explicitly:
envsync init --key-file /run/secrets/envsync.key --project my-appKEY_NAME:
required: truetype: string # string | int | bool | enumallowed: # only for type: enum
- development
- productiondefault: "3000"# hint shown in check output when missingscope: shared # shared (synced) | personal (local only)secret: true # redact value in diff/check output| Type | Valid values |
|---|---|
string | Any string |
int | Integer, e.g. 3000 |
bool | true / false / 1 / 0 / yes / no |
enum | One of the values in the allowed list |
| Scope | Pushed | Pulled | Validated |
|---|---|---|---|
shared | yes | yes | yes |
personal | no | no | yes, locally |
Personal keys let each developer use their own ports, local service URLs, or feature flags without polluting the shared remote.
Stores encrypted blobs and a manifest in a local directory.
Commit .envsync-remote/ to a private repository for team sharing,
or place it on any shared volume.
remote:
type: filepath: .envsync-remoteLayout:
.envsync-remote/
├─ manifest.json
└─ blobs/
└─ 2025-01-15T10-30-00-000Z..env.shared.enc
Future remote types: http, s3, vault.
- Encryption: AES-256-GCM with a per-push random IV
- Associated data:
<project>:<filePath>binds ciphertext to context - Key storage:
~/.envify/.<project>.keyat0600permissions - Secret values: redacted in
diffandcheckoutput (secret: true) - Backups:
.envfiles are backed up with a timestamp before everypull - The remote never sees plaintext — all encryption/decryption is local
- Not a replacement for production secret managers (Vault, AWS Secrets Manager)
- Not suitable for storing production credentials without additional controls
- Not audited — treat it as a developer-experience tool, not a security boundary
# Install dependencies
npm install
# Type check
npm run typecheck
# Run all tests
npm test# Run with coverage
npm run coverage
# Lint
npm run lint
# Format
npm run format
# Build
npm run build
# Run locally without building
npm run dev -- check
npm run dev -- init --project my-appsdk-node/
├─ bin/envsync.ts # CLI entrypoint
├─ src/
│ ├─ index.ts # Public SDK exports
│ ├─ cli/ # Subcommand handlers
│ ├─ core/ # Business logic
│ └─ util/ # fs, log, str helpers
├─ tests/
│ ├─ unit/ # Unit tests per module
│ └─ integration/ # End-to-end flow tests
└─ fixtures/ # Sample .env and .envsync.yml files
- name: Check env varsrun: | export ENVSYNC_KEYFILE=/run/secrets/envsync_key npx envsync check| Code | Meaning |
|---|---|
0 | Success |
1 | General error |
2 | Validation failed |
3 | Diff found differences |
4 | Remote not found |
httpremote providers3remote provider- Key rotation (
envsync key rotate) - Schema inference from existing
.env(envsync init --infer) - Shell completions (bash, zsh, fish)
- Multiple environment support (
.env.staging,.env.production)
MIT — see LICENSE