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 key
printf "%s" "<hex-key>" | envsync init --stdin-key --project my-app# .envsync.yml
project: my-app
keyfile: auto
files:
- path: .env.shared
schema:
PORT:
required: true
type: int
default: 3000
scope: shared
NODE_ENV:
required: true
type: enum
allowed: [development, test, production]
scope: shared
API_URL:
required: true
type: string
scope: shared
API_KEY:
required: true
type: string
scope: shared
secret: true
DEV_SERVER:
required: false
type: string
scope: personal
remote:
type: file
path: .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 it
printf "%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: true
type: string # string | int | bool | enum
allowed: # only for type: enum
- development
- production
default: "3000" # hint shown in check output when missing
scope: 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: file
path: .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 vars
run: |
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 provider -
s3remote 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