Skip to content

Commit 3438d3a

Browse files
committed
feat: add documentation for environment config and secrets management
1 parent af183e2 commit 3438d3a

1 file changed

Lines changed: 257 additions & 0 deletions

File tree

docs/Env-config-aliases.md

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# Environment config + secrets files (`.env.config` / `.env.secrets` + per-alias overrides)
2+
3+
This document proposes an environment-variable architecture for the Ensemble CLI that supports **multiple app environments** (or “targets”) cleanly and safely.
4+
5+
The chosen approach is:
6+
7+
- **Config base file**: `.env.config` (shared defaults)
8+
- **Config scoped override file**: `.env.config.<alias>` (per app/environment alias)
9+
- **Secrets base file**: `.env.secrets` (shared defaults)
10+
- **Secrets scoped override file**: `.env.secrets.<alias>` (per app/environment alias)
11+
12+
Where `<alias>` matches the `--app <alias>` value (an app key in `ensemble.config.json`). The README already describes `--app <alias>` as the “App alias / environment key”.
13+
14+
---
15+
16+
## Goals
17+
18+
- **Prevent accidental cross-environment breakage** (e.g. pushing dev values to prod).
19+
- **Support per-environment differences** (e.g. `api_url` differs between dev and prod).
20+
- **Keep local configuration readable** and easy to reason about.
21+
- **Play nicely with existing CLI behavior** that already maintains `.env.config` for assets.
22+
- **Keep secrets out of source control by default** with a predictable local structure.
23+
24+
## Non-goals
25+
26+
- Replacing runtime secrets management (e.g. Vault/KMS). This design is about **how the CLI stores and syncs config**, not the ultimate secret storage strategy.
27+
- Introducing a complex file format. Files remain simple `KEY=value` pairs.
28+
29+
---
30+
31+
## Terminology
32+
33+
- **Alias**: The value passed to `--app <alias>` (e.g. `default`, `dev`, `prod`), corresponding to an app entry in `ensemble.config.json`.
34+
- **Base config**: `.env.config`
35+
- **Alias config**: `.env.config.<alias>` (example: `.env.config.prod`)
36+
- **Effective config**: The merged view used by commands (base + alias overrides).
37+
- **Base secrets**: `.env.secrets`
38+
- **Alias secrets**: `.env.secrets.<alias>` (example: `.env.secrets.prod`)
39+
- **Effective secrets**: The merged view used by commands (base + alias overrides).
40+
41+
---
42+
43+
## File format
44+
45+
Both files use the same syntax:
46+
47+
- One entry per line: `KEY=value`
48+
- Empty lines allowed
49+
- Lines starting with `#` are comments
50+
- The first `=` separates key from value
51+
- Whitespace around keys is trimmed
52+
53+
Example:
54+
55+
```ini
56+
# shared defaults
57+
api_timeout_ms=30000
58+
api_url=https://dev.ensemble.com
59+
```
60+
61+
And an alias override:
62+
63+
```ini
64+
# prod overrides
65+
api_url=https://prod.ensemble.com
66+
```
67+
68+
---
69+
70+
## Resolution / precedence rules
71+
72+
When running a command for a given alias, apply the same rules to **config** and **secrets**:
73+
74+
- Config:
75+
1. Read `.env.config` if present (base defaults).
76+
2. Read `.env.config.<alias>` if present (alias overrides).
77+
3. Merge by key where **alias overrides win**.
78+
- Secrets:
79+
1. Read `.env.secrets` if present (base defaults).
80+
2. Read `.env.secrets.<alias>` if present (alias overrides).
81+
3. Merge by key where **alias overrides win**.
82+
83+
If only `.env.config` exists, behavior matches today (backwards compatible). Secrets files are additive and optional.
84+
85+
### Why this precedence?
86+
87+
- Shared values (common across envs) live in one place.
88+
- Environment-specific values override without duplicating the whole file.
89+
90+
---
91+
92+
## CLI behavior (proposed)
93+
94+
### Reading env config
95+
96+
Commands that need env config should use the **effective env config** for the selected `--app` alias.
97+
98+
- If `--app` is omitted, treat it as `default` (existing behavior).
99+
- If `.env.config.<alias>` is missing, fall back to `.env.config` only.
100+
101+
### Pushing env variables
102+
103+
If the CLI supports pushing env vars to the cloud, it should be **explicitly scoped**:
104+
105+
- `ensemble push --app prod` may only push the **prod effective env config**.
106+
- It must never push dev values to prod unless the user explicitly made them prod values (via `.env.config.prod` or identical base defaults).
107+
108+
Recommended sync semantics:
109+
110+
- Default: **upsert/patch** (add/update keys present in local effective env config).
111+
- Optional: `--delete-missing` (dangerous) to remove remote keys not present locally.
112+
- Optional: `--dry-run` to show changes without applying.
113+
114+
### Pulling env variables
115+
116+
Similarly, pulling should be scoped:
117+
118+
- `ensemble pull --app prod` should update **only** `.env.config.prod` (or optionally print a diff).
119+
- Avoid writing prod keys into `.env.config` unless explicitly requested.
120+
121+
---
122+
123+
## Asset-generated keys and `.env.config`
124+
125+
Today, the CLI “upserts” `.env.config` to ensure asset-related keys exist after:
126+
127+
- `ensemble add asset`
128+
- `ensemble push` (asset upload)
129+
- `ensemble pull` (asset sync)
130+
131+
This design proposes:
132+
133+
- Keep `.env.config` as a base defaults file for users.
134+
- Write **asset-generated keys into the alias file** by default (because assets are associated with a specific app target).
135+
136+
Suggested split:
137+
138+
- `.env.config`: user-managed shared defaults (checked in or not—team choice)
139+
- `.env.config.<alias>`: app-target-specific values, including:
140+
- `assets=<baseUrl>` for that target
141+
- any cloud-provided asset usage env keys for that target
142+
143+
Backwards-compatibility note:
144+
145+
- If alias files are not in use yet, continue writing to `.env.config` as today.
146+
- Once alias files exist (or a new setting/flag opts into alias-mode), write to alias files.
147+
148+
---
149+
150+
## Safety and production protections
151+
152+
To reduce “oops pushed dev to prod” failures:
153+
154+
- **Require explicit target** for sensitive operations (recommended UX):
155+
- For example, pushing env vars could require `--app` when multiple apps exist in `ensemble.config.json`.
156+
- **Stronger confirmations** for production-like aliases (e.g. `prod`, `production`):
157+
- Show a diff summary
158+
- Require a typed confirmation or `--yes`
159+
- **Never default to destructive deletes**:
160+
- `--delete-missing` must be opt-in.
161+
162+
---
163+
164+
## Git and secrets guidance
165+
166+
Different teams will choose different policies. Recommended defaults:
167+
168+
- Commit `.env.config` only if it contains **non-secret** shared defaults.
169+
- Do **not** commit `.env.secrets` or `.env.secrets.<alias>` (treat as sensitive).
170+
- Prefer `.env.config.example` / `.env.secrets.example` for documentation when needed.
171+
172+
At minimum, consider adding these to `.gitignore`:
173+
174+
```gitignore
175+
.env.config.*
176+
!.env.config.example
177+
.env.secrets
178+
.env.secrets.*
179+
!.env.secrets.example
180+
```
181+
182+
If you _do_ want to commit alias files for non-secret config, use a more selective ignore pattern or separate “public” vs “secret” configs.
183+
184+
### CLI handling expectations for secrets
185+
186+
If/when the CLI reads or syncs secrets:
187+
188+
- Never print secret values in logs (even in `--verbose`).
189+
- Prefer diff output that only shows keys changed (and counts), not values.
190+
- Consider stronger confirmations / restrictions for production aliases.
191+
192+
---
193+
194+
## Examples
195+
196+
### Dev + prod API URL
197+
198+
`.env.config`:
199+
200+
```ini
201+
api_timeout_ms=30000
202+
api_url=https://dev.ensemble.com
203+
```
204+
205+
`.env.config.prod`:
206+
207+
```ini
208+
api_url=https://prod.ensemble.com
209+
```
210+
211+
- `ensemble push --app default` uses dev URL
212+
- `ensemble push --app prod` uses prod URL
213+
214+
### Shared defaults + per-alias assets
215+
216+
`.env.config`:
217+
218+
```ini
219+
cdn_region=us-east-1
220+
```
221+
222+
`.env.config.dev`:
223+
224+
```ini
225+
assets=https://assets.dev.ensemble.com/
226+
```
227+
228+
`.env.config.prod`:
229+
230+
```ini
231+
assets=https://assets.prod.ensemble.com/
232+
```
233+
234+
---
235+
236+
## Migration plan (incremental)
237+
238+
1. **Introduce alias file support** in read-paths:
239+
- merge base + alias override (alias wins)
240+
2. **Introduce alias-aware write-paths**:
241+
- write generated keys (assets) into `.env.config.<alias>` when applicable
242+
3. **Add env push/pull commands or flags** (if desired):
243+
- ensure all operations are scoped to `--app <alias>`
244+
4. **Add guardrails**:
245+
- diffs, confirmations for prod, optional delete-missing
246+
247+
---
248+
249+
## Open questions (for follow-up)
250+
251+
- Should `.env.config` be treated as **shared defaults** only, or also as the “default alias” file?
252+
- This doc treats it as shared defaults that apply to all aliases unless overridden.
253+
- Should asset keys always be alias-scoped, or can some be global?
254+
- Recommended: alias-scoped, since assets are tied to a specific app target.
255+
256+
- Do we want separate commands for secrets vs config (recommended), or a unified env push/pull that handles both?
257+
- Recommendation: separate surfaces (e.g. `env` vs `secrets`) to make “high risk” operations explicit.

0 commit comments

Comments
 (0)