@dmgnr/kuber 1.3.0 → 2.1.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.
- package/README.md +439 -91
- package/dist/index.js +191 -194
- package/package.json +4 -3
- package/types.d.ts +0 -7
package/README.md
CHANGED
|
@@ -1,45 +1,280 @@
|
|
|
1
1
|
# kuber
|
|
2
2
|
|
|
3
|
-
`kuber` is
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- Reads `compose.yml` or `docker-compose.yml`
|
|
10
|
-
- Converts supported Compose services into Kubernetes resources
|
|
11
|
-
- Applies those resources into a namespace named after the current directory
|
|
12
|
-
- Turns `env_file` entries into Kubernetes Secrets and mounts them through `envFrom`
|
|
13
|
-
- Translates file mounts into ConfigMaps and directory/volume mounts into PVC-backed volumes
|
|
14
|
-
- Builds images on the remote builder by syncing:
|
|
15
|
-
- committed git state
|
|
16
|
-
- tracked local diffs
|
|
17
|
-
- untracked files
|
|
18
|
-
- ignored `.env*` files
|
|
19
|
-
- Supports host-based `ports` syntax that renders Kubernetes `Ingress` rules
|
|
20
|
-
- Supports managed Postgres claims through special pseudo-volumes such as `postgresql:app`
|
|
21
|
-
- Supports managed S3 buckets and credentials through pseudo-volumes such as `s3:app`
|
|
3
|
+
`kuber` is a Docker Compose to Kubernetes translation layer backed by a
|
|
4
|
+
self-hosted management service (`kuber-server`). It reads a local Compose file,
|
|
5
|
+
renders Kubernetes resources, and applies them to the cluster through an
|
|
6
|
+
authenticated v2 API. Image builds happen inside the cluster with rootless
|
|
7
|
+
BuildKit, so the workstation needs neither Docker nor `kubectl`.
|
|
22
8
|
|
|
23
|
-
##
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
workstation (kuber CLI) ──HTTPS──▶ kuber-server (in-cluster pod)
|
|
13
|
+
│
|
|
14
|
+
├─ CAS (content-addressed blobs on RWX PVC)
|
|
15
|
+
├─ BuildKit Jobs (rootless) ──▶ registry
|
|
16
|
+
└─ Kubernetes API (RBAC-scoped)
|
|
17
|
+
```
|
|
24
18
|
|
|
25
|
-
|
|
19
|
+
The CLI authenticates to the server with a Bearer token acquired by `kuber
|
|
20
|
+
login`. It snapshots the repository into a content-addressed workspace, uploads
|
|
21
|
+
only the blobs the server is missing, and submits a build request. The server
|
|
22
|
+
materializes that workspace onto a shared RWX PVC and runs a rootless BuildKit
|
|
23
|
+
Job that builds and pushes the image, then the CLI pins the resulting immutable
|
|
24
|
+
registry digest into the rendered Deployment.
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
The server also owns reconciliation: it plans, applies, and prunes resources in
|
|
27
|
+
a per-project namespace, reconciles managed Postgres and S3 claims, rolls
|
|
28
|
+
deployments back, streams logs, and exposes interactive `exec` sessions over a
|
|
29
|
+
WebSocket.
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
31
|
+
## Environment Assumptions
|
|
32
|
+
|
|
33
|
+
kuber targets a specific self-hosted cluster and workstation setup. It is not
|
|
34
|
+
intended to run unchanged against an arbitrary Kubernetes environment. The
|
|
35
|
+
expected setup:
|
|
36
|
+
|
|
37
|
+
- an account on the `kuber-server` management API
|
|
38
|
+
- a working ClusterRole/Role (see [Server Deployment](#server-deployment))
|
|
39
|
+
- a registry the in-cluster BuildKit can push to
|
|
32
40
|
|
|
33
41
|
Not required locally:
|
|
34
42
|
|
|
35
43
|
- Docker
|
|
36
44
|
- `kubectl`
|
|
37
45
|
|
|
38
|
-
|
|
46
|
+
## API Origin
|
|
47
|
+
|
|
48
|
+
The v2 management API has one hard-coded origin:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
https://kuber.astrxl.dev/api/v2
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Authentication
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
kuber login dmgnr
|
|
58
|
+
kuber login dmgnr --persist
|
|
59
|
+
kuber whoami
|
|
60
|
+
kuber logout
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The default login is stored with mode `0600` under
|
|
64
|
+
`$XDG_RUNTIME_DIR/kuber/session.json` and disappears with the user runtime
|
|
65
|
+
directory. `--persist` instead uses `$XDG_CONFIG_HOME/kuber/session.json`, or
|
|
66
|
+
`~/.config/kuber/session.json` when `XDG_CONFIG_HOME` is unset. Password input
|
|
67
|
+
is never echoed. Runtime sessions last 24 hours and persistent sessions last 30
|
|
68
|
+
days; `logout` revokes the server-side session. `readSession` falls back to the
|
|
69
|
+
persistent file when no runtime session exists.
|
|
70
|
+
|
|
71
|
+
The default login is scoped to the current user and the authenticated identity
|
|
72
|
+
is available to every v2 command. `login`, `logout`, and `whoami` are the only
|
|
73
|
+
commands that run without a loaded project configuration.
|
|
74
|
+
|
|
75
|
+
### Roles and Authorization
|
|
76
|
+
|
|
77
|
+
The server grants capabilities through three roles:
|
|
78
|
+
|
|
79
|
+
- `viewer` — read-only cluster access (`kubernetes:read`)
|
|
80
|
+
- `operator` — `viewer` plus `kubernetes:write` and `kubernetes:exec`
|
|
81
|
+
- `admin` — all capabilities, including user administration
|
|
82
|
+
(`users:read`, `users:write`, `sessions:revoke`)
|
|
83
|
+
|
|
84
|
+
Administer users with the `users` command tree:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
kuber users ls
|
|
88
|
+
kuber users add dmgnr --roles admin
|
|
89
|
+
kuber users update dmgnr --roles operator
|
|
90
|
+
kuber users update dmgnr --password
|
|
91
|
+
kuber users disable dmgnr
|
|
92
|
+
kuber users enable dmgnr
|
|
93
|
+
kuber users delete dmgnr
|
|
94
|
+
kuber users revoke dmgnr
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`users add`, `update --password`, and `delete` prompt for password / written
|
|
98
|
+
confirmation on an interactive terminal. Passwords are hashed with Argon2id on
|
|
99
|
+
the server and never stored in plaintext. Updating a user's roles or password
|
|
100
|
+
revokes all of that user's active sessions.
|
|
101
|
+
|
|
102
|
+
Inspect server-side operations and the audit trail:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
kuber operations ls
|
|
106
|
+
kuber operations get <operation-id>
|
|
107
|
+
kuber audit ls
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Server Deployment
|
|
111
|
+
|
|
112
|
+
The repository's `compose.yml` owns the `kuber-system` namespace, the
|
|
113
|
+
`kuber-server` image, its Deployment, Service, and Ingress. `.kuberrc.ts`
|
|
114
|
+
extends the rendered manifests with the server's ServiceAccount and RBAC:
|
|
115
|
+
|
|
116
|
+
- a namespaced `Role`/`RoleBinding` (`kuber-server-auth`) for the `kuber-system`
|
|
117
|
+
Secrets, ConfigMaps, Pods, Jobs, and Leases the server itself reads and writes
|
|
118
|
+
- a `ClusterRole`/`ClusterRoleBinding` (`kuber-server-manager`) granting the
|
|
119
|
+
cross-namespace verbs it needs to manage user projects
|
|
120
|
+
|
|
121
|
+
`compose.yml` runs the server as a non-root user (`runAsUser`/`runAsGroup`
|
|
122
|
+
`1000`), drops all Linux capabilities, uses a read-only root filesystem with a
|
|
123
|
+
`RuntimeDefault` seccomp profile, and backs `/data` with a Longhorn PVC
|
|
124
|
+
(`kuber-build-data`). The PVC hosts the CAS, materialized workspaces, and
|
|
125
|
+
resumable upload bytes, and is shared with BuildKit Jobs.
|
|
126
|
+
|
|
127
|
+
Deploy the server with kuber itself:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
KUBER_BOOTSTRAP_PASSWORD='replace-me' kuber up
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The server creates an `admin` user (from `KUBER_BOOTSTRAP_USERNAME`, default
|
|
134
|
+
`dmgnr`) on first boot only if that user does not already exist. The bootstrap
|
|
135
|
+
Secret is only rendered while `KUBER_BOOTSTRAP_PASSWORD` is set. After logging
|
|
136
|
+
in successfully, reconcile without that variable and restart once so kuber
|
|
137
|
+
removes the stale bootstrap Secret and the password leaves the pod environment:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
kuber up --no-b
|
|
141
|
+
kuber restart kuber-server
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
See [Account Recovery](#account-recovery) for what to do if you are locked out.
|
|
145
|
+
|
|
146
|
+
### Security Constraints
|
|
147
|
+
|
|
148
|
+
Because the server's ServiceAccount is scoped by the RBAC in `.kuberrc.ts`, it
|
|
149
|
+
can only act on the resources kuber manages. The management layer additionally
|
|
150
|
+
enforces ownership, so the server refuses to mutate resources that do not carry
|
|
151
|
+
kuber's workspace labels. Deleting a resource requires its UID as an optimistic
|
|
152
|
+
concurrency precondition, and workspace deletion also requires the namespace
|
|
153
|
+
UID. Namespaces that are not owned by kuber, or owned by a different workspace,
|
|
154
|
+
are never mutated (see [Workspaces and Migration](#workspaces-and-migration)).
|
|
155
|
+
|
|
156
|
+
## Workspaces and Migration
|
|
157
|
+
|
|
158
|
+
Each project maps to a Kubernetes namespace derived from the current working
|
|
159
|
+
directory. The CLI records a "workspace" on the server keyed by project name and
|
|
160
|
+
borrows the namespace UID to guarantee it owns the namespace before reconciling.
|
|
161
|
+
Adopting existing resources relabels them (Server-Side Apply) only when they are
|
|
162
|
+
already managed by kuber and not owned by another workspace.
|
|
163
|
+
|
|
164
|
+
`up` refuses to mutate a namespace when:
|
|
165
|
+
|
|
166
|
+
- the namespace already exists but does not carry kuber's managed-by label
|
|
167
|
+
(`external`), or
|
|
168
|
+
- the namespace is labeled for a different workspace UID
|
|
169
|
+
(`different-workspace`)
|
|
170
|
+
|
|
171
|
+
In those cases the CLI prints a hint to `POST
|
|
172
|
+
/workspaces/<project>/adopt` with the namespace UID to complete a safe,
|
|
173
|
+
explicit adoption. The platform namespace (`kuber-system`) is adopted through
|
|
174
|
+
the admin-only platform adoption route.
|
|
175
|
+
|
|
176
|
+
Workspace state, revisions, operations, and audit events are stored as Secrets
|
|
177
|
+
and ConfigMaps in `kuber-system` keyed by kuber's `kuber.astrxl.dev/type`
|
|
178
|
+
label. Workspace updates are optimistic (If-Match on resource version) and
|
|
179
|
+
immutable revisions are recorded so history survives. Expired sessions are
|
|
180
|
+
cleaned up on an interval, and stale operations are marked failed on server
|
|
181
|
+
startup recovery.
|
|
182
|
+
|
|
183
|
+
### Migration Behavior
|
|
184
|
+
|
|
185
|
+
`kuber up` is idempotent and safe to re-run. Each run:
|
|
186
|
+
|
|
187
|
+
1. snapshots the workspace and uploads missing blobs to the server CAS,
|
|
188
|
+
2. ensures the workspace record (creating or updating it with an optimistic
|
|
189
|
+
If-Match),
|
|
190
|
+
3. adopts the namespace and its kuber-managed resources,
|
|
191
|
+
4. reconciles managed Postgres and S3 claims,
|
|
192
|
+
5. renders manifests, plans the diff, applies desired resources, waits for
|
|
193
|
+
rollout, and deletes stale resources.
|
|
194
|
+
|
|
195
|
+
Because resource references are immutable digests, re-running `up` only restarts
|
|
196
|
+
deployments whose image content actually changed. `start` re-resolves published
|
|
197
|
+
digests without building.
|
|
198
|
+
|
|
199
|
+
## Registry Authentication
|
|
200
|
+
|
|
201
|
+
Building and pushing images from inside the cluster typically requires
|
|
202
|
+
credentials for the target registry. These are read from a Docker config file
|
|
203
|
+
(`KUBER_REGISTRY_CONFIG`, default `/etc/kuber/registry/config.json`) and mounted
|
|
204
|
+
as an image pull secret named by `KUBER_REGISTRY_SECRET`.
|
|
205
|
+
|
|
206
|
+
Registry authentication is optional. If `KUBER_REGISTRY_SECRET` is unset, the
|
|
207
|
+
server warns at startup and BuildKit uses anonymous registry access. This is
|
|
208
|
+
intended for registries that allow anonymous push/pull. The same credentials are
|
|
209
|
+
used when the server resolves a published image digest (`start` / `export`).
|
|
210
|
+
|
|
211
|
+
The server supports both standard registry bearer-token (`WWW-Authenticate:
|
|
212
|
+
Bearer`) and pre-emptive Basic auth when resolving digests.
|
|
213
|
+
|
|
214
|
+
### Build Registry Environment
|
|
215
|
+
|
|
216
|
+
The server's registry behavior is driven by a few closely related environment
|
|
217
|
+
variables:
|
|
218
|
+
|
|
219
|
+
- `KUBER_BUILD_REGISTRY` (default `registry.neko-piranha.ts.net`): the registry
|
|
220
|
+
the in-cluster BuildKit pushes built images to and `kuber` uses as the image
|
|
221
|
+
namespace. It also supplies the registry host for authentication.
|
|
222
|
+
- `KUBER_INTERNAL_REGISTRY_HOST`: the host of an internal registry (for example
|
|
223
|
+
the in-cluster distribution service) used for the BuildKit cache image and,
|
|
224
|
+
when set, the image direct push target. When unset, push and cache fall back
|
|
225
|
+
to `KUBER_BUILD_REGISTRY`.
|
|
226
|
+
- `KUBER_INTERNAL_REGISTRY_INSECURE`: set to `"true"` to push to the internal
|
|
227
|
+
registry over plain HTTP instead of HTTPS. Only meaningful when
|
|
228
|
+
`KUBER_INTERNAL_REGISTRY_HOST` is set.
|
|
229
|
+
- `KUBER_PUSH_IMAGE_PREFIX` (default `kuber/`): a prefix applied to project
|
|
230
|
+
images pushed to the internal registry when `KUBER_INTERNAL_REGISTRY_HOST` is
|
|
231
|
+
configured.
|
|
232
|
+
- `KUBER_REGISTRY_RESOLVE_ORIGIN`: an explicit origin used to resolve a
|
|
233
|
+
published image digest (used by `start` / `export`). Useful when the digest
|
|
234
|
+
must be resolved from a different endpoint than the build/push registry, such
|
|
235
|
+
as an internal HTTP registry.
|
|
236
|
+
- `KUBER_REGISTRY_CONFIG`: path to the Docker config file with registry
|
|
237
|
+
credentials (see above).
|
|
238
|
+
- `KUBER_REGISTRY_SECRET`: the image pull Secret mounted for BuildKit's
|
|
239
|
+
registry access.
|
|
240
|
+
- `KUBER_BUILDKIT_IMAGE`: override the BuildKit runner image used for builds.
|
|
241
|
+
- `KUBER_BUILD_DATA_CLAIM`: the PVC claim backing builds.
|
|
242
|
+
|
|
243
|
+
See [Server Deployment](#server-deployment) for how `compose.yml` wires the
|
|
244
|
+
internal registry variables for the kuber-server pod.
|
|
245
|
+
|
|
246
|
+
## Account Recovery
|
|
247
|
+
|
|
248
|
+
If you lose your credentials and cannot log in:
|
|
249
|
+
|
|
250
|
+
1. Recreate the bootstrap admin by deploying with
|
|
251
|
+
`KUBER_BOOTSTRAP_PASSWORD` set again:
|
|
252
|
+
```bash
|
|
253
|
+
KUBER_BOOTSTRAP_PASSWORD='new-password' kuber up --no-b
|
|
254
|
+
kuber restart kuber-server
|
|
255
|
+
```
|
|
256
|
+
2. The server only creates the bootstrap user if the account does not already
|
|
257
|
+
exist, so a fresh `kuber login <username>` with the new password works, or
|
|
258
|
+
use the newly created admin to reset other accounts:
|
|
259
|
+
```bash
|
|
260
|
+
kuber users update <username> --password
|
|
261
|
+
```
|
|
262
|
+
3. After recovering, reconcile without `KUBER_BOOTSTRAP_PASSWORD` and restart
|
|
263
|
+
so the bootstrap Secret is removed and the password leaves the pod
|
|
264
|
+
environment.
|
|
265
|
+
|
|
266
|
+
Because user records and session hashes are stored as Secrets in
|
|
267
|
+
`kuber-system`, recovery relies on cluster administrators being able to redeploy
|
|
268
|
+
the server with bootstrap credentials. `users revoke <username>` forcibly logs a
|
|
269
|
+
user out across all devices.
|
|
39
270
|
|
|
40
271
|
## Next.js Example
|
|
41
272
|
|
|
42
|
-
[`example/`](example/) contains a documented deployment template for adding
|
|
273
|
+
[`example/`](example/) contains a documented deployment template for adding
|
|
274
|
+
kuber to an existing Bun-powered Next.js project without initializing or
|
|
275
|
+
bundling an application in this repository. It includes a standalone-output
|
|
276
|
+
Dockerfile, `.dockerignore`, `compose.yml`, and the required Next.js
|
|
277
|
+
configuration.
|
|
43
278
|
|
|
44
279
|
## Running
|
|
45
280
|
|
|
@@ -72,16 +307,23 @@ bun run index.ts db ls
|
|
|
72
307
|
bun run index.ts s3 ls
|
|
73
308
|
bun run index.ts s3 creds app
|
|
74
309
|
bun run index.ts s3 ui app
|
|
310
|
+
bun run index.ts login dmgnr
|
|
311
|
+
bun run index.ts users ls
|
|
312
|
+
bun run index.ts operations ls
|
|
313
|
+
bun run index.ts audit ls
|
|
75
314
|
```
|
|
76
315
|
|
|
77
316
|
All commands accept `--config` to use a configuration file other than
|
|
78
317
|
`.kuberrc.ts`:
|
|
79
318
|
|
|
80
319
|
```bash
|
|
81
|
-
kuber --config
|
|
82
|
-
kuber up --config
|
|
320
|
+
kuber --config production.kuberrc.ts up
|
|
321
|
+
kuber up --config production.kuberrc.ts
|
|
83
322
|
```
|
|
84
323
|
|
|
324
|
+
`login`, `logout`, and `whoami` are context-free and do not require a
|
|
325
|
+
configuration.
|
|
326
|
+
|
|
85
327
|
### Shell Completion
|
|
86
328
|
|
|
87
329
|
Generate and load completions for your shell:
|
|
@@ -91,27 +333,44 @@ source <(kuber complete zsh)
|
|
|
91
333
|
source <(kuber complete bash)
|
|
92
334
|
```
|
|
93
335
|
|
|
94
|
-
For a permanent setup, write the generated script to a file and source it from
|
|
336
|
+
For a permanent setup, write the generated script to a file and source it from
|
|
337
|
+
your shell configuration. Fish and PowerShell are also supported through
|
|
338
|
+
`kuber complete fish` and `kuber complete powershell`.
|
|
95
339
|
|
|
96
340
|
## Commands
|
|
97
341
|
|
|
98
|
-
- `up`: build images if needed,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
- `
|
|
102
|
-
|
|
103
|
-
- `
|
|
104
|
-
- `
|
|
105
|
-
- `
|
|
106
|
-
- `
|
|
107
|
-
- `
|
|
108
|
-
- `
|
|
109
|
-
- `
|
|
110
|
-
|
|
111
|
-
- `
|
|
112
|
-
|
|
113
|
-
- `
|
|
114
|
-
|
|
342
|
+
- `up [--no-b]`: build images if needed, ensure the workspace, reconcile managed
|
|
343
|
+
Postgres/S3, render manifests, apply them, wait for rollout, and delete stale
|
|
344
|
+
resources
|
|
345
|
+
- `start`: like `up` but re-resolves the currently published image digests
|
|
346
|
+
instead of building
|
|
347
|
+
- `login [username] [--persist]`: authenticate with the kuber API
|
|
348
|
+
- `logout`: revoke and remove the current API session
|
|
349
|
+
- `whoami`: show the authenticated API user and roles
|
|
350
|
+
- `users`: administer user accounts and roles
|
|
351
|
+
- `operations`: inspect server-side reconciliation operations
|
|
352
|
+
- `audit`: inspect the audit trail
|
|
353
|
+
- `ps [-a]`: print an ANSI graph of the current project namespace, hiding stopped
|
|
354
|
+
deployments by default
|
|
355
|
+
- `logs [deployment] [-f]`: print (or follow) logs for one deployment or all
|
|
356
|
+
managed deployments
|
|
357
|
+
- `exec <deployment> <command...>`: execute a command inside a running
|
|
358
|
+
deployment pod over an interactive WebSocket
|
|
359
|
+
- `restart [deployment]`: roll out a restart across managed deployments
|
|
360
|
+
- `stop`: delete the matching name-scoped HPAs (so autoscaling cannot scale
|
|
361
|
+
replicas back up) and scale managed deployments to zero
|
|
362
|
+
- `rollback` (alias `fuck`) `[deployment]`: roll one deployment back to its
|
|
363
|
+
previous release, or all managed deployments when no name is given
|
|
364
|
+
- `down [-f]`: delete managed resources while keeping ingress, PVCs, managed
|
|
365
|
+
databases, and managed S3 storage; `-f` also deletes those and the namespace
|
|
366
|
+
- `db ls` / `db creds <service>`: list or print credentials for managed Postgres
|
|
367
|
+
claims
|
|
368
|
+
- `s3 ls` / `s3 creds <service>` / `s3 ui <service>`: list managed S3 claims,
|
|
369
|
+
print their credentials, or print the Garage UI URL for a bucket
|
|
370
|
+
- `export [-o file]`: render manifests to a YAML file without applying them
|
|
371
|
+
|
|
372
|
+
Lifecycle commands (`restart`, `stop`, `rollback`, `down`) are idempotent:
|
|
373
|
+
identical requests are deduplicated server-side and tracked as operations.
|
|
115
374
|
|
|
116
375
|
## Configuration
|
|
117
376
|
|
|
@@ -125,11 +384,6 @@ export default {
|
|
|
125
384
|
project: "my-app",
|
|
126
385
|
composeFile: "compose.production.yml",
|
|
127
386
|
registry: "registry.example.com",
|
|
128
|
-
builders: {
|
|
129
|
-
amd64: "kuber@amd-builder",
|
|
130
|
-
arm64: "kuber@arm-builder",
|
|
131
|
-
remoteRoot: "kuber-build",
|
|
132
|
-
},
|
|
133
387
|
rolloutTimeoutMs: 10 * 60_000,
|
|
134
388
|
|
|
135
389
|
async compose(compose) {
|
|
@@ -147,37 +401,50 @@ export default {
|
|
|
147
401
|
|
|
148
402
|
Operational defaults:
|
|
149
403
|
|
|
150
|
-
- `project`: Compose top-level `name`, falling back to the current working
|
|
404
|
+
- `project`: Compose top-level `name`, falling back to the current working
|
|
405
|
+
directory name
|
|
151
406
|
- `composeFile`: the first recognized Compose filename in the working directory
|
|
152
407
|
- `registry`: `registry.neko-piranha.ts.net`
|
|
153
|
-
- `builders.amd64`: `kuber@astral-th`
|
|
154
|
-
- `builders.arm64`: `kuber@astral`
|
|
155
|
-
- `builders.remoteRoot`: `kuber-build`
|
|
156
408
|
- `rolloutTimeoutMs`: `300000`
|
|
157
409
|
|
|
158
410
|
Project-name precedence is `.kuberrc.ts project`, Compose top-level `name`, then
|
|
159
411
|
the current working directory name.
|
|
160
412
|
|
|
413
|
+
The `registry` value controls which registry the CLI requests build images from
|
|
414
|
+
and which the server uses to resolve published digests. `rolloutTimeoutMs`
|
|
415
|
+
bounds how long `up` and `rollback` wait for a Deployment rollout.
|
|
416
|
+
|
|
161
417
|
Configuration hooks can be synchronous or asynchronous and receive mutable
|
|
162
418
|
values:
|
|
163
419
|
|
|
164
|
-
- `compose(compose, context)`: once after parsing and validation; affects every
|
|
165
|
-
|
|
166
|
-
- `
|
|
167
|
-
|
|
168
|
-
- `
|
|
420
|
+
- `compose(compose, context)`: once after parsing and validation; affects every
|
|
421
|
+
command that reads Compose
|
|
422
|
+
- `preBuild(compose, context)`: before build eligibility is evaluated when
|
|
423
|
+
builds are enabled
|
|
424
|
+
- `postBuild(result, context)`: after images are built; `result` contains
|
|
425
|
+
`built` and `changed` service names
|
|
426
|
+
- `postRender(resources, context)`: after rendering and before reconciliation
|
|
427
|
+
planning; also runs for `export`
|
|
428
|
+
- `postApply(resources, context)`: after desired resources are successfully
|
|
429
|
+
applied
|
|
169
430
|
|
|
170
431
|
Hook context contains the resolved `cwd`, `project`, `composeFile`, and optional
|
|
171
432
|
`configFile`. A hook error aborts the command and is reported by the normal CLI
|
|
172
433
|
error handler.
|
|
173
434
|
|
|
435
|
+
Registry and rollout configuration remain part of the CLI-facing contract.
|
|
436
|
+
Image builder selection is not configurable: builds are scheduled, executed, and
|
|
437
|
+
owned entirely by the server.
|
|
438
|
+
|
|
174
439
|
## Compose Conventions
|
|
175
440
|
|
|
176
|
-
`kuber` supports a few project-specific Compose conventions on top of normal
|
|
441
|
+
`kuber` supports a few project-specific Compose conventions on top of normal
|
|
442
|
+
service translation.
|
|
177
443
|
|
|
178
444
|
### Host-Based Ports
|
|
179
445
|
|
|
180
|
-
If a `ports` entry uses a hostname instead of a numeric published port, `kuber`
|
|
446
|
+
If a `ports` entry uses a hostname instead of a numeric published port, `kuber`
|
|
447
|
+
treats it as an ingress host and routes traffic to the target container port.
|
|
181
448
|
|
|
182
449
|
Example:
|
|
183
450
|
|
|
@@ -188,9 +455,11 @@ services:
|
|
|
188
455
|
- somedomain.astrxl.dev:3000
|
|
189
456
|
```
|
|
190
457
|
|
|
191
|
-
That produces a Kubernetes `Ingress` rule for `somedomain.astrxl.dev` pointing
|
|
458
|
+
That produces a Kubernetes `Ingress` rule for `somedomain.astrxl.dev` pointing
|
|
459
|
+
at the service port for container port `3000`.
|
|
192
460
|
|
|
193
|
-
Single-level wildcard subdomains are supported. Quote wildcard entries so YAML
|
|
461
|
+
Single-level wildcard subdomains are supported. Quote wildcard entries so YAML
|
|
462
|
+
does not treat the leading `*` as an alias:
|
|
194
463
|
|
|
195
464
|
```yml
|
|
196
465
|
services:
|
|
@@ -200,7 +469,8 @@ services:
|
|
|
200
469
|
- "*.secure.astrxl.dev:3001:protected"
|
|
201
470
|
```
|
|
202
471
|
|
|
203
|
-
Protected routes use the kuber dialect and render Traefik `IngressRoute`
|
|
472
|
+
Protected routes use the kuber dialect and render Traefik `IngressRoute`
|
|
473
|
+
resources instead of plain Kubernetes `Ingress`:
|
|
204
474
|
|
|
205
475
|
```yml
|
|
206
476
|
services:
|
|
@@ -216,6 +486,39 @@ Translation rules:
|
|
|
216
486
|
- `host:port:protected` -> Traefik `IngressRoute` with middleware `routing/cf-auth` and host-wide matching
|
|
217
487
|
- `host:port:protected(path1,path2,...)` -> Traefik `IngressRoute` with middleware `routing/cf-auth` and explicit `PathPrefix(...)` matches only
|
|
218
488
|
|
|
489
|
+
### Replicas and Autoscaling
|
|
490
|
+
|
|
491
|
+
`deploy.replicas` (or the top-level `scale` field) controls the Deployment
|
|
492
|
+
replica count. A plain integer or numeric string renders a fixed `replicas`
|
|
493
|
+
value, with `scale` taking precedence over `deploy.replicas`.
|
|
494
|
+
|
|
495
|
+
A `"min-max"` range string requests autoscaling instead of a fixed count:
|
|
496
|
+
|
|
497
|
+
```yml
|
|
498
|
+
services:
|
|
499
|
+
app:
|
|
500
|
+
image: app
|
|
501
|
+
deploy:
|
|
502
|
+
replicas: "2-6"
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
kuber renders:
|
|
506
|
+
|
|
507
|
+
- a `Deployment` with `replicas` set to the range minimum (`2`)
|
|
508
|
+
- a `HorizontalPodAutoscaler` (`autoscaling/v2`) targeting that Deployment,
|
|
509
|
+
with `minReplicas: 2`, `maxReplicas: 6`, and a CPU target of 80%
|
|
510
|
+
utilization
|
|
511
|
+
- a `100m` CPU request injected into the container, unless `x-container`
|
|
512
|
+
already specifies a CPU request (the HPA needs a CPU request to scale on)
|
|
513
|
+
|
|
514
|
+
For both fixed counts above one and autoscaled ranges whose maximum exceeds
|
|
515
|
+
one, kuber also adds a `topologySpreadConstraints` entry spreading pods across
|
|
516
|
+
hosts (`kubernetes.io/hostname`, `maxSkew: 1`,
|
|
517
|
+
`whenUnsatisfiable: ScheduleAnyway`).
|
|
518
|
+
|
|
519
|
+
Malformed non-numeric replica values (for example `"lots"`) are rejected with
|
|
520
|
+
a clear error instead of silently defaulting.
|
|
521
|
+
|
|
219
522
|
### Managed Postgres
|
|
220
523
|
|
|
221
524
|
You can declare a managed Postgres database with a pseudo-volume:
|
|
@@ -236,7 +539,12 @@ services:
|
|
|
236
539
|
- postgresql:user/database
|
|
237
540
|
```
|
|
238
541
|
|
|
239
|
-
This creates or reuses the managed CNPG role secret, reconciles the database
|
|
542
|
+
This creates or reuses the managed CNPG role secret, reconciles the database
|
|
543
|
+
resource, and injects `DATABASE_URL` and
|
|
544
|
+
`REDIS_URL=redis://redis.database.svc.cluster.local` into the generated app
|
|
545
|
+
secret in Kubernetes. `REDIS_URL` is only added to services with a managed
|
|
546
|
+
Postgres claim. Running `kuber db creds <service>` performs the same focused
|
|
547
|
+
Secret, role, and Database reconciliation before printing credentials.
|
|
240
548
|
|
|
241
549
|
### Managed S3
|
|
242
550
|
|
|
@@ -249,7 +557,8 @@ services:
|
|
|
249
557
|
- s3:app
|
|
250
558
|
```
|
|
251
559
|
|
|
252
|
-
This creates `GarageBucket/app` and `GarageKey/app` in `garage-system`. To use
|
|
560
|
+
This creates `GarageBucket/app` and `GarageKey/app` in `garage-system`. To use
|
|
561
|
+
different key and bucket names:
|
|
253
562
|
|
|
254
563
|
```yml
|
|
255
564
|
services:
|
|
@@ -258,7 +567,8 @@ services:
|
|
|
258
567
|
- s3:app-key/shared-assets
|
|
259
568
|
```
|
|
260
569
|
|
|
261
|
-
The Garage operator generates the credentials. `kuber` reads its generated
|
|
570
|
+
The Garage operator generates the credentials. `kuber` reads its generated
|
|
571
|
+
Secret and injects these values into the service's `<service>-env` Secret:
|
|
262
572
|
|
|
263
573
|
- `AWS_ACCESS_KEY_ID`
|
|
264
574
|
- `AWS_SECRET_ACCESS_KEY`
|
|
@@ -266,7 +576,9 @@ The Garage operator generates the credentials. `kuber` reads its generated Secre
|
|
|
266
576
|
- `AWS_REGION`
|
|
267
577
|
- `S3_BUCKET`
|
|
268
578
|
|
|
269
|
-
One service can declare both `postgresql:...` and `s3:...`; all generated
|
|
579
|
+
One service can declare both `postgresql:...` and `s3:...`; all generated
|
|
580
|
+
values are merged into the same service Secret. Managed Garage buckets and keys
|
|
581
|
+
are retained by normal `down` and deleted by `down -f`.
|
|
270
582
|
|
|
271
583
|
Inspect a claim, print its generated credentials, or get its Garage UI URL:
|
|
272
584
|
|
|
@@ -282,9 +594,11 @@ assignments. `s3 ui` only prints the URL; it does not open a browser.
|
|
|
282
594
|
|
|
283
595
|
### Environment Files
|
|
284
596
|
|
|
285
|
-
`env_file` entries are read locally and turned into a Kubernetes `Secret` named
|
|
597
|
+
`env_file` entries are read locally and turned into a Kubernetes `Secret` named
|
|
598
|
+
`<service>-env`. Deployments then consume that secret through `envFrom`.
|
|
286
599
|
|
|
287
|
-
This is also where generated values such as `DATABASE_URL` and the managed S3
|
|
600
|
+
This is also where generated values such as `DATABASE_URL` and the managed S3
|
|
601
|
+
environment are injected.
|
|
288
602
|
|
|
289
603
|
### Volumes
|
|
290
604
|
|
|
@@ -297,7 +611,11 @@ This is also where generated values such as `DATABASE_URL` and the managed S3 en
|
|
|
297
611
|
- `postgresql:...` is treated as a managed database claim, not as a filesystem mount
|
|
298
612
|
- `s3:...` is treated as a managed object-storage claim, not as a filesystem mount
|
|
299
613
|
|
|
300
|
-
Named volumes also support kuber-specific storage hints.
|
|
614
|
+
Named volumes also support kuber-specific Longhorn storage hints. Kuber renders
|
|
615
|
+
each distinct placement policy as a deterministic, reusable Longhorn
|
|
616
|
+
`StorageClass`, then references that class from the PVC. The generated class
|
|
617
|
+
uses Longhorn's `numberOfReplicas`, `diskSelector`, and `dataLocality`
|
|
618
|
+
parameters; placement fields are never written directly to the PVC.
|
|
301
619
|
|
|
302
620
|
Default behavior:
|
|
303
621
|
|
|
@@ -329,8 +647,8 @@ services:
|
|
|
329
647
|
Meaning:
|
|
330
648
|
|
|
331
649
|
- `name(20Gi):/path` -> PVC size `20Gi`
|
|
332
|
-
- `name(20Gi on archive):/path` -> PVC size `20Gi`, `
|
|
333
|
-
- `name(20Gi on 1 archive):/path` -> PVC size `20Gi`,
|
|
650
|
+
- `name(20Gi on archive):/path` -> PVC size `20Gi`, with a StorageClass using `diskSelector: "archive"` and disabled data locality
|
|
651
|
+
- `name(20Gi on 1 archive):/path` -> PVC size `20Gi`, with a StorageClass using one replica and `diskSelector: "archive"`
|
|
334
652
|
|
|
335
653
|
Explicit extensions are also supported.
|
|
336
654
|
|
|
@@ -368,14 +686,32 @@ Precedence:
|
|
|
368
686
|
- top-level `volumes.<name>.x-*`
|
|
369
687
|
- fallback default `1Gi on 2 fast`
|
|
370
688
|
|
|
689
|
+
StorageClasses are cluster-scoped and content-addressed by policy. They are
|
|
690
|
+
shared across projects and intentionally retained when a project is removed.
|
|
691
|
+
|
|
371
692
|
## Building
|
|
372
693
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
694
|
+
When a service declares `build`, the CLI:
|
|
695
|
+
|
|
696
|
+
1. snapshots the repository into a content-addressed workspace
|
|
697
|
+
(committed git state, tracked changes, untracked files, and ignored `.env*`
|
|
698
|
+
files),
|
|
699
|
+
2. negotiates with the server and uploads only the blobs it is missing,
|
|
700
|
+
3. submits a build request; the server materializes the workspace from the CAS
|
|
701
|
+
onto a shared RWX PVC and runs a rootless BuildKit Job that builds and pushes
|
|
702
|
+
the configured image with registry cache.
|
|
703
|
+
|
|
704
|
+
Build containers are restricted: they run as non-root (`runAsUser`/`runAsGroup`
|
|
705
|
+
`1000`), do not mount the service account token, and only read the workspace
|
|
706
|
+
(read-only mount) and a writable BuildKit state `emptyDir`. After a push, the
|
|
707
|
+
registry's manifest digest is captured and embedded into the rendered Deployment
|
|
708
|
+
as an immutable `:latest@sha256:...` reference, so a changed image naturally
|
|
709
|
+
triggers a rollout. `start` and `export` look up the currently published digest
|
|
710
|
+
without rebuilding, and fail if a buildable service has no published image yet.
|
|
711
|
+
|
|
712
|
+
`export` is side-effect-free: it refuses to render managed Postgres or S3 claims
|
|
713
|
+
(because it cannot call the server to generate credentials) and instead tells
|
|
714
|
+
you to run `kuber up` or remove the managed provider claims.
|
|
379
715
|
|
|
380
716
|
## Rollback
|
|
381
717
|
|
|
@@ -395,23 +731,35 @@ Notes and limitations:
|
|
|
395
731
|
- A later `kuber up` or `kuber start` re-resolves `:latest` and returns the
|
|
396
732
|
Deployment to the current desired state anyway, so rollback is the right tool
|
|
397
733
|
for responding to a bad deploy, not for permanently pinning an old version.
|
|
398
|
-
- Rollback only considers Deployments managed by kuber
|
|
734
|
+
- Rollback only considers Deployments managed by kuber
|
|
735
|
+
(`app.kubernetes.io/managed-by=kuber`) and only runs within a workspace whose
|
|
736
|
+
namespace kuber owns.
|
|
399
737
|
|
|
400
|
-
|
|
738
|
+
## Workspace State and Operations
|
|
401
739
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
740
|
+
The server persists per-workspace state, revisions, operations, and audit
|
|
741
|
+
events as Secrets/ConfigMaps in `kuber-system`. Mutations are idempotent:
|
|
742
|
+
|
|
743
|
+
- operations carry an idempotency key so retries do not double-apply,
|
|
744
|
+
- resource deletion requires a UID precondition,
|
|
745
|
+
- workspace replacement is guarded by If-Match.
|
|
405
746
|
|
|
406
|
-
|
|
747
|
+
The CLI is built with the normal `build` script for distribution and local
|
|
748
|
+
use:
|
|
407
749
|
|
|
408
750
|
```bash
|
|
409
|
-
bun build
|
|
751
|
+
bun run build
|
|
410
752
|
```
|
|
411
753
|
|
|
412
754
|
## Notes
|
|
413
755
|
|
|
414
756
|
- Resource names and namespaces are derived from the current working directory.
|
|
415
|
-
- The
|
|
416
|
-
|
|
417
|
-
-
|
|
757
|
+
- The workspace snapshot is optimized for local iteration, not for producing a
|
|
758
|
+
perfectly clean export of the repository.
|
|
759
|
+
- Managed database support is Kubernetes-only. It injects `DATABASE_URL` into
|
|
760
|
+
the generated app secret and does not rewrite local `.env` files.
|
|
761
|
+
- `kuber` operates on managed resources in the namespace matching the current
|
|
762
|
+
directory name.
|
|
763
|
+
- Builds are scheduled, executed, and owned by the server. There is no local
|
|
764
|
+
SSH/daemon builder configuration; `registry` and `rolloutTimeoutMs` remain
|
|
765
|
+
CLI-facing settings.
|