@localctl/cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +398 -431
  3. package/assets/manifests/namespace-addons.yaml +4 -4
  4. package/assets/manifests/tls-store.yaml +8 -8
  5. package/bin/localctl.js +2 -2
  6. package/package.json +61 -54
  7. package/src/commands/addons.js +56 -56
  8. package/src/commands/doctor.js +44 -43
  9. package/src/commands/down.js +35 -35
  10. package/src/commands/exec.js +15 -15
  11. package/src/commands/generateManifests.js +12 -12
  12. package/src/commands/hosts.js +23 -23
  13. package/src/commands/logs.js +45 -45
  14. package/src/commands/profiles.js +180 -180
  15. package/src/commands/prune.js +49 -49
  16. package/src/commands/registryInfo.js +18 -18
  17. package/src/commands/reload.js +44 -44
  18. package/src/commands/secrets.js +75 -75
  19. package/src/commands/setup.js +36 -36
  20. package/src/commands/status.js +136 -136
  21. package/src/commands/up.js +123 -123
  22. package/src/index.js +38 -36
  23. package/src/lib/addonDirs.js +43 -43
  24. package/src/lib/addonLoader.js +190 -190
  25. package/src/lib/addons/cluster/logging/addon.yaml +46 -46
  26. package/src/lib/addons/cluster/logging/config/all-logs.json +21 -21
  27. package/src/lib/addons/cluster/monitoring/addon.yaml +33 -33
  28. package/src/lib/addons/mongo/addon.yaml +18 -18
  29. package/src/lib/addons/postgres/addon.yaml +39 -39
  30. package/src/lib/addons/redis/addon.yaml +29 -29
  31. package/src/lib/appSecrets.js +60 -60
  32. package/src/lib/clusterAddons.js +59 -59
  33. package/src/lib/clusterProvision.js +171 -170
  34. package/src/lib/configSchema.js +103 -103
  35. package/src/lib/constants.js +16 -16
  36. package/src/lib/engine.js +36 -20
  37. package/src/lib/exec.js +77 -77
  38. package/src/lib/hostsFile.js +79 -79
  39. package/src/lib/loadConfig.js +51 -51
  40. package/src/lib/logger.js +9 -9
  41. package/src/lib/manifestGen.js +210 -210
  42. package/src/lib/paths.js +41 -41
  43. package/src/lib/podman.js +72 -72
  44. package/src/lib/profileStore.js +134 -134
  45. package/src/lib/projectContext.js +32 -32
  46. package/src/lib/prompt.js +97 -97
  47. package/src/lib/toolInstall.js +30 -30
  48. package/src/lib/ui.js +50 -50
  49. package/src/lib/updateCheck.js +84 -0
  50. package/src/lib/updateCheckWorker.js +46 -0
  51. package/src/lib/workloadReadiness.js +35 -35
  52. package/templates/Tiltfile.tmpl +35 -35
