# kuber `kuber` is Astral's internal Docker Compose to Kubernetes translation layer. It reads a local Compose file, renders Kubernetes resources, applies them to the cluster, and can build images remotely from your working tree without requiring Docker on your machine. ## What It Does - Reads `compose.yml` or `docker-compose.yml` - Converts supported Compose services into Kubernetes resources - Applies those resources into a namespace named after the current directory - Turns `env_file` entries into Kubernetes Secrets and mounts them through `envFrom` - Translates file mounts into ConfigMaps and directory/volume mounts into PVC-backed volumes - Builds images on the remote builder by syncing: - committed git state - tracked local diffs - untracked files - ignored `.env*` files - Supports host-based `ports` syntax that renders Kubernetes `Ingress` rules - Supports managed Postgres claims through special pseudo-volumes such as `postgresql:app` - Supports managed S3 buckets and credentials through pseudo-volumes such as `s3:app` ## Environment Assumptions This tool is built for Astral's cluster and workstation setup. It is not intended to work unchanged outside that environment. Expected local setup: - Tailscale access to the remote builder and Kubernetes network - a working `~/.kube/config` - `ssh` available locally Not required locally: - Docker - `kubectl` Docker is not required because builds happen on the remote builder after `kuber` syncs your repo state there. `kubectl` is not required because cluster access is handled through the bundled Kubernetes client. ## Running During development: ```bash bun run index.ts up ``` Other useful commands: ```bash bun run index.ts ps bun run index.ts logs bun run index.ts logs -f bun run index.ts exec app sh bun run index.ts start bun run index.ts stop bun run index.ts restart bun run index.ts db ls ``` ## Commands - `up`: build images if needed, render manifests, apply them, restart deployments, and wait for rollout - `start`: same as `up` but skips image builds - `stop`: scale managed deployments to zero - `restart`: roll out a restart across managed deployments - `down`: delete managed resources while keeping ingress, PVCs, managed databases, and managed S3 storage - `down -f`: also delete ingress, PVCs, managed database and S3 resources, and the namespace - `ps`: list deployments in the current project namespace - `logs [deployment]`: print logs for one deployment or all managed deployments - `logs -f [deployment]`: follow logs continuously - `exec `: execute a command inside a running deployment pod - `db ls`: list managed Postgres claims declared in the current Compose file - `db creds `: print the generated connection details for a managed Postgres claim ## Compose Conventions `kuber` supports a few project-specific Compose conventions on top of normal service translation. ### Host-Based Ports If a `ports` entry uses a hostname instead of a numeric published port, `kuber` treats it as an ingress host and routes traffic to the target container port. Example: ```yml services: app: ports: - somedomain.astrxl.dev:3000 ``` That produces a Kubernetes `Ingress` rule for `somedomain.astrxl.dev` pointing at the service port for container port `3000`. Protected routes use the kuber dialect and render Traefik `IngressRoute` resources instead of plain Kubernetes `Ingress`: ```yml services: app: ports: - db.astrxl.dev:4984:protected - status.astrxl.dev:3001:protected(/dashboard,/socket.io) ``` Translation rules: - `host:port` -> Kubernetes `Ingress` - `host:port:protected` -> Traefik `IngressRoute` with middleware `routing/cf-auth` and host-wide matching - `host:port:protected(path1,path2,...)` -> Traefik `IngressRoute` with middleware `routing/cf-auth` and explicit `PathPrefix(...)` matches only ### Managed Postgres You can declare a managed Postgres database with a pseudo-volume: ```yml services: app: volumes: - postgresql:app ``` Or with an explicit username and database name: ```yml services: app: volumes: - postgresql:user/database ``` This creates or reuses the managed CNPG role secret, reconciles the database resource, and injects `DATABASE_URL` into the generated app secret in Kubernetes. ### Managed S3 Declare a Garage S3 bucket and access key with a pseudo-volume: ```yml services: app: volumes: - s3:app ``` This creates `GarageBucket/app` and `GarageKey/app` in `garage-system`. To use different key and bucket names: ```yml services: app: volumes: - s3:app-key/shared-assets ``` The Garage operator generates the credentials. `kuber` reads its generated Secret and injects these values into the service's `-env` Secret: - `AWS_ACCESS_KEY_ID` - `AWS_SECRET_ACCESS_KEY` - `AWS_ENDPOINT_URL_S3` - `AWS_REGION` - `S3_BUCKET` One service can declare both `postgresql:...` and `s3:...`; all generated values are merged into the same service Secret. Managed Garage buckets and keys are retained by normal `down` and deleted by `down -f`. ### Environment Files `env_file` entries are read locally and turned into a Kubernetes `Secret` named `-env`. Deployments then consume that secret through `envFrom`. This is also where generated values such as `DATABASE_URL` and the managed S3 environment are injected. ### Volumes `kuber` treats different volume shapes differently: - file bind mounts become ConfigMaps - directory bind mounts become PVC-backed mounts - named volumes become PVC-backed mounts - `tmpfs` becomes `emptyDir` with memory backing - `postgresql:...` is treated as a managed database claim, not as a filesystem mount - `s3:...` is treated as a managed object-storage claim, not as a filesystem mount Named volumes also support kuber-specific storage hints. Default behavior: ```yml # compose services: app: volumes: - myvolume:/data # effective kuber interpretation services: app: volumes: - myvolume(1Gi on 2 fast):/data ``` Short syntax: ```yml services: app: volumes: - data(20Gi):/data - archive(200Gi on archive):/archive - cache(10Gi on 1 fast):/cache ``` Meaning: - `name(20Gi):/path` -> PVC size `20Gi` - `name(20Gi on archive):/path` -> PVC size `20Gi`, `diskTag: ["archive"]`, `dataLocality: "none"` - `name(20Gi on 1 archive):/path` -> PVC size `20Gi`, `replicaCount: 1`, `diskTag: ["archive"]`, `dataLocality: "none"` Explicit extensions are also supported. Top-level named volume: ```yml volumes: data: x-size: 20Gi x-diskTag: [archive] x-replicaCount: 1 x-dataLocality: none ``` Long-form service mount: ```yml services: app: volumes: - type: volume source: data target: /data volume: x-size: 20Gi x-diskTag: [archive] x-replicaCount: 1 x-dataLocality: none ``` Precedence: - short syntax like `data(20Gi on 1 archive):/data` - long-form `volume.x-*` - top-level `volumes..x-*` - fallback default `1Gi on 2 fast` ## Building To build distributable binaries: ```bash bun run build.ts ``` If you only want a plain JavaScript bundle for quick local use in another workspace, build `index.ts` directly: ```bash bun build index.ts --target bun --minify --sourcemap --outdir dist ``` ## Notes - Resource names and namespaces are derived from the current working directory. - The remote build flow is optimized for local iteration, not for producing a perfectly clean export of the repository. - Managed database support is Kubernetes-only. It injects `DATABASE_URL` into the generated app secret and does not rewrite local `.env` files. - `kuber` operates on managed resources in the namespace matching the current directory name.