Skip to main content

push

Write 1Password values to every target of the selected services.

env-sync push (--service <name> | --all) --env <env> [--apply] [--yes]
FlagMeaning
--service <name> / --allWhich services to push. Exactly one is required.
--env <env>Required. The environment to push.
--applyActually write. Without it, push is a dry run.
--yesSkip the confirmation gate (see below).

Dry run (the default)​

Without --apply, push writes nothing. It prints what would change:

$ env-sync push --service web --env dev
[web] Dry-run (no --apply given) — showing what would change:
[web] vercel:myapp-web[development,preview]: drift on [API_KEY]
[web] dotenv:apps/web/.env.local: no drift

Like diff, a dry run exits 1 if it finds drift.

Apply​

Targets are written one after another. The first failure stops the remaining targets of that service. Other services still run.

$ env-sync push --service api --env dev --apply
[api] Succeeded: ssm:/myapp/api, github-actions:myorg/myapp:dev

Read-only targets are never written. CapRover targets are read-only by default:

[api] SKIPPED (read_only): caprover:myapp-api@captain.example.com

Confirmation gate​

This safety net is on by default. No flag turns it on. A push --apply runs without a prompt only if both of these are true:

  1. --env is dev-tier: dev, development, test, or testing (case-insensitive).
  2. Every non-read-only target is structurally provable as scoped to that environment.
PlatformAuto-approvable when
ssmAlways. The live path is <path>/<env>, derived from --env itself.
github-actionsOnly if github_env is set and is itself dev-tier.
vercel, render, dotenv, caproverNever. No manifest field proves where the live write lands.

If the gate applies, env-sync explains why:

$ env-sync push --service web --env dev --apply
Protected push:
[web] vercel:myapp-web[development,preview] is not structurally scoped to a dev-tier env
[web] dotenv:apps/web/.env.local is not structurally scoped to a dev-tier env
Type 'y' to continue:
  • TTY: only an explicit y/yes proceeds. Any other answer aborts with Aborted: nothing was written. (exit 1).
  • No TTY (CI, pipes): fails closed immediately with exit 1. It never waits for input.
  • --yes: proceeds without asking.

With --all, the check runs across every target of every selected service before any write, and it asks at most once. A safe service is never applied before an unsafe service fails closed, so a partial apply cannot happen.

The Architecture page explains why Vercel is never auto-approved, even for vercel_targets: [development].

Audit log​

Every push --apply attempt appends one JSON line per target to .env-sync/audit-YYYY-MM-DD.jsonl in the current directory. The file mode is 0600. Secret values are redacted from every field.

{"ts":"2026-09-28T12:00:00.000Z","service":"api","env":"dev","target":"ssm:/myapp/api","outcome":"success","actor":"alice"}

outcome is success, failure, or aborted (the gate declined). Add .env-sync/ to your .gitignore.