package/README.md CHANGED
@@ -1,431 +1,398 @@
1
- # localctl
2
-
3
- 📖 **[Full documentation site](https://psilvmoreira.github.io/localctl/)** — same content as
4
- below, plus diagrams, search, and a nicer read. Update the `USERNAME`/`repo_url` placeholders in
5
- `mkdocs.yml` and this line once this repo has a real GitHub remote (see [Docs
6
- Site](#docs-site) below for how it's built/deployed).
7
-
8
- A local Kubernetes dev platform for macOS and Windows: one shared cluster, one `*.local.test`
9
- subdomain per app, config-driven deploys, live-reload via Tilt, HTTPS everywhere, and a single
10
- CLI — `localctl` — that owns the whole lifecycle from first install to full teardown.
11
-
12
- Point it at any app repo, describe the app in a small JSON file, and `localctl app up` builds it,
13
- deploys it, wires up its subdomain and TLS certificate, and live-syncs your code on every save.
14
-
15
- ```
16
- $ localctl app up
17
- ok orders-api -> https://orders.local.test
18
- ```
19
-
20
- ## Table of contents
21
-
22
- - [Features](#features)
23
- - [Requirements](#requirements)
24
- - [Installing the CLI](#installing-the-cli)
25
- - [Quickstart: deploy your first app](#quickstart-deploy-your-first-app)
26
- - [CLI reference](#cli-reference)
27
- - [App configuration](#app-configuration)
28
- - [Multiple projects](#multiple-projects)
29
- - [Architecture](#architecture)
30
- - [Debugging](#debugging)
31
- - [Adding tools (databases, logging, more)](#adding-tools-databases-logging-more)
32
- - [Uninstalling](#uninstalling)
33
- - [Troubleshooting](#troubleshooting)
34
- - [Repo layout](#repo-layout)
35
- - [Documentation index](#documentation-index)
36
- - [Docs Site](#docs-site)
37
-
38
- ## Features
39
-
40
- - **One cluster, one CLI.** A single k3d (k3s-in-Docker) cluster runs every app you're developing
41
- locally. No per-project Kubernetes setup, no VM sprawl.
42
- - **A real subdomain per app.** `myapp.local.test`, `orders.local.test`, whatever you name it —
43
- routed by Traefik, no port-juggling, no `localhost:3000` vs `localhost:3001` confusion.
44
- - **HTTPS by default.** A locally-trusted wildcard certificate (via `mkcert`) is wired into the
45
- cluster's ingress automatically. Every app gets a green padlock, not a browser warning.
46
- - **Config-driven, not YAML-driven.** Each app repo gets one `.local/config.json`. `localctl`
47
- generates every Kubernetes manifest from it — you never hand-write or hand-edit Kubernetes YAML.
48
- - **Fast inner loop.** Tilt builds your image, pushes it to a local registry, deploys it, and
49
- live-syncs changed files straight into the running container — no full rebuild for every edit.
50
- - **Debugger-friendly.** Declare a debug port in config and it's forwarded to `localhost`
51
- automatically. Node, Python, Go, Java, and .NET patterns are documented and ready to copy.
52
- - **Cross-platform, including Podman.** Works with Docker Desktop, Podman Desktop, or Rancher
53
- Desktop. Podman-specific quirks (BuildKit protocol, insecure-registry trust) are detected and
54
- handled automatically — you don't need to know they exist.
55
- - **Real Helm charts, zero JS.** Per-app databases (Postgres, Redis, MongoDB via Bitnami's own
56
- charts) and cluster-wide addons (a Grafana/Loki logging stack, a Prometheus metrics stack) are
57
- each just one `addon.yaml` file pointing at a published chart — adding a new one is dropping in
58
- a folder, no code required. Each dependency also gets a `wait-for-<type>` init container
59
- automatically, so your app never races one that isn't ready yet.
60
- - **Real readiness, not just "the process started."** Every app gets a TCP (or HTTP, if you set a
61
- path) readiness/liveness probe by default — `kubectl rollout status` succeeding actually means
62
- it's serving.
63
- - **Your own secrets, never plaintext.** API keys and tokens you supply (not addon-generated
64
- credentials) go through the same real-Secret/`secretKeyRef` pipeline — `localctl app secrets
65
- set/show` to write and read them, `.local/config.json` only ever holds the key name.
66
- - **Clean install, clean uninstall.** Nothing is installed with `sudo`-requiring global state where
67
- avoidable, and `localctl uninstall` reverses everything `localctl setup` did.
68
- - **More than one project when you need it.** `default` (one shared cluster) is all most people
69
- ever need, but `localctl profiles new` can spin up an isolated project - either its own separate
70
- cluster, or just a namespace-scoped slice of an existing one - and directory-based resolution
71
- (walk up for the nearest `.local/project.json`, same idea as `.git`/`.claude`) means `app`
72
- commands just know which project they're in.
73
-
74
- ## Requirements
75
-
76
- - **macOS** or **Windows** (PowerShell)
77
- - One running container engine: **Docker Desktop**, **Podman Desktop**, or **Rancher Desktop**
78
- - **Git**, to clone this repo and your app repos
79
- - **Homebrew** (macOS) or **winget** (Windows) — used once to install missing tools
80
-
81
- Everything else (`k3d`, `kubectl`, `tilt`, `mkcert`, `helm`, Node.js) is installed automatically if it's
82
- missing.
83
-
84
- ## Installing the CLI
85
-
86
- **From npm (recommended):**
87
-
88
- ```
89
- npm install -g @localctl/cli
90
- localctl setup
91
- ```
92
-
93
- Pre-releases (from the `beta` branch) are published under the `beta` dist-tag: `npm install -g @localctl/cli@beta`.
94
-
95
- **From source** (to hack on `localctl` itself):
96
-
97
- 1. Clone this repo somewhere permanent (it acts as the control plane for every app you run
98
- locally, so keep it around — don't nest it inside an app repo):
99
-
100
- ```
101
- git clone <this-repo-url> ~/dev/localctl
102
- cd ~/dev/localctl
103
- ```
104
-
105
- 2. Install `localctl` as a standard global npm package:
106
-
107
- ```
108
- cd cli
109
- npm install
110
- npm install -g .
111
- ```
112
-
113
- > **Hit `EACCES`?** That means npm's global install directory isn't writable by your user —
114
- > common on macOS when Node wasn't installed through a version manager. Fix it once, for every
115
- > future global npm install, not just this one ([npm's own documented fix](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally)):
116
- > ```
117
- > mkdir ~/.npm-global
118
- > npm config set prefix ~/.npm-global
119
- > echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.zshrc # or ~/.bash_profile
120
- > source ~/.zshrc
121
- > ```
122
- > Then re-run `npm install -g .` from `cli/`.
123
-
124
- 3. Run the actual infra setup:
125
-
126
- ```
127
- localctl setup
128
- ```
129
-
130
- This detects your container engine, installs `k3d`/`kubectl`/`tilt`/`mkcert`/`helm` if missing,
131
- creates the local cluster, starts the local image registry, generates and trusts the
132
- `*.local.test` TLS certificate, and syncs your hosts file.
133
-
134
- You'll see up to two password prompts during this step — both expected, both one-time:
135
- - **mkcert** asking to trust its local certificate authority (macOS Touch ID/password prompt)
136
- - **sudo/Administrator** asking to write the `*.local.test` entries to your hosts file
137
-
138
- 4. Confirm it worked:
139
-
140
- ```
141
- localctl doctor
142
- ```
143
-
144
- Every line should read `[ok]`.
145
-
146
- From here on, `localctl` is a normal globally-installed command:
147
-
148
- ```
149
- localctl setup # re-check or repair the infra at any time — fully idempotent
150
- localctl uninstall # tear the infra back down (see Uninstalling below)
151
- ```
152
-
153
- <details>
154
- <summary>Alternative: install without touching npm's global config</summary>
155
-
156
- If you'd rather not change npm's global prefix, `bin/bootstrap.sh` (macOS) / `bootstrap.ps1`
157
- (Windows) install `localctl` as a self-contained PATH shim in `~/.localctl/bin` instead of a real
158
- npm global package — no `npm install -g`, no `EACCES` possible, same `localctl` command either
159
- way:
160
-
161
- ```
162
- ./bin/bootstrap.sh # macOS — installs the shim, then runs `localctl setup` for you
163
- ./bin/bootstrap.ps1 # Windows (PowerShell)
164
- ```
165
-
166
- Uninstall the shim version with `./bin/uninstall.sh` / `uninstall.ps1` instead of
167
- `npm uninstall -g` (see [Uninstalling](#uninstalling)).
168
- </details>
169
-
170
- ## Quickstart: deploy your first app
171
-
172
- Try it immediately with the bundled example, no app of your own required:
173
-
174
- ```
175
- cd examples/node-app
176
- localctl app up
177
- ```
178
-
179
- `localctl app up` runs in the background by default — it prints the app's real URL once the
180
- deployment is ready and hands control of the terminal straight back to you, so closing that
181
- terminal doesn't stop the app:
182
-
183
- ```
184
- $ localctl app up
185
- > Waiting for the deployment to become ready...
186
- deployment "node-app" successfully rolled out
187
-
188
- ok node-app -> https://node-app.local.test
189
- debug: localhost:9229
190
- logs: localctl app logs node-app -f
191
- status: localctl app status
192
- stop: localctl app down
193
- ```
194
-
195
- Open <https://node-app.local.test>. Live-sync keeps working in the background exactly as if it
196
- were running in the foreground — edit a file under any path listed in `"sync"` and it updates
197
- without a full rebuild, as long as something inside the container watches for the change and
198
- restarts (`nodemon`, `uvicorn --reload`, etc. — see [Debugging](#debugging) and
199
- `examples/node-app/Dockerfile` for a working pattern).
200
-
201
- Want the interactive Tilt UI instead (build logs streaming live in your terminal)? Add `-f`:
202
-
203
- ```
204
- localctl app up -f
205
- ```
206
-
207
- To use it with your own app:
208
-
209
- 1. From inside your app's repo:
210
-
211
- ```
212
- localctl app new
213
- ```
214
-
215
- This scaffolds `.local/config.json` and a generic `Tiltfile`. `localctl app new` asks for an app
216
- name and port; edit `.local/config.json` afterward for anything else (env vars, database
217
- dependencies, debug settings — see [App configuration](#app-configuration)).
218
-
219
- 2. Deploy it: `localctl app up` (see above).
220
-
221
- 3. Check on it any time. Run from inside the app's own repo (or `-a <name>` from anywhere) for
222
- that app's detail:
223
-
224
- ```
225
- $ localctl app status
226
- node-app
227
- namespace: node-app
228
- ready: 1/1
229
- url: https://node-app.local.test
230
- debug: localhost:9229
231
- dev loop: running (pid 4815)
232
- dependencies:
233
- node-app-postgres 1/1
234
- ```
235
-
236
- From anywhere else (or with `-A`/`--all`), it's a table of every app:
237
-
238
- ```
239
- $ localctl app status
240
- APP NAMESPACE READY URL DEV LOOP
241
- node-app node-app 1/1 https://node-app.local.test running (pid 4815)
242
- ```
243
-
244
- 4. When you're done:
245
-
246
- ```
247
- localctl app down
248
- ```
249
-
250
- Stops the background dev loop (if any) and tears down just this app; the shared cluster keeps
251
- running for everything else.
252
-
253
- ## CLI reference
254
-
255
- | Command | Purpose |
256
- |---|---|
257
- | `localctl setup` | Install/repair the `default` project's infra (container engine, cluster, TLS, hosts). Idempotent. |
258
- | `localctl uninstall [-y]` | Remove everything `setup`/`profiles` set up: every project's cluster, registries, hosts entries, certs/state. |
259
- | `localctl doctor` | Quick health check of the active project's environment. |
260
- | `localctl app new` | Scaffold `.local/config.json` + `Tiltfile` in the current app repo. |
261
- | `localctl app up [-f]` | Build, deploy, and live-reload the app in the current directory. Runs detached in the background by default (survives closing the terminal) and prints the app's URL once ready; `-f`/`--foreground` runs the interactive Tilt UI instead. |
262
- | `localctl app down` | Stop the background dev loop (if any) and tear down the app deployed from the current directory. |
263
- | `localctl app reload [-a <name>]` | Force an immediate rebuild/redeploy of a running background dev loop, without waiting on a file change. |
264
- | `localctl app status [-a <name>] [-A]` | App detail (readiness, URL, debug port, dependencies, dev-loop state) when run inside an app repo or with `-a <name>`; a table of every app otherwise, or always with `-A`/`--all`. |
265
- | `localctl app logs <name> [-f]` | Tail logs for a deployed app. |
266
- | `localctl app exec [cmd...]` | Shell into the running app's pod (`kubectl exec`); defaults to `sh`. |
267
- | `localctl app prune [-y]` | Permanently delete an already-torn-down app's leftover data (dependency PVCs, secrets) - `app down` deliberately leaves these alone. |
268
- | `localctl app secrets set <KEY> [value]` | Set your own secret value (hidden prompt if not given inline) - stored as a real k8s Secret, never in `.local/config.json`. |
269
- | `localctl app secrets list` / `show [KEY]` / `unset <KEY>` | List which keys are set, reveal actual value(s), or remove one. |
270
- | `localctl hosts sync` / `list` | Manage the `*.local.test` entries in your hosts file. |
271
- | `localctl addons list` / `enable <name>` / `disable <name>` | Manage cluster-wide addons (e.g. `logging`, `monitoring`). |
272
- | `localctl addons status` | Every addon instance in the active project's cluster — per-app dependencies and cluster-wide addons — shown separately from your actual apps. |
273
- | `localctl profiles new <name>` | Create a project - its own cluster (Main project), or namespace-scoped under an existing one. |
274
- | `localctl profiles list` | List every project this machine knows about, nested under its Main (cluster) project. |
275
- | `localctl profiles switch <name>` | Make a project active - brings its cluster up if needed, applies its exclusive-teardown setting. |
276
- | `localctl profiles status` | Show the currently active project. |
277
-
278
- Run `localctl <command> --help` for options on any of these.
279
-
280
- ## App configuration
281
-
282
- Everything about how an app deploys locally lives in that app repo's `.local/config.json`:
283
-
284
- ```json
285
- {
286
- "name": "orders-api",
287
- "subdomain": "orders",
288
- "port": 3000,
289
- "build": { "context": ".", "dockerfile": "Dockerfile" },
290
- "env": { "NODE_ENV": "development" },
291
- "debug": { "enabled": true, "port": 9229, "type": "node" },
292
- "dependencies": [{ "type": "postgres", "database": "orders" }],
293
- "sync": [{ "localPath": "./src", "remotePath": "/app/src" }],
294
- "tls": true
295
- }
296
- ```
297
-
298
- This resolves to `https://orders.local.test`, with a dedicated Postgres instance deployed
299
- alongside it. Point your editor's JSON schema support at `schema/app.schema.json` for
300
- autocomplete and validation as you type — `localctl app new` sets this up automatically via
301
- `"$schema"`.
302
-
303
- Full field-by-field reference: **[docs/config-schema.md](./docs/config-schema.md)**.
304
-
305
- ## Multiple projects
306
-
307
- `default` is one shared cluster for everything - fine until you need real isolation (a second
308
- client's stack, a "staging-like" environment, or just apps you don't want sharing a cluster with
309
- your main work). From the folder you want it to apply to:
310
-
311
- ```
312
- localctl profiles new staging
313
- ```
314
-
315
- Choose either your own cluster (a Main project) or a namespace-scoped slice of an existing one, and
316
- it writes `.local/project.json` there - every app under that folder, and any subfolder below it,
317
- now resolves to `staging` automatically, the same directory walk-up `.git`/`.claude` use.
318
- `localctl profiles list` / `status` / `switch <name>` manage which project is current.
319
-
320
- Full mechanism (exclusive vs. concurrent clusters, namespace scoping, port handling):
321
- **[docs/architecture.md#multi-project-support](./docs/architecture.md#multi-project-support)**.
322
-
323
- ## Architecture
324
-
325
- k3d cluster (Traefik ingress + local registry) → Tilt (build, push, live-sync) → `localctl`
326
- (config validation, manifest generation, infra lifecycle) → your app repo's
327
- `.local/config.json`. Nothing about an app's Kubernetes shape is hand-written; it's all generated
328
- from that one file by `cli/src/lib/manifestGen.js`.
329
-
330
- Full breakdown of every component and why it was chosen (including the Podman-specific handling):
331
- **[docs/architecture.md](./docs/architecture.md)**.
332
-
333
- ## Debugging
334
-
335
- Set `"debug": { "enabled": true, "port": <port>, "type": "<node|python|go|java|dotnet>" }` in
336
- `.local/config.json` and the port is forwarded to `localhost` automatically while `localctl app up` is
337
- running — attach your debugger like you would to any local process. Per-language Dockerfile and
338
- `launch.json` patterns (including the live-reload watcher each language needs — `nodemon`,
339
- `uvicorn --reload`, `air`, Spring Boot DevTools): **[docs/debugging.md](./docs/debugging.md)**.
340
-
341
- ## Adding tools (databases, logging, more)
342
-
343
- Every addon is one folder with one `addon.yaml` pointing at a real, published Helm chart — no JS.
344
-
345
- - **Per-app dependencies** (Postgres, Redis, MongoDB today, via Bitnami's own charts) are declared
346
- in an app's `"dependencies"` array and deployed alongside it, isolated per app, with connection
347
- details (host, port, credentials via a real k8s Secret) auto-injected into its container.
348
- - **Cluster-wide addons**: `logging` (Grafana Labs' own loki-stack chart, at
349
- `https://grafana.local.test` once enabled, dashboard auto-provisioned from a file in the addon's
350
- `config/` folder) and `monitoring` (prometheus-community's own Prometheus chart, at
351
- `https://monitoring.local.test`) - shared infrastructure turned on once via `localctl addons
352
- enable <name>`.
353
-
354
- Adding a new one — a message broker, a tracing backend, whatever's next — means finding its Helm
355
- chart and writing one `addon.yaml`: **[docs/adding-tools.md](./docs/adding-tools.md)**.
356
-
357
- ## Uninstalling
358
-
359
- ```
360
- localctl uninstall # every project's cluster/registry, Podman VM config, hosts entries, certs/state
361
- npm uninstall -g @localctl/cli # + the CLI itself, if you installed it via npm
362
- ./bin/uninstall.sh # or uninstall.ps1 # + the CLI itself, if you installed it via bin/bootstrap.sh's shim
363
- ```
364
-
365
- Prompts for confirmation (skip with `-y`/`--yes`); every step is best-effort and safe to re-run.
366
- Deliberately left untouched: `k3d`/`kubectl`/`tilt`/`mkcert`/`node` themselves and mkcert's root CA
367
- trust (other projects on your machine may depend on either) — both are printed with the exact
368
- command to remove them yourself if you want them gone too.
369
-
370
- Full details: **[docs/uninstalling.md](./docs/uninstalling.md)**.
371
-
372
- ## Troubleshooting
373
-
374
- Hit something unexpected — a Podman-specific build/push error, a port conflict, a certificate
375
- warning, `ImagePullBackOff`, stale live-sync? Check
376
- **[docs/troubleshooting.md](./docs/troubleshooting.md)** first; it covers every issue found while
377
- building and testing this repo, with the actual cause and fix for each.
378
-
379
- ## Repo layout
380
-
381
- ```
382
- localctl/
383
- ├── bin/ thin installer stubs (macOS + Windows) - install Node/the CLI, then
384
- │ hand off to `localctl setup`/`localctl uninstall`
385
- ├── cluster/ k3d cluster definition + cluster-level manifests (TLS store, namespaces)
386
- ├── cli/ the localctl Node.js CLI
387
- │ ├── src/commands/ one file per CLI subcommand (setup, uninstall, up, new, profiles, ...)
388
- │ ├── src/lib/ config validation, manifest generation, project/cluster/Podman/hosts handling
389
- │ │ └── addons/ per-app (postgres/redis/mongo) + cluster/ (logging, monitoring) addons
390
- │ └── templates/ generic Tiltfile copied into app repos by `localctl app new`
391
- ├── schema/ JSON Schema for .local/config.json (editor autocomplete)
392
- ├── examples/ ready-to-run sample apps (Node.js + Postgres, Python + Redis, .NET)
393
- └── docs/ full documentation - also published as the docs site, see below
394
- ```
395
-
396
- ## Documentation index
397
-
398
- - [docs/index.md](./docs/index.md) — documentation site home
399
- - [docs/getting-started.md](./docs/getting-started.md) — the install/quickstart flow, in more detail
400
- - [docs/architecture.md](./docs/architecture.md) — every component and why it was chosen, with diagrams
401
- - [docs/config-schema.md](./docs/config-schema.md) — full `.local/config.json` reference
402
- - [docs/adding-tools.md](./docs/adding-tools.md) — add new per-app or cluster-wide addons
403
- - [docs/debugging.md](./docs/debugging.md) — attach a debugger, per language
404
- - [docs/uninstalling.md](./docs/uninstalling.md) — full teardown reference
405
- - [docs/troubleshooting.md](./docs/troubleshooting.md) — known issues and fixes
406
-
407
- ## Docs Site
408
-
409
- Everything under `docs/` is also published as a [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/)
410
- site — search, dark/light mode, and the Mermaid diagrams in [Architecture](./docs/architecture.md)
411
- rendered properly (GitHub renders Mermaid too, but the site's theme is nicer for reading).
412
-
413
- **One-time setup**, once this repo has a real GitHub remote:
414
-
415
- 1. In `mkdocs.yml`, replace every `USERNAME` with your GitHub username (or org), and in this
416
- README's link at the top.
417
- 2. Push to GitHub, then in the repo's **Settings → Pages**, set **Source** to **Deploy from a
418
- branch** and **Branch** to **`gh-pages`** / **`/ (root)`**. (The first push of
419
- `.github/workflows/docs.yml` below creates that branch for you — the option won't appear in the
420
- dropdown until after that first run.)
421
-
422
- From then on, **every push to `master`** rebuilds and redeploys the site automatically
423
- (`.github/workflows/docs.yml`) — nothing to run by hand.
424
-
425
- To preview locally before pushing:
426
-
427
- ```bash
428
- python3 -m venv .venv && source .venv/bin/activate
429
- pip install mkdocs-material
430
- mkdocs serve # http://127.0.0.1:8000, live-reloads on save
431
- ```
1
+ <div align="center">
2
+
3
+ <img src="https://raw.githubusercontent.com/psilvmoreira/localctl/master/docs/assets/favicon.svg" alt="localctl logo" width="96" height="96">
4
+
5
+ # localctl
6
+
7
+ **Local Kubernetes for app developers. One CLI, one cluster, a real HTTPS subdomain per app.**
8
+
9
+ [![npm version](https://img.shields.io/npm/v/@localctl/cli?logo=npm&color=cb3837)](https://www.npmjs.com/package/@localctl/cli)
10
+ [![npm downloads](https://img.shields.io/npm/dm/@localctl/cli?color=cb3837)](https://www.npmjs.com/package/@localctl/cli)
11
+ [![Node.js](https://img.shields.io/node/v/@localctl/cli?logo=node.js&logoColor=white&color=5fa04e)](https://nodejs.org)
12
+ [![License: MIT](https://img.shields.io/github/license/psilvmoreira/localctl?color=blue)](https://github.com/psilvmoreira/localctl/blob/master/LICENSE)
13
+ <br>
14
+ [![Release](https://github.com/psilvmoreira/localctl/actions/workflows/release.yml/badge.svg?branch=master)](https://github.com/psilvmoreira/localctl/actions/workflows/release.yml)
15
+ [![Tests](https://github.com/psilvmoreira/localctl/actions/workflows/ci.yml/badge.svg)](https://github.com/psilvmoreira/localctl/actions/workflows/ci.yml)
16
+ [![Docs](https://github.com/psilvmoreira/localctl/actions/workflows/docs.yml/badge.svg?branch=master)](https://psilvmoreira.github.io/localctl/)
17
+ [![semantic-release](https://img.shields.io/badge/semantic--release-conventionalcommits-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release)
18
+
19
+ [**Documentation**](https://psilvmoreira.github.io/localctl/) ·
20
+ [**Getting started**](https://psilvmoreira.github.io/localctl/getting-started/) ·
21
+ [**Releases**](https://github.com/psilvmoreira/localctl/releases) ·
22
+ [**Report a bug**](https://github.com/psilvmoreira/localctl/issues/new)
23
+
24
+ </div>
25
+
26
+ ```console
27
+ $ npm install -g @localctl/cli
28
+ $ localctl setup
29
+ $ cd my-app && localctl app new && localctl app up
30
+
31
+ ok my-app -> https://my-app.local.test
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Table of contents
37
+
38
+ - [Why localctl](#why-localctl)
39
+ - [Requirements](#requirements)
40
+ - [Installation](#installation)
41
+ - [Supported container engines](#supported-container-engines)
42
+ - [Updating](#updating)
43
+ - [Quick start](#quick-start)
44
+ - [Commands](#commands)
45
+ - [Configuration](#configuration)
46
+ - [Addons](#addons)
47
+ - [How it works](#how-it-works)
48
+ - [Documentation](#documentation)
49
+ - [Releases and versioning](#releases-and-versioning)
50
+ - [Contributing](#contributing)
51
+ - [Uninstalling](#uninstalling)
52
+ - [License](#license)
53
+
54
+ ## Why localctl
55
+
56
+ Running several apps on Kubernetes locally usually means port juggling, hand-written YAML,
57
+ self-signed certificate warnings, and slow rebuilds. `localctl` replaces all of that with one
58
+ JSON file per app.
59
+
60
+ | | |
61
+ |---|---|
62
+ | 🧩 **One cluster, one CLI** | A single [k3d](https://k3d.io) cluster runs every app you develop. No per-project Kubernetes setup. |
63
+ | 🌐 **A subdomain per app** | `orders.local.test`, `billing.local.test`, routed by Traefik. No `localhost:3000` vs `:3001`. |
64
+ | 🔒 **HTTPS by default** | A locally-trusted wildcard certificate (via [mkcert](https://github.com/FiloSottile/mkcert)). Green padlock, no browser warnings. |
65
+ | 📄 **Config, not YAML** | Each app has one `.local/config.json`. Every Kubernetes manifest is generated from it. |
66
+ | ⚡ **Fast inner loop** | [Tilt](https://tilt.dev) builds, deploys, and live-syncs changed files into the running container. |
67
+ | 🐞 **Debugger-friendly** | Debug ports forwarded to `localhost`. Node, Python, Go, Java and .NET patterns included. |
68
+ | 🗄️ **Databases in one line** | Postgres, Redis and MongoDB per app, from real Helm charts, with credentials injected as Secrets. |
69
+ | 🐳 **Podman first** | Podman quirks (BuildKit, insecure registry) are detected and handled automatically. Docker Desktop and Rancher Desktop support is [experimental](#supported-container-engines). |
70
+ | 🧹 **Clean uninstall** | `localctl uninstall` reverses everything `localctl setup` did. |
71
+
72
+ ## Requirements
73
+
74
+ | Requirement | Notes |
75
+ |---|---|
76
+ | **macOS** or **Windows** | Windows commands run in PowerShell. |
77
+ | **Node.js 18+** | Needed to install the CLI from npm. |
78
+ | **Container engine** | [Podman Desktop](https://podman-desktop.io/), running. See [supported container engines](#supported-container-engines). |
79
+ | **Homebrew** or **winget** | Used once by `localctl setup` to install missing tools. |
80
+
81
+ `localctl setup` installs `k3d`, `kubectl`, `tilt`, `mkcert` and `helm` for you if they're missing.
82
+
83
+ ### Supported container engines
84
+
85
+ | Engine | Status |
86
+ |---|---|
87
+ | [Podman Desktop](https://podman-desktop.io/) | ✅ Tested |
88
+ | [Docker Desktop](https://www.docker.com/products/docker-desktop/) | 🧪 Experimental — not yet tested |
89
+ | [Rancher Desktop](https://rancherdesktop.io/) | 🧪 Experimental — not yet tested |
90
+
91
+ Experimental engines are detected and should work, since k3d only needs a Docker-compatible API,
92
+ but they haven't been through a full test cycle yet. `localctl setup` and `localctl doctor` print
93
+ a warning when one is in use. If you try one, please
94
+ [report how it went](https://github.com/psilvmoreira/localctl/issues/new).
95
+
96
+ ## Installation
97
+
98
+ ```sh
99
+ npm install -g @localctl/cli
100
+ localctl setup
101
+ localctl doctor # every line should read [ok]
102
+ ```
103
+
104
+ `localctl setup` detects your container engine, installs missing tools, creates the cluster and
105
+ the local image registry, trusts the `*.local.test` certificate, and updates your hosts file. It
106
+ is idempotent: run it again any time to repair your environment.
107
+
108
+ You'll see up to two one-time prompts during setup: **mkcert** asking to trust its local
109
+ certificate authority, and **sudo/Administrator** to write the `*.local.test` hosts entries.
110
+
111
+ **Pre-releases** are published under the `beta` tag: `npm install -g @localctl/cli@beta`.
112
+
113
+ ### Updating
114
+
115
+ `localctl` checks npm for a new version at most once a day, in the background, and tells you
116
+ when one is available:
117
+
118
+ ```console
119
+ Update available: 0.2.0 -> 0.3.0
120
+ Run npm install -g @localctl/cli to update.
121
+ ```
122
+
123
+ Update with `npm install -g @localctl/cli`. The check never slows a command down, is skipped in
124
+ CI, and can be turned off with `LOCALCTL_NO_UPDATE_CHECK=1`.
125
+
126
+ <details>
127
+ <summary><b>Getting <code>EACCES</code> on <code>npm install -g</code>?</b></summary>
128
+
129
+ npm's global directory isn't writable by your user, which is common on macOS when Node wasn't
130
+ installed through a version manager. Fix it once for every global install
131
+ ([npm's documented fix](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally)):
132
+
133
+ ```sh
134
+ mkdir ~/.npm-global
135
+ npm config set prefix ~/.npm-global
136
+ echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.zshrc # or ~/.bash_profile
137
+ source ~/.zshrc
138
+ ```
139
+
140
+ </details>
141
+
142
+ <details>
143
+ <summary><b>Install from source</b> (to work on localctl itself)</summary>
144
+
145
+ ```sh
146
+ git clone https://github.com/psilvmoreira/localctl.git
147
+ cd localctl/cli
148
+ npm install
149
+ npm install -g .
150
+ localctl setup
151
+ ```
152
+
153
+ Or, without touching npm's global config, use the bootstrap scripts. They install a
154
+ self-contained shim in `~/.localctl/bin` and run `localctl setup` for you:
155
+
156
+ ```sh
157
+ ./bin/bootstrap.sh # macOS
158
+ ./bin/bootstrap.ps1 # Windows (PowerShell)
159
+ ```
160
+
161
+ </details>
162
+
163
+ ## Quick start
164
+
165
+ **1. Scaffold** — from inside your app's repository:
166
+
167
+ ```sh
168
+ localctl app new
169
+ ```
170
+
171
+ This asks for an app name and port, then creates `.local/config.json` and a `Tiltfile`.
172
+
173
+ **2. Deploy:**
174
+
175
+ ```console
176
+ $ localctl app up
177
+ > Waiting for the deployment to become ready...
178
+ deployment "my-app" successfully rolled out
179
+
180
+ ok my-app -> https://my-app.local.test
181
+ debug: localhost:9229
182
+ logs: localctl app logs my-app -f
183
+ status: localctl app status
184
+ stop: localctl app down
185
+ ```
186
+
187
+ The dev loop runs in the background, so closing the terminal doesn't stop it. Edit a file under
188
+ a `sync` path and it updates in the running container without a full rebuild. Use
189
+ `localctl app up -f` for the interactive Tilt UI instead.
190
+
191
+ **3. Check on it:**
192
+
193
+ ```console
194
+ $ localctl app status
195
+ APP NAMESPACE READY URL DEV LOOP
196
+ my-app my-app 1/1 https://my-app.local.test running (pid 4815)
197
+ ```
198
+
199
+ **4. Stop it:**
200
+
201
+ ```sh
202
+ localctl app down
203
+ ```
204
+
205
+ This removes only that app. The shared cluster keeps running for everything else.
206
+
207
+ > **Tip:** no app of your own yet? Clone the repo and try a ready-made example:
208
+ > [`node-app`](https://github.com/psilvmoreira/localctl/tree/master/examples/node-app) (Node.js + Postgres),
209
+ > [`python-app`](https://github.com/psilvmoreira/localctl/tree/master/examples/python-app) (Python + Redis), or
210
+ > [`dotnet-api`](https://github.com/psilvmoreira/localctl/tree/master/examples/dotnet-api) (.NET).
211
+
212
+ ## Commands
213
+
214
+ Run `localctl <command> --help` for every option.
215
+
216
+ <details open>
217
+ <summary><b>Machine setup</b></summary>
218
+
219
+ | Command | Description |
220
+ |---|---|
221
+ | `localctl setup` | Install or repair the infrastructure: container engine, cluster, TLS, hosts. Idempotent. |
222
+ | `localctl doctor` | Health check of the active project's environment. |
223
+ | `localctl hosts sync` / `list` | Manage the `*.local.test` entries in your hosts file. |
224
+ | `localctl uninstall [-y]` | Remove every cluster, registry, hosts entry, certificate and state file `localctl` created. |
225
+
226
+ </details>
227
+
228
+ <details open>
229
+ <summary><b>Apps</b></summary>
230
+
231
+ | Command | Description |
232
+ |---|---|
233
+ | `localctl app new` | Scaffold `.local/config.json` and a `Tiltfile` in the current repository. |
234
+ | `localctl app up [-f]` | Build, deploy and live-reload the app. Background by default; `-f` for the Tilt UI. |
235
+ | `localctl app down` | Stop the dev loop and remove the app. Keeps its data. |
236
+ | `localctl app reload [-a <name>]` | Force a rebuild and redeploy without waiting for a file change. |
237
+ | `localctl app status [-a <name>] [-A]` | One app's detail inside its repository, or a table of every app. |
238
+ | `localctl app logs <name> [-f]` | Show or follow an app's logs. |
239
+ | `localctl app exec [cmd...]` | Open a shell (default `sh`) in the app's pod. |
240
+ | `localctl app prune [-y]` | Delete leftover data (volumes, secrets) of apps already taken down. |
241
+ | `localctl app secrets set <KEY> [value]` | Store a secret as a Kubernetes Secret, never in `config.json`. |
242
+ | `localctl app secrets list` / `show [KEY]` / `unset <KEY>` | List, reveal or remove secrets. |
243
+
244
+ </details>
245
+
246
+ <details open>
247
+ <summary><b>Addons and projects</b></summary>
248
+
249
+ | Command | Description |
250
+ |---|---|
251
+ | `localctl addons list` / `status` | Show available addons and what is running in the cluster. |
252
+ | `localctl addons enable <name>` / `disable <name>` | Turn a cluster-wide addon (`logging`, `monitoring`) on or off. |
253
+ | `localctl profiles new <name>` | Create an isolated project: its own cluster, or a namespace in an existing one. |
254
+ | `localctl profiles list` / `status` | Show every project, or the active one. |
255
+ | `localctl profiles switch <name>` | Make a project active and start its cluster if needed. |
256
+
257
+ </details>
258
+
259
+ ## Configuration
260
+
261
+ Everything about how an app runs locally lives in its `.local/config.json`:
262
+
263
+ ```json
264
+ {
265
+ "name": "orders-api",
266
+ "subdomain": "orders",
267
+ "port": 3000,
268
+ "build": { "context": ".", "dockerfile": "Dockerfile" },
269
+ "env": { "NODE_ENV": "development" },
270
+ "debug": { "enabled": true, "port": 9229, "type": "node" },
271
+ "dependencies": [{ "type": "postgres", "database": "orders" }],
272
+ "sync": [{ "localPath": "./src", "remotePath": "/app/src" }],
273
+ "tls": true
274
+ }
275
+ ```
276
+
277
+ This deploys `orders-api` at `https://orders.local.test`, with its own Postgres instance and the
278
+ Node.js debugger on `localhost:9229`.
279
+
280
+ A [JSON Schema](https://github.com/psilvmoreira/localctl/blob/master/schema/app.schema.json) is
281
+ available for editor autocomplete and validation. Full field reference:
282
+ [**Config schema**](https://psilvmoreira.github.io/localctl/config-schema/).
283
+
284
+ ## Addons
285
+
286
+ Every addon is one `addon.yaml` file pointing at a published Helm chart. No code required.
287
+
288
+ | Addon | Scope | Enable with | Chart |
289
+ |---|---|---|---|
290
+ | `postgres` | Per app | `"dependencies": [{ "type": "postgres" }]` | Bitnami |
291
+ | `redis` | Per app | `"dependencies": [{ "type": "redis" }]` | Bitnami |
292
+ | `mongo` | Per app | `"dependencies": [{ "type": "mongo" }]` | Bitnami |
293
+ | `logging` | Cluster | `localctl addons enable logging` → `https://grafana.local.test` | Grafana `loki-stack` |
294
+ | `monitoring` | Cluster | `localctl addons enable monitoring` → `https://monitoring.local.test` | `prometheus-community` |
295
+
296
+ Per-app dependencies get their connection details injected into the app container, and a
297
+ `wait-for-<type>` init container so the app never starts before its database is ready.
298
+ To add your own addon, see [**Adding tools**](https://psilvmoreira.github.io/localctl/adding-tools/).
299
+
300
+ ## How it works
301
+
302
+ ```
303
+ .local/config.json ──► localctl ──► Kubernetes manifests ──► Tilt ──► k3d cluster
304
+ (your app repo) validates (generated, never builds, Traefik ingress
305
+ + generates hand-written) pushes, + local registry
306
+ live-syncs + *.local.test TLS
307
+ ```
308
+
309
+ `localctl` validates the config, generates every manifest, and manages the cluster lifecycle.
310
+ Tilt handles the build, push and live-sync loop. Full breakdown, including multi-project support
311
+ and Podman handling: [**Architecture**](https://psilvmoreira.github.io/localctl/architecture/).
312
+
313
+ ## Documentation
314
+
315
+ The full documentation lives at **[psilvmoreira.github.io/localctl](https://psilvmoreira.github.io/localctl/)**.
316
+
317
+ | Guide | What's inside |
318
+ |---|---|
319
+ | [Getting started](https://psilvmoreira.github.io/localctl/getting-started/) | Install and first deploy, step by step |
320
+ | [Architecture](https://psilvmoreira.github.io/localctl/architecture/) | Every component, why it was chosen, multi-project support |
321
+ | [Config schema](https://psilvmoreira.github.io/localctl/config-schema/) | Every `.local/config.json` field |
322
+ | [Debugging](https://psilvmoreira.github.io/localctl/debugging/) | Attach a debugger in Node, Python, Go, Java and .NET |
323
+ | [Adding tools](https://psilvmoreira.github.io/localctl/adding-tools/) | Write your own per-app or cluster-wide addon |
324
+ | [Troubleshooting](https://psilvmoreira.github.io/localctl/troubleshooting/) | Known issues, with cause and fix |
325
+ | [Uninstalling](https://psilvmoreira.github.io/localctl/uninstalling/) | Full teardown reference |
326
+ | [Releasing](https://psilvmoreira.github.io/localctl/releasing/) | How versions are cut and published |
327
+
328
+ ## Releases and versioning
329
+
330
+ - Releases are fully automated: every merge to `master` is analysed by
331
+ [semantic-release](https://github.com/semantic-release/semantic-release), which picks the next
332
+ version, publishes to npm and creates a [GitHub Release](https://github.com/psilvmoreira/localctl/releases)
333
+ with notes and the package tarball.
334
+ - Versions follow [Semantic Versioning](https://semver.org). Before `1.0.0`, minor versions may
335
+ contain breaking changes.
336
+ - npm releases are published from GitHub Actions with
337
+ [provenance](https://docs.npmjs.com/generating-provenance-statements). The npm package page links
338
+ each version to the exact commit and workflow run that built it.
339
+
340
+ ## Contributing
341
+
342
+ Issues and pull requests are welcome.
343
+
344
+ 1. Fork the repository and create a branch.
345
+ 2. Make your change and run the tests:
346
+ ```sh
347
+ cd cli
348
+ npm ci
349
+ npm test # every command loads, --version is correct
350
+ npm run test:pack # the published package installs and runs
351
+ ```
352
+ 3. Open a pull request with a [Conventional Commits](https://www.conventionalcommits.org/) title.
353
+ The title decides the next version:
354
+
355
+ | Title | Release |
356
+ |---|---|
357
+ | `fix: ...` | Patch (`0.2.0` → `0.2.1`) |
358
+ | `feat: ...` | Minor (`0.2.1` → `0.3.0`) |
359
+ | `feat!: ...` | Major (`0.3.0` → `1.0.0`) |
360
+ | `docs:` `chore:` `ci:` `refactor:` `test:` | No release |
361
+
362
+ <details>
363
+ <summary><b>Repository layout</b></summary>
364
+
365
+ ```
366
+ localctl/
367
+ ├── cli/ the localctl CLI (published to npm as @localctl/cli)
368
+ │ ├── bin/ executable entry point
369
+ │ ├── src/commands/ one file per command
370
+ │ ├── src/lib/ config validation, manifest generation, cluster/hosts/Podman handling
371
+ │ │ └── addons/ built-in addons (postgres, redis, mongo, logging, monitoring)
372
+ │ ├── templates/ Tiltfile copied into apps by `localctl app new`
373
+ │ └── scripts/ packaging and test scripts
374
+ ├── cluster/manifests/ cluster-level manifests (TLS store, addon namespace)
375
+ ├── schema/ JSON Schema for .local/config.json
376
+ ├── examples/ ready-to-run sample apps
377
+ ├── docs/ documentation site (MkDocs Material, deployed to GitHub Pages)
378
+ └── bin/ bootstrap and uninstall scripts for source installs
379
+ ```
380
+
381
+ </details>
382
+
383
+ ## Uninstalling
384
+
385
+ ```sh
386
+ localctl uninstall # clusters, registries, hosts entries, certificates, state
387
+ npm uninstall -g @localctl/cli # the CLI itself
388
+ ```
389
+
390
+ `localctl uninstall` asks for confirmation (skip with `-y`) and is safe to re-run. It leaves the
391
+ shared tools (`k3d`, `kubectl`, `tilt`, `mkcert`, `node`) and mkcert's root CA installed, since
392
+ other projects may use them. It prints the exact commands to remove them too. Installed from
393
+ source with the bootstrap script? Use `./bin/uninstall.sh` or `./bin/uninstall.ps1` instead.
394
+ Details: [**Uninstalling**](https://psilvmoreira.github.io/localctl/uninstalling/).
395
+
396
+ ## License
397
+
398
+ [MIT](https://github.com/psilvmoreira/localctl/blob/master/LICENSE) © Pedro Moreira