This release adds secrets for passing sensitive data to services, a new uc proxy command for port-forwarding, a Prometheus metrics endpoint, uc machine ls that surfaces each machine's OS, Docker, and daemon versions. Plus daemon self-upgrade on uc machine init/add, WireGuard MTU auto-detection, shell completion, and more.
⚠️ This is a breaking release. Both the uc CLI and the uncloudd daemon on every machine must be upgraded to 0.20. See Breaking changes below before you upgrade.
⚠️ Breaking changes
0.20 changes the internal gRPC API proxy format and migrates the cluster store to a new Corrosion version, so a mixed 0.19/0.20 cluster will not work. The built-in version check rejects requests between incompatible CLI and daemon versions, so upgrade everything together. See Upgrade to 0.20.0 for instructions.
- Server-side gRPC proxy resolution (#247). API proxy targets (machines) are now resolved on the daemon instead of the CLI. This changes the request format on the wire, so old and new components can't talk to each other. Thanks to @jabr for the contribution ❤️
- Corrosion now runs as a managed container (71da325). The Corrosion cluster store moved from a systemd unit to a Docker container and was upgraded to Corrosion v1 (
2026.6.15). Existing data is migrated to the new format automatically on first daemon start. Auth is now enabled for local Corrosion API requests too (#110). - CLI artifact renamed
uncloud_*→uc_*(a5d73fe). Homebrew and the install script handle this for you. Only update your own scripts if you download the CLI archive directly. The daemon archive name (uncloudd_*) is unchanged. - Unregistry image proxy moved from port
5000to51500(d36ff67).uchandles this automatically when pushing images. Only update your setup if you reach this proxy port directly in your own tooling.
Secrets support
You can now keep sensitive data like passwords, API tokens, and private keys out of your Compose file. Define a secret with the new x-command extension and reference it from a service's environment with secret://<name>. The command runs locally at deploy time and pulls the value from your password manager, cloud secrets store, or any CLI you already use.
services:
api:
image: myapp:latest
environment:
DB_PASSWORD: secret://db_password
secrets:
db_password:
x-command: op read "op://prod/myapp/db_password"The value never lands in your Compose file or git. See the new Secrets doc for supported sources, caveats, and examples.
Proxy a service port locally
PR: #384. Thanks to @miekg for the contribution ❤️
The new uc proxy command forwards a service port to a local port over the cluster network, similar to kubectl port-forward. This is handy for reaching an internal database or admin UI from your laptop without exposing them publicly.
Richer uc machine ls
Changes: 8aa0a96, ad43569, 1d5c717, 23d6b44
uc machine ls now shows the OS, KERNEL, ARCH, DOCKER, and VERSION (daemon) of each machine. You can spot version drift and outdated daemons at a glance.
$ uc machine ls
NAME STATE ADDRESS PUBLIC IP WIREGUARD ENDPOINTS OS KERNEL ARCH DOCKER VERSION
machine-1 Up 10.210.0.1/24 203.0.113.10 203.0.113.10:51820 Debian 12.12 6.1.0-41-amd64 amd64 29.4.0 0.20.0
machine-2 Up 10.210.1.1/24 198.51.100.7 198.51.100.7:51820 Debian 12.5 6.1.0-18-amd64 amd64 26.0.0 0.20.0
machine-3 Up 10.210.2.1/24 - 192.0.2.44:51820 Ubuntu 22.04.5 LTS 6.8.0-1050-oracle arm64 29.5.3 0.20.0Pass -o json for machine-readable output to use in scripts.
Prometheus metrics
PR: #304. Thanks to @miekg for the contribution ❤️
Each daemon now serves the initial set of Prometheus metrics on http://<machine-ip>:51090/metrics, listening on the machine's internal IP. Point your scraper at it to monitor the cluster.
Daemon self-upgrade on machine init/add
Change: a26af3b
uc machine init and uc machine add now upgrade the installed daemon binary to the latest (or a specified --version) if the daemon was already installed on the machine.
Improvements
- WireGuard now auto-detects the optimal interface MTU, with a new
--wg-mtuflag to override it (0c3b8b1). - New
--wg-portflag foruc machine init/addto customise the WireGuard listen port (#366). Thanks to @dasunsrule32 for the contribution ❤️ x-portscan bind to multiple host IP addresses given as a CIDR prefix (#358). Thanks to @miekg for the contribution ❤️- New
uc versionanduncloudd versioncommands that print full build information, replacing the--versionflag (2f15915). uc machine init/addinstallscurlon the remote machine if it's missing (#402).- New
uc caddy logsalias foruc logs caddy(#381). - Shell/argument completion for the
ucanducindCLIs (machines, services, volumes) (#309, #344, #352). Thanks to @miekg for the contributions ❤️ - Support rootless Docker locally when pushing images to the cluster (#347). Thanks to @tonyo for the contribution ❤️
- The CLI strips ANSI colors from table output and skips interactive TUI elements when stdout is not a TTY (e3823c2, #386).
Bug fixes
- Reject relative volume source paths in Compose with a clear error (#353). Thanks to @miekg for the contribution ❤️
- Return a non-zero exit code when a
uccommand is cancelled by declining a confirmation prompt (#354). - Don't include outdated containers from a removed machine in the DNS and Caddy configs (07acb31).
- Improve context cancellation and Corrosion resubscription handling during watch and sync operations (328d175, 83256b5, 99e1ae0, 5f07bc5).
- Use a forked Bubble Tea that drains pending TTY input on shutdown (#286).
Upgrade to 0.20.0
Upgrade all machines and your local CLI to 0.20. Corrosion data migrates automatically on the first daemon restart.
Important
On the first restart, each daemon pulls the Corrosion container image ghcr.io/unlabs-dev/corrosion:2026.6.15. Make sure your machines can reach ghcr.io, or pre-pull the image, otherwise the cluster store won't start.
Uncloud CLI locally
# Homebrew (macOS, Linux)
brew upgrade uncloud
# Install script (macOS, Linux)
curl -fsS https://get.uncloud.run/install.sh | shMachine daemon
Run the following commands on each machine:
ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
curl -fsSL -o uncloudd.tar.gz https://github.com/psviderski/uncloud/releases/download/v0.20.0/uncloudd_linux_${ARCH}.tar.gz
tar -xf uncloudd.tar.gz
sudo install uncloudd /usr/local/bin/uncloudd
rm uncloudd uncloudd.tar.gz
sudo systemctl restart uncloudAfter upgrading
On the first restart, the daemon migrates the Corrosion store and stops the legacy systemd service. A couple of leftovers need manual cleanup on each machine:
-
The old store is preserved at
/var/lib/uncloud/corrosion.backup-<timestamp>. Once you've verified the cluster is healthy (uc machine ls), you can remove it:sudo rm -rf /var/lib/uncloud/corrosion.backup-* -
The legacy Corrosion systemd unit and binary are left in place because the daemon's sandbox doesn't have permission to delete them. Remove them manually:
sudo rm /etc/systemd/system/uncloud-corrosion.service /usr/local/bin/uncloud-corrosion sudo systemctl daemon-reload
Changelog
- c95136e BREAKING CHANGE: resolve gRPC API proxy targets (machines) on server instead of client, needs upgrade to v0.20 (#247)
- 03ff4cd add netbird embed experiment
- eebb7b5 add tsnet experiment
- e9762dd chore(api): add TODO to refactor Machine* domain types in api/client packages
- 15e3e32 chore(grpc): update min client/server versions to 0.20.0
- 0dd4bc9 chore: build a use new corrosion image 2026.5.14 (pointing to v1.0.0 upstream) for ucind
- a040ce5 chore: make retry log debug
- 7f7d091 chore: make the reset machine prompt a bit clearer
- 2aaaa70 chore: mod tidy
- e7a62f6 chore: use 'lb_policy first' for uncloud.run to always route traffic to local replica
- 73d515b cli: add -m/--machine flag completion for 'uc machine logs' (#352)
- 2f717a1 cli: do not use interactive TUI elements when control terminal (tty) is not available (fixes #386)
- e3823c2 cli: strip ANSI sequences (colors, styles) from table output when stdout is not a TTY
- 247154e cli: use forked bubbletea with a fix to drain pending TTY input on shutdown (#286)
- 7bac40a client: refactor ListImages to return an error per machine
- 0c42a7d cluster: derive default machine name from its hostname when adding to cluster
- 2f76187 corrosion: use Corrosion 2026.6.15 with fixed API authz for CLI query/exec commands
- e01c8d3 docs(secrets): add new Concepts/Secrets doc about passing sensitive data to services, including x-command extension
- 1d5c717 feat(cli): add JSON output format for 'uc machine ls', remove MACHINE ID column from table output
- 8aa0a96 feat(machine ls): add ARCH, VERSION, DOCKER columns in the output table
- ad43569 feat(machine): add OS and kernel version information to machine info, show in 'machine ls'
- 8b95db6 feat(machine): sync machine arch, daemon and docker versions to store, sync machine info on schedule
- 925695a feat(secrets): initial support for 'x-command' and 'driver: exec' secrets, referenced as secret://name in environment (#403)
- 28c88e4 feat(version): collect build-time metadata for uncloud binaries in version pkg
- 2f15915 feat(version): replace --version with 'version' command to print build information for uc and uncloudd
- 11df8e4 feat: add 'uc caddy logs' alias for 'uc logs caddy' (#381)
- 3586a32 feat: add 'uc proxy' command to proxy a service port to a local port (#384)
- 24b81af feat: add :51090/metrics endpoint with Prometheus metrics listening on machine IP (#304)
- 2695177 feat: add created_at/updated_at columns to cluster and machines store tables
- dd68079 feat: add shell completion for ucind CLI (#344)
- 4a12217 feat: add uc CLI argument completion for machines, services, volumes (#309)
- 1247f96 feat: allow to bind to multiple host IP addresses specified as CIDR prefix in x-ports (#358)
- 424263b feat: allow to customise WireGuard listen port with --wg-port for machine init/add (#366)
- 9424daf feat: enable auth for local requests to Corrosion API (resolves #110)
- a26af3b feat: make machine init/add upgrade installed daemon binary to latest or specified version
- 4503b17 feat: support rootless docker when pushing images to cluster (#347)
- 35191c0 fix(cluster): improve error handling on failed machine subscription in cluster store
- 83256b5 fix(corrosion): handle 404 errors during resubscription and stop retrying
- 99e1ae0 fix(corrosion): handle resubscription with from_change=0 that streams rows in new corrosion version
- e5b2053 fix(install): install curl on machine init/add if missing (fixes #402)
- 5f07bc5 fix(machine): correctly recreate machine in store if missing, improve context handling
- 328d175 fix(machine): improve context cancellation handling during watch and sync operations
- 07acb31 fix: do not include outdated containers from the removed machine in DNS and Caddy configs
- 7616f13 fix: reject extra args in context commands (#376)
- 5ecd09a fix: return non-exit code when uc commands cancelled by not confirming prompt (fixes #354)
- 7bb3cb9 logs: stream corrosion logs with 'uc machine logs corrosion' from container instead of journal (fixes #392)
- 23d6b44 machine: make each machine the source of truth for its configuration, sync it to cluster store
- 0c3b8b1 network: add auto-detection of optimal MTU for WireGuard interface, --wg-mtu flag, set max_mtu for Corrosion to 1232
- a5d73fe refactor(cli): move cmd/uncloud to cmd/uc, change the artifact name uncloud_* -> uc_*
- 9ea30e6 refactor(corrosion): reduce transport retry time for corrosion 10s->2s, move some logs to error level
- b08cee0 refactor(machine): improve startup and shuitdown logic, shutdown corrosion in one place
- 442b5c6 refactor: cleanup Corrosion container on reset, move admin socket to runtime dir
- d23f1bd refactor: manually modernize Go (#349)
- 9236aa3 refactor: migrate to latest Corrosion version 2026.5.14 for ucind (compare versions vector for store sync)
- 71da325 refactor: migrate to run Corrosion service as managed container instead of systemd unit, automatically migrate data to v1
- cccb863 ucind: add command aliases rm/delete (#396)
- 18b4112 ucind: use corrosion image tag 2026.6.15
- d36ff67 unregistry: change the listening port on each machine 5000 -> 51500
- cffa030 volumes: error when using relative volumes sources in compose (#353)
- f275a31 website: include js in the image
- fffc6a8 website: landing revamp with deploy demo and testimonial
- e622c06 website: landing update: Multi-machine Docker Compose for production with terminal examples
- 3841da9 website: node instead of machine in header, cut subheader