Getting Started
Install (clone and build)
There is no published package yet. Build from source:
git clone https://github.com/catesworks/env-sync.git
cd env-sync
pnpm install && pnpm build
This builds the TypeScript CLI. The executable is apps/cli/bin/env-sync.js. To call it as env-sync from anywhere, put a symlink or alias on your PATH:
alias env-sync="node $PWD/apps/cli/bin/env-sync.js"
To build the Go binary instead (Go toolchain required):
cd go && go build -o env-sync ./cmd/env-sync
Prerequisites
env-sync reads from 1Password and writes through each platform's own API or CLI. You only need the tools and credentials for the platforms that your manifest uses.
| Needed for | What |
|---|---|
| Every command | 1Password access. The TypeScript CLI uses the op CLI, with a signed-in desktop session or OP_SERVICE_ACCOUNT_TOKEN. The Go CLI uses the 1Password SDK and requires OP_SERVICE_ACCOUNT_TOKEN. |
ssm targets | The aws CLI, with credentials for the expected account and region. Override them with ENV_SYNC_AWS_ACCOUNT_ID / ENV_SYNC_AWS_REGION. |
github-actions targets | The gh CLI, authenticated. (The Go CLI talks to the GitHub API directly.) |
vercel targets | VERCEL_ACCESS_TOKEN, ideally a project-scoped token. |
render targets | RENDER_API_KEY (or numbered RENDER_<n>_API_KEY keys, one per Render team). |
caprover targets | CAPROVER_ADMIN_PASSWORD. |
dotenv targets | Nothing extra. |
A 1Password Service Account token is a long-lived bearer credential. Scope the Service Account to only the vaults that your manifests reference. Never use an org-wide Service Account.
Write a manifest
env-sync looks for secrets.manifest.yml in the current directory. Each service names a 1Password source (<vault>/<item-prefix>) and one or more targets:
services:
api:
source: app-secrets/myapp/api
targets:
- platform: ssm
path: /myapp/api
- platform: github-actions
repo: myorg/myapp
github_env: dev
web:
source: app-secrets/myapp/web
targets:
- platform: vercel
project: myapp-web
vercel_targets: [development, preview]
- platform: dotenv
path: apps/web/.env.local
The 1Password item is resolved per environment. With --env dev, service api reads the item myapp/api/dev from the app-secrets vault. Every labeled field on that item is a key to sync. The notes field is skipped.
First run
Run the commands in this order. Each step is safer than the next.
1. Validate. Check that the 1Password item exists with no empty fields and that every target is reachable:
env-sync validate --service api --env dev
[api] OK: app-secrets/myapp/api/dev exists with fields: DATABASE_URL, API_KEY
[api] target ssm:/myapp/api: reachable
[api] target github-actions:myorg/myapp:dev: reachable
2. Diff. See what differs, with no writes:
env-sync diff --service api --env dev
[api] ssm:/myapp/api: drift on [API_KEY]
[api] github-actions:myorg/myapp:dev: DATABASE_URL: present, value unverifiable; API_KEY: MISSING_FROM_GITHUB
3. Push. Without --apply, push is a dry run that prints the diff. With --apply, it writes:
env-sync push --service api --env dev --apply
[api] Succeeded: ssm:/myapp/api, github-actions:myorg/myapp:dev
That push ran without a prompt because every target is provably dev-scoped: SSM derives its path from --env, and the GitHub target has a dev-tier github_env. Add a Vercel, Render, or dotenv target, or use a non-dev --env, and env-sync asks you to confirm first. See push.