Vibecode Doppler
track this build5 phases, 10 steps, beginner friendly0%For one person, encrypted env files in the repo with sops and age give you versioned secrets with no server, and that is an afternoon. Doppler sells the sync to every platform, rotation, and the audit log across a team, which a repo of encrypted files does not.
You are building a lean indie version of Doppler. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Doppler · indie build Secrets management without a secrets server: sops and age encrypt env files you commit, a wrapper decrypts them into a process and never to disk, environments have different recipients so a laptop key cannot read production, CI holds one key, rotation removes a leaver, and a push command feeds one platform. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Encryption | sops with age | rule zero: do not invent a scheme | | Wrapper | A small shell or Node script | decrypt into a child process environment only | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **sops and age** · free - Why: The whole mechanism. - Get it: brew install sops age, or the releases pages. age-keygen -o key.txt creates a key pair. - Verify: sops --version and age --version print - [ ] **One age key per person and one for CI, and where the private keys live** · free - Why: Recipients define who can decrypt each environment. - Get it: Each person runs age-keygen; the private key goes in their password manager, the public key into .sops.yaml. - [ ] **The platform you deploy to (optional)** (optional) · free - Why: Phase 5 pushes secrets into one platform via its CLI. - Get it: Vercel, Railway or Fly CLI logged in. - [ ] **A CI with repository secrets** · free - Why: CI holds only the age private key. - Get it: GitHub Actions: Settings > Secrets and variables > Actions. ## Quick start ```sh age-keygen -o ~/.config/sops/age/keys.txt sops --encrypt --in-place secrets/dev.env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Dynamic secrets, sync to every platform, per-person access in a UI. - one-click sync into Vercel, AWS, GitHub Actions and the rest - the audit log and access control per person - automatic rotation and dynamic secrets - the dashboard If one of those is essential to you, that is the reason to keep paying for Doppler, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Doppler The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me secrets management to replace Doppler for one person or a tiny team. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write an encryption scheme or a secrets server. sops with age encrypts files you commit; every decision below is about workflow around it. ### Stack (fixed, do not substitute) - sops and age. Encrypted .env files per environment in the repo. A small shell or Node wrapper. CI secrets hold only the age private key. ### Phase 1 · Keys and one file Build: generate an age key pair per person and one for CI; a .sops.yaml mapping paths to recipients; encrypt secrets/dev.env so plaintext never touches the repo. A pre-commit hook that refuses any unencrypted *.env. Done when: the encrypted file is committed, git log shows no plaintext, and the hook blocks a plaintext .env. Do not build yet: runtime, CI. ### Phase 2 · Runtime Build: a run wrapper: secrets run --env dev -- npm start decrypts into the child process environment only, never to disk. Done when: the app starts with its secrets and no decrypted file exists afterward. ### Phase 3 · Environments and CI Build: staging.env and prod.env with different recipients (prod excludes dev laptops), and a GitHub Actions job that decrypts with the CI key from repository secrets. Done when: a dev key cannot decrypt prod, and CI runs with its secrets without any plaintext in the workflow file. ### Phase 4 · Rotation and audit Build: a rotate command that re-encrypts every file for the current recipient list (removing a leaver), and a log command that shows git history of who changed which key when. Done when: removing a recipient and rotating leaves them unable to decrypt the new files, and the log names the change. ### Phase 5 · Sync where needed Build: a push command that writes selected secrets into one platform (Vercel or Railway) via its CLI, so the platform is a target, not a source. Done when: a change in the encrypted file reaches the platform with one command. ### Out of scope (and why) - Dynamic secrets, sync to every platform, per-person access in a UI, an audit trail beyond git. That is the seat price. ### README must contain - Where private keys live and what happens if one leaks. - The rotate procedure. ===== AGENTS.md ===== # Agent instructions · Doppler indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: sops with age, A small shell or Node script. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ===== BUILD_PLAN.md ===== # Build plan · Doppler Secrets management without a secrets server: sops and age encrypt env files you commit, a wrapper decrypts them into a process and never to disk, environments have different recipients so a laptop key cannot read production, CI holds one key, rotation removes a leaver, and a push command feeds one platform. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Keys and one file An encrypted dev.env committed; plaintext never can be. ### Steps 1. Generate keys and write .sops.yaml mapping secrets/*.env to recipients Files: `.sops.yaml`, `secrets/dev.env` ```sh age-keygen -o ~/.config/sops/age/keys.txt sops --encrypt --in-place secrets/dev.env ``` 2. A pre-commit hook refusing any unencrypted *.env ### Done when - [ ] The encrypted file is committed and git log shows no plaintext - [ ] The hook blocks a plaintext .env ## Phase 2 · Runtime secrets run --env dev -- npm start, nothing decrypted to disk. ### Steps 1. The run wrapper using sops exec-env ```sh sops exec-env secrets/dev.env 'npm start' ``` 2. Verify no decrypted file exists afterward ### Done when - [ ] The app starts with its secrets - [ ] No decrypted file exists afterward ## Phase 3 · Environments and CI Prod excludes laptops; CI decrypts with its own key. ### Steps 1. staging.env and prod.env with different recipient lists 2. A GitHub Actions job decrypting with SOPS_AGE_KEY from repository secrets ### Done when - [ ] A dev key cannot decrypt prod - [ ] CI runs with its secrets and no plaintext in the workflow ## Phase 4 · Rotation and audit Remove a leaver; see who changed what. ### Steps 1. A rotate command re-encrypting every file for the current recipients ```sh sops updatekeys secrets/*.env ``` 2. A log command over git history of the secrets folder ### Done when - [ ] A removed recipient cannot decrypt the new files - [ ] The log names the change ## Phase 5 · Sync where needed One platform fed from the encrypted file. ### Steps 1. A push command writing selected keys via the platform CLI 2. README: where private keys live, what a leak means, the rotate procedure Files: `README.md` ### Done when - [ ] A change in the encrypted file reaches the platform with one command ## Not in this build - Dynamic secrets, sync to every platform, per-person access in a UI. ## After v1, if you want it - Infisical self-hosted when the team outgrows files ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Where sops finds your private key locally. SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt # Optional · secret. The CI private key, set only in the CI secret store. SOPS_AGE_KEY=AGE-SECRET-KEY-...
You are building a lean indie version of Doppler. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Doppler · indie build Secrets management without a secrets server: sops and age encrypt env files you commit, a wrapper decrypts them into a process and never to disk, environments have different recipients so a laptop key cannot read production, CI holds one key, rotation removes a leaver, and a push command feeds one platform. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Encryption | sops with age | rule zero: do not invent a scheme | | Wrapper | A small shell or Node script | decrypt into a child process environment only | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **sops and age** · free - Why: The whole mechanism. - Get it: brew install sops age, or the releases pages. age-keygen -o key.txt creates a key pair. - Verify: sops --version and age --version print - [ ] **One age key per person and one for CI, and where the private keys live** · free - Why: Recipients define who can decrypt each environment. - Get it: Each person runs age-keygen; the private key goes in their password manager, the public key into .sops.yaml. - [ ] **The platform you deploy to (optional)** (optional) · free - Why: Phase 5 pushes secrets into one platform via its CLI. - Get it: Vercel, Railway or Fly CLI logged in. - [ ] **A CI with repository secrets** · free - Why: CI holds only the age private key. - Get it: GitHub Actions: Settings > Secrets and variables > Actions. ## Quick start ```sh age-keygen -o ~/.config/sops/age/keys.txt sops --encrypt --in-place secrets/dev.env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Dynamic secrets, sync to every platform, per-person access in a UI. - one-click sync into Vercel, AWS, GitHub Actions and the rest - the audit log and access control per person - automatic rotation and dynamic secrets - the dashboard If one of those is essential to you, that is the reason to keep paying for Doppler, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Doppler The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me secrets management to replace Doppler for one person or a tiny team. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write an encryption scheme or a secrets server. sops with age encrypts files you commit; every decision below is about workflow around it. ### Stack (fixed, do not substitute) - sops and age. Encrypted .env files per environment in the repo. A small shell or Node wrapper. CI secrets hold only the age private key. ### Phase 1 · Keys and one file Build: generate an age key pair per person and one for CI; a .sops.yaml mapping paths to recipients; encrypt secrets/dev.env so plaintext never touches the repo. A pre-commit hook that refuses any unencrypted *.env. Done when: the encrypted file is committed, git log shows no plaintext, and the hook blocks a plaintext .env. Do not build yet: runtime, CI. ### Phase 2 · Runtime Build: a run wrapper: secrets run --env dev -- npm start decrypts into the child process environment only, never to disk. Done when: the app starts with its secrets and no decrypted file exists afterward. ### Phase 3 · Environments and CI Build: staging.env and prod.env with different recipients (prod excludes dev laptops), and a GitHub Actions job that decrypts with the CI key from repository secrets. Done when: a dev key cannot decrypt prod, and CI runs with its secrets without any plaintext in the workflow file. ### Phase 4 · Rotation and audit Build: a rotate command that re-encrypts every file for the current recipient list (removing a leaver), and a log command that shows git history of who changed which key when. Done when: removing a recipient and rotating leaves them unable to decrypt the new files, and the log names the change. ### Phase 5 · Sync where needed Build: a push command that writes selected secrets into one platform (Vercel or Railway) via its CLI, so the platform is a target, not a source. Done when: a change in the encrypted file reaches the platform with one command. ### Out of scope (and why) - Dynamic secrets, sync to every platform, per-person access in a UI, an audit trail beyond git. That is the seat price. ### README must contain - Where private keys live and what happens if one leaks. - The rotate procedure. ===== AGENTS.md ===== # Agent instructions · Doppler indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: sops with age, A small shell or Node script. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ===== BUILD_PLAN.md ===== # Build plan · Doppler Secrets management without a secrets server: sops and age encrypt env files you commit, a wrapper decrypts them into a process and never to disk, environments have different recipients so a laptop key cannot read production, CI holds one key, rotation removes a leaver, and a push command feeds one platform. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Keys and one file An encrypted dev.env committed; plaintext never can be. ### Steps 1. Generate keys and write .sops.yaml mapping secrets/*.env to recipients Files: `.sops.yaml`, `secrets/dev.env` ```sh age-keygen -o ~/.config/sops/age/keys.txt sops --encrypt --in-place secrets/dev.env ``` 2. A pre-commit hook refusing any unencrypted *.env ### Done when - [ ] The encrypted file is committed and git log shows no plaintext - [ ] The hook blocks a plaintext .env ## Phase 2 · Runtime secrets run --env dev -- npm start, nothing decrypted to disk. ### Steps 1. The run wrapper using sops exec-env ```sh sops exec-env secrets/dev.env 'npm start' ``` 2. Verify no decrypted file exists afterward ### Done when - [ ] The app starts with its secrets - [ ] No decrypted file exists afterward ## Phase 3 · Environments and CI Prod excludes laptops; CI decrypts with its own key. ### Steps 1. staging.env and prod.env with different recipient lists 2. A GitHub Actions job decrypting with SOPS_AGE_KEY from repository secrets ### Done when - [ ] A dev key cannot decrypt prod - [ ] CI runs with its secrets and no plaintext in the workflow ## Phase 4 · Rotation and audit Remove a leaver; see who changed what. ### Steps 1. A rotate command re-encrypting every file for the current recipients ```sh sops updatekeys secrets/*.env ``` 2. A log command over git history of the secrets folder ### Done when - [ ] A removed recipient cannot decrypt the new files - [ ] The log names the change ## Phase 5 · Sync where needed One platform fed from the encrypted file. ### Steps 1. A push command writing selected keys via the platform CLI 2. README: where private keys live, what a leak means, the rotate procedure Files: `README.md` ### Done when - [ ] A change in the encrypted file reaches the platform with one command ## Not in this build - Dynamic secrets, sync to every platform, per-person access in a UI. ## After v1, if you want it - Infisical self-hosted when the team outgrows files ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Where sops finds your private key locally. SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt # Optional · secret. The CI private key, set only in the CI secret store. SOPS_AGE_KEY=AGE-SECRET-KEY-...
You are building a production product version of Doppler. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Doppler · product brief ## Problem For one person, encrypted env files in the repo with sops and age give you versioned secrets with no server, and that is an afternoon. Doppler sells the sync to every platform, rotation, and the audit log across a team, which a repo of encrypted files does not. ## Product outcome Secrets with access control and history, no server to run, and a platform sync you control. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - sops and age - a place for the private keys (a password manager and CI secrets) ## Explicit non-goals for v1 - Dynamic secrets, sync to every platform, per-person access in a UI. - one-click sync into Vercel, AWS, GitHub Actions and the rest - the audit log and access control per person - automatic rotation and dynamic secrets - the dashboard ## Success criteria - Dev key cannot decrypt prod - Hook blocks plaintext - Rotation verified ===== BRIEF.md ===== # Build brief · Doppler The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me secrets management to replace Doppler for one person or a tiny team. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write an encryption scheme or a secrets server. sops with age encrypts files you commit; every decision below is about workflow around it. ### Stack (fixed, do not substitute) - sops and age. Encrypted .env files per environment in the repo. A small shell or Node wrapper. CI secrets hold only the age private key. ### Phase 1 · Keys and one file Build: generate an age key pair per person and one for CI; a .sops.yaml mapping paths to recipients; encrypt secrets/dev.env so plaintext never touches the repo. A pre-commit hook that refuses any unencrypted *.env. Done when: the encrypted file is committed, git log shows no plaintext, and the hook blocks a plaintext .env. Do not build yet: runtime, CI. ### Phase 2 · Runtime Build: a run wrapper: secrets run --env dev -- npm start decrypts into the child process environment only, never to disk. Done when: the app starts with its secrets and no decrypted file exists afterward. ### Phase 3 · Environments and CI Build: staging.env and prod.env with different recipients (prod excludes dev laptops), and a GitHub Actions job that decrypts with the CI key from repository secrets. Done when: a dev key cannot decrypt prod, and CI runs with its secrets without any plaintext in the workflow file. ### Phase 4 · Rotation and audit Build: a rotate command that re-encrypts every file for the current recipient list (removing a leaver), and a log command that shows git history of who changed which key when. Done when: removing a recipient and rotating leaves them unable to decrypt the new files, and the log names the change. ### Phase 5 · Sync where needed Build: a push command that writes selected secrets into one platform (Vercel or Railway) via its CLI, so the platform is a target, not a source. Done when: a change in the encrypted file reaches the platform with one command. ### Out of scope (and why) - Dynamic secrets, sync to every platform, per-person access in a UI, an audit trail beyond git. That is the seat price. ### README must contain - Where private keys live and what happens if one leaks. - The rotate procedure. ===== ARCHITECTURE.md ===== # Architecture · Doppler ## Stack | Part | Choice | Why | | --- | --- | --- | | Encryption | sops with age | rule zero: do not invent a scheme | | Wrapper | A small shell or Node script | decrypt into a child process environment only | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Files | encrypted env files and .sops.yaml | Infisical when you outgrow files | | Wrapper | exec-env | Any language | | Push | platform sync | One adapter per platform | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `SOPS_AGE_KEY_FILE` · required · Where sops finds your private key locally. - `SOPS_AGE_KEY` · optional, secret · The CI private key, set only in the CI secret store. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Doppler product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: sops with age, A small shell or Node script. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ===== MILESTONES.md ===== # Delivery milestones · Doppler Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Keys and one file An encrypted dev.env committed; plaintext never can be. ### Steps 1. Generate keys and write .sops.yaml mapping secrets/*.env to recipients Files: `.sops.yaml`, `secrets/dev.env` ```sh age-keygen -o ~/.config/sops/age/keys.txt sops --encrypt --in-place secrets/dev.env ``` 2. A pre-commit hook refusing any unencrypted *.env ### Done when - [ ] The encrypted file is committed and git log shows no plaintext - [ ] The hook blocks a plaintext .env ## M2 · Runtime secrets run --env dev -- npm start, nothing decrypted to disk. ### Steps 1. The run wrapper using sops exec-env ```sh sops exec-env secrets/dev.env 'npm start' ``` 2. Verify no decrypted file exists afterward ### Done when - [ ] The app starts with its secrets - [ ] No decrypted file exists afterward ## M3 · Environments and CI Prod excludes laptops; CI decrypts with its own key. ### Steps 1. staging.env and prod.env with different recipient lists 2. A GitHub Actions job decrypting with SOPS_AGE_KEY from repository secrets ### Done when - [ ] A dev key cannot decrypt prod - [ ] CI runs with its secrets and no plaintext in the workflow ## M4 · Rotation and audit Remove a leaver; see who changed what. ### Steps 1. A rotate command re-encrypting every file for the current recipients ```sh sops updatekeys secrets/*.env ``` 2. A log command over git history of the secrets folder ### Done when - [ ] A removed recipient cannot decrypt the new files - [ ] The log names the change ## M5 · Sync where needed One platform fed from the encrypted file. ### Steps 1. A push command writing selected keys via the platform CLI 2. README: where private keys live, what a leak means, the rotate procedure Files: `README.md` ### Done when - [ ] A change in the encrypted file reaches the platform with one command ## M6 · Team hygiene (production only) Onboarding and offboarding as procedures. ### Steps 1. An onboarding script adding a recipient and rotating 2. A quarterly rotation reminder for the CI key ### Done when - [ ] A new person is onboarded in five minutes - [ ] The CI key rotation is documented and tested ===== OPERATIONS.md ===== # Operations · Doppler ## Backup The repo; private keys in password managers. ## Restore Clone plus a private key. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring The pre-commit hook and CI. ## Incident checklist A leaked private key: remove the recipient, rotate, and rotate every secret it could read. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Dev key cannot decrypt prod - [ ] Hook blocks plaintext - [ ] Rotation verified ## Launch constraint Do not market omitted Doppler capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Where sops finds your private key locally. SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt # Optional · secret. The CI private key, set only in the CI secret store. SOPS_AGE_KEY=AGE-SECRET-KEY-...
# Doppler · indie build Secrets management without a secrets server: sops and age encrypt env files you commit, a wrapper decrypts them into a process and never to disk, environments have different recipients so a laptop key cannot read production, CI holds one key, rotation removes a leaver, and a push command feeds one platform. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Encryption | sops with age | rule zero: do not invent a scheme | | Wrapper | A small shell or Node script | decrypt into a child process environment only | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **sops and age** · free - Why: The whole mechanism. - Get it: brew install sops age, or the releases pages. age-keygen -o key.txt creates a key pair. - Verify: sops --version and age --version print - [ ] **One age key per person and one for CI, and where the private keys live** · free - Why: Recipients define who can decrypt each environment. - Get it: Each person runs age-keygen; the private key goes in their password manager, the public key into .sops.yaml. - [ ] **The platform you deploy to (optional)** (optional) · free - Why: Phase 5 pushes secrets into one platform via its CLI. - Get it: Vercel, Railway or Fly CLI logged in. - [ ] **A CI with repository secrets** · free - Why: CI holds only the age private key. - Get it: GitHub Actions: Settings > Secrets and variables > Actions. ## Quick start ```sh age-keygen -o ~/.config/sops/age/keys.txt sops --encrypt --in-place secrets/dev.env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Dynamic secrets, sync to every platform, per-person access in a UI. - one-click sync into Vercel, AWS, GitHub Actions and the rest - the audit log and access control per person - automatic rotation and dynamic secrets - the dashboard If one of those is essential to you, that is the reason to keep paying for Doppler, and the README should say so rather than pretend.
# Build brief · Doppler The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me secrets management to replace Doppler for one person or a tiny team. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write an encryption scheme or a secrets server. sops with age encrypts files you commit; every decision below is about workflow around it. ### Stack (fixed, do not substitute) - sops and age. Encrypted .env files per environment in the repo. A small shell or Node wrapper. CI secrets hold only the age private key. ### Phase 1 · Keys and one file Build: generate an age key pair per person and one for CI; a .sops.yaml mapping paths to recipients; encrypt secrets/dev.env so plaintext never touches the repo. A pre-commit hook that refuses any unencrypted *.env. Done when: the encrypted file is committed, git log shows no plaintext, and the hook blocks a plaintext .env. Do not build yet: runtime, CI. ### Phase 2 · Runtime Build: a run wrapper: secrets run --env dev -- npm start decrypts into the child process environment only, never to disk. Done when: the app starts with its secrets and no decrypted file exists afterward. ### Phase 3 · Environments and CI Build: staging.env and prod.env with different recipients (prod excludes dev laptops), and a GitHub Actions job that decrypts with the CI key from repository secrets. Done when: a dev key cannot decrypt prod, and CI runs with its secrets without any plaintext in the workflow file. ### Phase 4 · Rotation and audit Build: a rotate command that re-encrypts every file for the current recipient list (removing a leaver), and a log command that shows git history of who changed which key when. Done when: removing a recipient and rotating leaves them unable to decrypt the new files, and the log names the change. ### Phase 5 · Sync where needed Build: a push command that writes selected secrets into one platform (Vercel or Railway) via its CLI, so the platform is a target, not a source. Done when: a change in the encrypted file reaches the platform with one command. ### Out of scope (and why) - Dynamic secrets, sync to every platform, per-person access in a UI, an audit trail beyond git. That is the seat price. ### README must contain - Where private keys live and what happens if one leaks. - The rotate procedure.
# Agent instructions · Doppler indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: sops with age, A small shell or Node script. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs".
# Build plan · Doppler Secrets management without a secrets server: sops and age encrypt env files you commit, a wrapper decrypts them into a process and never to disk, environments have different recipients so a laptop key cannot read production, CI holds one key, rotation removes a leaver, and a push command feeds one platform. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Keys and one file An encrypted dev.env committed; plaintext never can be. ### Steps 1. Generate keys and write .sops.yaml mapping secrets/*.env to recipients Files: `.sops.yaml`, `secrets/dev.env` ```sh age-keygen -o ~/.config/sops/age/keys.txt sops --encrypt --in-place secrets/dev.env ``` 2. A pre-commit hook refusing any unencrypted *.env ### Done when - [ ] The encrypted file is committed and git log shows no plaintext - [ ] The hook blocks a plaintext .env ## Phase 2 · Runtime secrets run --env dev -- npm start, nothing decrypted to disk. ### Steps 1. The run wrapper using sops exec-env ```sh sops exec-env secrets/dev.env 'npm start' ``` 2. Verify no decrypted file exists afterward ### Done when - [ ] The app starts with its secrets - [ ] No decrypted file exists afterward ## Phase 3 · Environments and CI Prod excludes laptops; CI decrypts with its own key. ### Steps 1. staging.env and prod.env with different recipient lists 2. A GitHub Actions job decrypting with SOPS_AGE_KEY from repository secrets ### Done when - [ ] A dev key cannot decrypt prod - [ ] CI runs with its secrets and no plaintext in the workflow ## Phase 4 · Rotation and audit Remove a leaver; see who changed what. ### Steps 1. A rotate command re-encrypting every file for the current recipients ```sh sops updatekeys secrets/*.env ``` 2. A log command over git history of the secrets folder ### Done when - [ ] A removed recipient cannot decrypt the new files - [ ] The log names the change ## Phase 5 · Sync where needed One platform fed from the encrypted file. ### Steps 1. A push command writing selected keys via the platform CLI 2. README: where private keys live, what a leak means, the rotate procedure Files: `README.md` ### Done when - [ ] A change in the encrypted file reaches the platform with one command ## Not in this build - Dynamic secrets, sync to every platform, per-person access in a UI. ## After v1, if you want it - Infisical self-hosted when the team outgrows files
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Where sops finds your private key locally. SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt # Optional · secret. The CI private key, set only in the CI secret store. SOPS_AGE_KEY=AGE-SECRET-KEY-...
# Doppler · product brief ## Problem For one person, encrypted env files in the repo with sops and age give you versioned secrets with no server, and that is an afternoon. Doppler sells the sync to every platform, rotation, and the audit log across a team, which a repo of encrypted files does not. ## Product outcome Secrets with access control and history, no server to run, and a platform sync you control. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - sops and age - a place for the private keys (a password manager and CI secrets) ## Explicit non-goals for v1 - Dynamic secrets, sync to every platform, per-person access in a UI. - one-click sync into Vercel, AWS, GitHub Actions and the rest - the audit log and access control per person - automatic rotation and dynamic secrets - the dashboard ## Success criteria - Dev key cannot decrypt prod - Hook blocks plaintext - Rotation verified
# Build brief · Doppler The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me secrets management to replace Doppler for one person or a tiny team. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write an encryption scheme or a secrets server. sops with age encrypts files you commit; every decision below is about workflow around it. ### Stack (fixed, do not substitute) - sops and age. Encrypted .env files per environment in the repo. A small shell or Node wrapper. CI secrets hold only the age private key. ### Phase 1 · Keys and one file Build: generate an age key pair per person and one for CI; a .sops.yaml mapping paths to recipients; encrypt secrets/dev.env so plaintext never touches the repo. A pre-commit hook that refuses any unencrypted *.env. Done when: the encrypted file is committed, git log shows no plaintext, and the hook blocks a plaintext .env. Do not build yet: runtime, CI. ### Phase 2 · Runtime Build: a run wrapper: secrets run --env dev -- npm start decrypts into the child process environment only, never to disk. Done when: the app starts with its secrets and no decrypted file exists afterward. ### Phase 3 · Environments and CI Build: staging.env and prod.env with different recipients (prod excludes dev laptops), and a GitHub Actions job that decrypts with the CI key from repository secrets. Done when: a dev key cannot decrypt prod, and CI runs with its secrets without any plaintext in the workflow file. ### Phase 4 · Rotation and audit Build: a rotate command that re-encrypts every file for the current recipient list (removing a leaver), and a log command that shows git history of who changed which key when. Done when: removing a recipient and rotating leaves them unable to decrypt the new files, and the log names the change. ### Phase 5 · Sync where needed Build: a push command that writes selected secrets into one platform (Vercel or Railway) via its CLI, so the platform is a target, not a source. Done when: a change in the encrypted file reaches the platform with one command. ### Out of scope (and why) - Dynamic secrets, sync to every platform, per-person access in a UI, an audit trail beyond git. That is the seat price. ### README must contain - Where private keys live and what happens if one leaks. - The rotate procedure.
# Architecture · Doppler ## Stack | Part | Choice | Why | | --- | --- | --- | | Encryption | sops with age | rule zero: do not invent a scheme | | Wrapper | A small shell or Node script | decrypt into a child process environment only | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Files | encrypted env files and .sops.yaml | Infisical when you outgrow files | | Wrapper | exec-env | Any language | | Push | platform sync | One adapter per platform | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `SOPS_AGE_KEY_FILE` · required · Where sops finds your private key locally. - `SOPS_AGE_KEY` · optional, secret · The CI private key, set only in the CI secret store. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Doppler product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: sops with age, A small shell or Node script. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone.
# Delivery milestones · Doppler Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Keys and one file An encrypted dev.env committed; plaintext never can be. ### Steps 1. Generate keys and write .sops.yaml mapping secrets/*.env to recipients Files: `.sops.yaml`, `secrets/dev.env` ```sh age-keygen -o ~/.config/sops/age/keys.txt sops --encrypt --in-place secrets/dev.env ``` 2. A pre-commit hook refusing any unencrypted *.env ### Done when - [ ] The encrypted file is committed and git log shows no plaintext - [ ] The hook blocks a plaintext .env ## M2 · Runtime secrets run --env dev -- npm start, nothing decrypted to disk. ### Steps 1. The run wrapper using sops exec-env ```sh sops exec-env secrets/dev.env 'npm start' ``` 2. Verify no decrypted file exists afterward ### Done when - [ ] The app starts with its secrets - [ ] No decrypted file exists afterward ## M3 · Environments and CI Prod excludes laptops; CI decrypts with its own key. ### Steps 1. staging.env and prod.env with different recipient lists 2. A GitHub Actions job decrypting with SOPS_AGE_KEY from repository secrets ### Done when - [ ] A dev key cannot decrypt prod - [ ] CI runs with its secrets and no plaintext in the workflow ## M4 · Rotation and audit Remove a leaver; see who changed what. ### Steps 1. A rotate command re-encrypting every file for the current recipients ```sh sops updatekeys secrets/*.env ``` 2. A log command over git history of the secrets folder ### Done when - [ ] A removed recipient cannot decrypt the new files - [ ] The log names the change ## M5 · Sync where needed One platform fed from the encrypted file. ### Steps 1. A push command writing selected keys via the platform CLI 2. README: where private keys live, what a leak means, the rotate procedure Files: `README.md` ### Done when - [ ] A change in the encrypted file reaches the platform with one command ## M6 · Team hygiene (production only) Onboarding and offboarding as procedures. ### Steps 1. An onboarding script adding a recipient and rotating 2. A quarterly rotation reminder for the CI key ### Done when - [ ] A new person is onboarded in five minutes - [ ] The CI key rotation is documented and tested
# Operations · Doppler ## Backup The repo; private keys in password managers. ## Restore Clone plus a private key. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring The pre-commit hook and CI. ## Incident checklist A leaked private key: remove the recipient, rotate, and rotate every secret it could read. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Dev key cannot decrypt prod - [ ] Hook blocks plaintext - [ ] Rotation verified ## Launch constraint Do not market omitted Doppler capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Where sops finds your private key locally. SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt # Optional · secret. The CI private key, set only in the CI secret store. SOPS_AGE_KEY=AGE-SECRET-KEY-...
$ choose a build depth, inspect the files, then open the complete pack in your agent
Teams pay so a secret changes in one place and every environment follows, with a record of who did it.
xone-click sync into Vercel, AWS, GitHub Actions and the rest
xthe audit log and access control per person
xautomatic rotation and dynamic secrets
xthe dashboard
Doppler pricing
team$21/mo · monthly per user · $252/yr
free tierThe free Developer plan covers 3 users, 10 projects and 4 environments.
verified 2026-09-04 · source ↗
Is Doppler free?
The free Developer plan covers 3 users, 10 projects and 4 environments. Paid is Team at $21/mo (checked 2026-09-04).
Vibecode Doppler
Kinda. The core of Doppler is buildable in a weekend with the prompt on this page, but there are real gaps: one-click sync into Vercel, AWS, GitHub Actions and the rest, the audit log and access control per person. Read the honest list above before committing.
How much does Doppler cost?
Doppler costs about $21/month (Team, checked 2026-09-04), which is $252 per year.
What do I lose by replacing Doppler?
Honestly: one-click sync into Vercel, AWS, GitHub Actions and the rest; the audit log and access control per person; automatic rotation and dynamic secrets; the dashboard. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Doppler?
Yes: sops (encrypted files for secrets in git), Infisical (open-source secrets platform). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.