Skip to content

Commit ae8c6fe

Browse files
docs(G3): quickstart + troubleshooting for the working command surface (#43)
Add docs/QUICKSTART.md (workspace layout → `up` saga → status/down → recovery → opt-in proxy/dns/trust/secrets) and docs/TROUBLESHOOTING.md (doctor-first; up failures, ledger recovery, WSL2 locking, concurrency, *.localhost/HTTPS, secrets, the update notifier). Both document only implemented, e2e-verified behavior (M2 saga + M4/M5 opt-ins). Part of G3; migration/threat-model docs follow. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0247c3c commit ae8c6fe

2 files changed

Lines changed: 165 additions & 0 deletions

File tree

‎docs/QUICKSTART.md‎

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# devstack — Quickstart
2+
3+
`devstack` runs one warm set of shared infrastructure (Postgres/Redis/MinIO) on a
4+
tool-owned Docker network and lets many project stacks attach to it — instead of a
5+
duplicate database per repo. This guide covers the commands that work today.
6+
7+
> **Prerequisites:** Docker Engine + Compose v2.20+, git ≥ 2.30. Run
8+
> `devstack doctor` first — it probes everything and prints a one-line fix for
9+
> anything missing.
10+
11+
## 1. Lay out a workspace
12+
13+
A workspace is one `workspace.yaml` (the shared layer) plus a `devstack.yaml` per
14+
repo (the portable project layer):
15+
16+
```yaml
17+
# workspace.yaml
18+
apiVersion: devstack/v1
19+
kind: Workspace
20+
name: acme
21+
shared:
22+
postgres: { template: postgres, params: { version: "16" } }
23+
redis: { template: redis }
24+
projects:
25+
- { name: api, path: services/api }
26+
```
27+
28+
```yaml
29+
# services/api/devstack.yaml
30+
apiVersion: devstack/v1
31+
kind: Project
32+
name: api
33+
services:
34+
api:
35+
template: php.laravel.nginx
36+
uses: [workspace.shared.postgres, workspace.shared.redis]
37+
healthcheck: { kind: http, port: 8080, path: /healthz }
38+
env:
39+
import:
40+
- { from: workspace.shared.postgres, vars: [host, port, user, password, database] }
41+
```
42+
43+
## 2. Bring it up
44+
45+
```bash
46+
devstack up
47+
```
48+
49+
One idempotent command runs the saga: **preflight → network → generate →
50+
shared (health-gated) → compose-up → hooks**. The shared services start once and
51+
are gated healthy *before* your project starts. Re-running `up` is near-instant —
52+
satisfied phases are skipped (resumable via the state ledger); a crash mid-run
53+
resumes; a failure compensates (drops ref rows / downs the project) but **never
54+
destroys data** (your DB volumes survive).
55+
56+
```bash
57+
devstack up --json # machine-readable phase records
58+
devstack up api # only the named project(s) + the shared services they use
59+
devstack up --build # rebuild images first
60+
devstack up --no-hooks # skip lifecycle hooks
61+
```
62+
63+
## 3. Inspect and tear down
64+
65+
```bash
66+
devstack status # per-project service health + last saga outcome + shared ref graph
67+
devstack shared status # shared services, ref counts, consuming projects
68+
devstack down # stop this workspace's project stacks, release their refs
69+
devstack shared gc # report shared services at zero refs (--stop to actually stop them)
70+
```
71+
72+
`down` leaves the shared services running by default (warm DBs are cheap). The
73+
external network and volumes are never touched by `down`.
74+
75+
## 4. Recovery
76+
77+
The state ledger is a *cache of reality*, never the source of truth:
78+
79+
```bash
80+
devstack shared doctor # reconcile: prune ref rows for projects no longer live
81+
devstack doctor --rebuild-state # reconstruct the ledger from live container labels + config
82+
```
83+
84+
## 5. Local HTTPS, DNS, and secrets (opt-in)
85+
86+
```bash
87+
# Reverse proxy: set network.proxy.engine: caddy in workspace.yaml, then:
88+
sudo devstack dns setup # marker-fenced /etc/hosts for *.localhost
89+
sudo devstack trust install # install the local CA (mkcert)
90+
devstack trust status # diagnose CA / certutil / WSL2 readiness
91+
92+
# Offline secrets with SOPS+age:
93+
devstack secrets keygen -o ~/.config/devstack/age.txt # generate an age key
94+
export SOPS_AGE_KEY_FILE=~/.config/devstack/age.txt
95+
# reference secrets in devstack.yaml env as secret://<provider>/<file>#<key>
96+
```
97+
98+
See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) when something doesn't work, and
99+
[ARCHITECTURE.md](ARCHITECTURE.md) for the design.

