From 8b6aed0b80e4626ce34792a86b6bddfbb81e5605 Mon Sep 17 00:00:00 2001 From: David Parry Date: Mon, 18 May 2026 11:14:55 +1000 Subject: [PATCH] feat(skills): add uncloud skill --- skills/uncloud/SKILL.md | 317 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 skills/uncloud/SKILL.md diff --git a/skills/uncloud/SKILL.md b/skills/uncloud/SKILL.md new file mode 100644 index 00000000..405115c8 --- /dev/null +++ b/skills/uncloud/SKILL.md @@ -0,0 +1,317 @@ +--- +name: uncloud +description: Use when managing an Uncloud cluster — deploying services, configuring Caddy ingress, adding static proxy routes for non-cluster devices, publishing ports, scaling, inspecting logs, or managing machines and volumes with the `uc` CLI. +origin: ECC +--- + +# Uncloud Cluster Management + +Reference for the `uc` CLI — a decentralised self-hosting platform using Docker containers, WireGuard mesh networking, and Caddy reverse proxy. + +## Core Concepts + +- **No central control plane** — all machines are equal peers connected by WireGuard +- **Caddy** runs as a global service on every machine; auto-obtains TLS from Let's Encrypt +- **Overlay network** — services communicate via `10.210.0.0/16` by default; DNS provided inside the mesh +- **Caddyfile is autogenerated** — never edit it directly; use `x-caddy` / `--caddyfile` instead + +--- + +## CLI Quick Reference + +### Machines + +| Command | Purpose | +|---------|---------| +| `uc machine init user@host` | Bootstrap first machine / new cluster | +| `uc machine add user@host` | Join machine to existing cluster | +| `uc machine ls` | List machines | +| `uc machine update NAME --public-ip IP` | Update public IP for ingress | +| `uc machine rm NAME` | Remove machine | + +Key `init` flags: `--name`, `--network 10.210.0.0/16`, `--no-caddy`, `--no-dns`, `--public-ip auto\|IP\|none` + +### Services + +| Command | Purpose | +|---------|---------| +| `uc service ls` / `uc ls` | List services | +| `uc service run IMAGE` | Run a single container service | +| `uc deploy` | Deploy from `compose.yaml` | +| `uc scale SERVICE N` | Set replica count | +| `uc service logs SERVICE` | View logs | +| `uc service exec SERVICE` | Shell into container | +| `uc service inspect SERVICE` | Detailed info | +| `uc service rm SERVICE` | Remove service (keeps named volumes) | +| `uc ps` | All containers across cluster | + +### Images + +```bash +uc image push myapp:latest # Push local image to all machines +uc image push myapp:latest -m machine1,machine2 # Push to specific machines +uc images # List images in cluster +``` + +### Volumes + +```bash +uc volume ls # All volumes +uc volume ls -m machine1 # On specific machine +uc volume create NAME -m MACHINE +uc volume rm NAME +``` + +### Caddy + +```bash +uc caddy config # Show current generated Caddyfile (read-only) +uc caddy deploy # Deploy/upgrade Caddy across cluster +``` + +### DNS & Context + +```bash +uc dns show # Show reserved *.uncld.dev domain +uc dns reserve # Reserve a new domain +uc ctx ls # List cluster contexts +uc ctx use prod # Switch context +``` + +--- + +## Port Publishing + +### HTTP/HTTPS (via Caddy reverse proxy) + +``` +-p [hostname:]container_port[/protocol] +``` + +| Example | Meaning | +|---------|---------| +| `-p 8080/https` | HTTPS with auto `service-name.cluster-domain` hostname | +| `-p app.example.com:8080/https` | HTTPS with custom hostname | +| `-p 8080/http` | HTTP only, no TLS | + +### TCP/UDP (host-bound, bypasses Caddy) + +``` +-p [host_ip:]host_port:container_port[/protocol]@host +``` + +| Example | Meaning | +|---------|---------| +| `-p 5432:5432@host` | TCP 5432 on all interfaces | +| `-p 127.0.0.1:5432:5432@host` | TCP 5432 loopback only | +| `-p 53:5353/udp@host` | UDP | + +--- + +## Compose File Extensions + +Uncloud adds these extensions on top of Docker Compose: + +### `x-ports` — publish ports with domains + +```yaml +services: + app: + image: app:latest + x-ports: + - example.com:8000/https + - www.example.com:8000/https + - api.example.com:9000/https +``` + +### `x-caddy` — custom Caddy config for service + +```yaml +services: + app: + image: app:latest + x-caddy: | + example.com { + redir https://www.example.com{uri} permanent + } + www.example.com { + reverse_proxy {{upstreams 8000}} { + import common_proxy + } + basic_auth /admin/* { + admin $2a$14$... + } + } +``` + +Template functions available inside `x-caddy`: +- `{{upstreams [service] [port]}}` — healthy container IPs +- `{{.Name}}` — service name +- `{{.Upstreams}}` — map of all services → IPs + +### `x-machines` — placement constraints + +```yaml +services: + db: + image: postgres:18 + x-machines: db-machine # Single machine name + app: + image: app:latest + x-machines: + - machine-1 + - machine-2 +``` + +### Full multi-service example + +```yaml +services: + api: + build: ./api + x-ports: + - api.example.com:3000/https + environment: + DATABASE_URL: postgres://db:5432/mydb + + web: + build: ./web + x-ports: + - example.com:8000/https + - www.example.com:8000/https + environment: + API_URL: http://api:3000 + + db: + image: postgres:18 + environment: + POSTGRES_PASSWORD: ${DB_PASSWORD} + volumes: + - db-data:/var/lib/postgresql/data + x-machines: db-machine + +volumes: + db-data: +``` + +--- + +## Routing to External (Non-Cluster) Devices + +To expose an external device (e.g. BMC, NAS, router UI) via Caddy without running a real container: + +**1. Create a Caddyfile snippet** (e.g. `~/device.caddyfile`): + +```caddyfile +https://device.example.com { + reverse_proxy https://192.168.1.x { + transport http { + tls_insecure_skip_verify # needed for self-signed BMC certs + } + } + log +} +``` + +For plaintext upstream: `reverse_proxy http://192.168.1.x:port` + +**2. Register as a named service with no-op container:** + +```bash +uc service run \ + --name device-bmc \ + --caddyfile ~/device.caddyfile \ + registry.k8s.io/pause:3.9 +``` + +`pause` is a minimal no-op container — it does nothing, but gives Uncloud a service entry to attach the Caddyfile to. + +**3. Verify:** + +```bash +uc caddy config # device.example.com block should appear +``` + +> `--caddyfile` cannot be combined with non-`@host` published ports. + +--- + +## Service DNS (Internal) + +Services inside the cluster resolve each other by name: + +| DNS name | Resolves to | +|----------|------------| +| `service-name` | Any healthy container | +| `service-name.internal` | Same | +| `rr.service-name.internal` | Round-robin | +| `nearest.service-name.internal` | Machine-local first | + +--- + +## Scaling & Global Services + +```bash +uc scale web 5 # 5 replicas (spread across machines) +uc scale web 1 # Scale down +``` + +```yaml +services: + caddy: + deploy: + mode: global # One container on every machine +``` + +--- + +## Image Tag Templates (in compose.yaml) + +```yaml +image: myapp:{{gitdate "20060102"}}.{{gitsha 7}} +image: myapp:{{gitsha 7}}.${GITHUB_RUN_ID:-local} +``` + +| Function | Output | +|----------|--------| +| `{{gitsha N}}` | First N chars of commit SHA | +| `{{gitdate "format"}}` | Git commit date in Go format | +| `{{date "format"}}` | Current date | + +--- + +## Common Workflows + +**Deploy from source:** +```bash +uc deploy # Build + push + deploy +uc build --push && uc deploy --no-build # Separate steps +``` + +**Inspect a service:** +```bash +uc inspect web +uc logs -f web +uc logs --since 1h web +uc exec web # Opens shell +uc exec web /bin/sh -c "env" # Run specific command +``` + +**Zero-downtime deploys** happen automatically; Uncloud waits for health checks before terminating old containers. + +**Force recreate:** +```bash +uc deploy --recreate +``` + +--- + +## Common Mistakes + +| Mistake | Fix | +|---------|-----| +| Editing the Caddyfile directly | Use `x-caddy` in compose or `--caddyfile` on `uc service run` | +| Proxying an HTTPS upstream with self-signed cert | Add `transport http { tls_insecure_skip_verify }` | +| `uc caddy config` shows no user-defined blocks | Caddy admin socket unreachable — check `uc inspect caddy` and `uc logs caddy` | +| Service can't reach external LAN IP from container | Verify Caddy container's host can route to target network | +| Volumes lost after `uc service rm` | Named volumes persist; only anonymous volumes are auto-removed |