‎docs/TROUBLESHOOTING.md‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# devstack — Troubleshooting
2+
3+
Run **`devstack doctor`** first — it runs the real probe matrix (daemon, compose,
4+
git, filesystem, trust, dns) and prints a one-line remediation for each problem.
5+
`devstack doctor --json` emits the machine-readable contract.
6+
7+
## `up` fails or hangs
8+
9+
- **"docker daemon not reachable" (preflight).** Start Docker / fix
10+
`DOCKER_HOST` or the active `docker context`. `devstack doctor` confirms the
11+
context the ledger is keyed by.
12+
- **A shared service never goes healthy.** `up` fails fast and inlines the last
13+
log lines of the failing service plus the failing healthcheck. Slow stateful
14+
images (Postgres/MinIO) on Docker Desktop/WSL2 may need a larger `startPeriod`
15+
in the service's `healthcheck:`.
16+
- **"dependsOn cycle: a → b → a".** A `dependsOn` loop is reported with the full
17+
path at generate time — break the cycle.
18+
- **"condition: healthy … declares no healthcheck".** A `dependsOn` edge with
19+
`condition: healthy` requires the target to define a `healthcheck:`. Add one or
20+
use `condition: started`.
21+
22+
## State / ledger
23+
24+
- **Stale ref counts after a crash or manual `docker rm`.** Reconcile:
25+
`devstack shared doctor` (prunes refs for projects no longer live). Every
26+
command also self-heals lazily.
27+
- **Corrupt or lost `state.db`.** `devstack doctor --rebuild-state` reconstructs
28+
the shared-service + ref rows from live container labels + your config. A backup
29+
is written before any migration.
30+
- **WSL2 "database is locked" / flaky locking.** Keep `XDG_STATE_HOME` and
31+
`XDG_RUNTIME_DIR` on the Linux filesystem (ext4/tmpfs), not `/mnt/*` (9p).
32+
`devstack doctor` warns when they're on an unreliable filesystem.
33+
34+
## Concurrency
35+
36+
Two terminals running `devstack` at once is safe: only the four mutating points
37+
(network-ensure, port allocation, ref rows, `CREATE ROLE`) briefly serialize on
38+
the machine-global lock; everything else interleaves.
39+
40+
## Local HTTPS / DNS
41+
42+
- **`*.localhost` doesn't resolve.** `*.localhost` is not zero-config everywhere
43+
(WSL2/minimal Ubuntu lack systemd-resolved; macOS ≤15 resolves it only in
44+
browsers; Firefox ignores `/etc/hosts`). Run `sudo devstack dns setup` to write
45+
the marker-fenced `/etc/hosts` block; `devstack dns status` shows what's
46+
missing.
47+
- **Browser shows an untrusted cert.** `sudo devstack trust install` (needs
48+
`mkcert`; `certutil` from `libnss3-tools` for Firefox). `devstack trust status`
49+
prints the exact missing tool. On WSL2 the CA must also be imported into the
50+
Windows store (browsers run on Windows).
51+
52+
## Secrets
53+
54+
- **A `secret://` value isn't reaching the container.** Generated files only ever
55+
carry the *key name* (the value is injected at runtime) — that's by design (no
56+
secret value is ever written to disk). Check the provider is declared in
57+
`workspace.yaml secrets.providers` and that its kind's backend is reachable.
58+
- **SOPS+age:** ensure `SOPS_AGE_KEY_FILE` points at your key
59+
(`devstack secrets keygen -o <file>` generates one) and the `sops` binary is on
60+
PATH.
61+
62+
## Updates
63+
64+
- A new-release notice may print after a command. Suppress it with
65+
`DEVSTACK_NO_UPDATE_NOTIFIER=1`. The check is throttled (once/day) and never
66+
blocks or fails a command.

0 commit comments

Comments
 (0)