- Upgrade **all** machines and the local CLI to v0.20 together; mixed versions are incompatible.
- Ensure each node can reach `ghcr.io` to pull the Corrosion container image (`ghcr.io/unlabs-dev/corrosion:2026.6.15`) on first restart, or pre‑pull it manually.
- After upgrade, remove leftover legacy Corrosion data and systemd unit: ``` sudo rm -rf /var/lib/uncloud/corrosion.backup-* sudo rm /etc/systemd/system/uncloud-corrosion.service /usr/local/bin/uncloud-corrosion sudo systemctl daemon-reload ```
- Server‑side gRPC API proxy resolution now performed on the daemon, changing request format (requires full cluster upgrade).
- Corrosion moved from a systemd unit to a managed Docker container and upgraded to Corrosion v1 (2026.6.15); automatic data migration occurs on first start.
- CLI artifact names changed from `uncloud_*` to `uc_*`; existing scripts must be updated if they reference the old names.
- Secrets support via `x-command` extension for injecting sensitive data without exposing it in compose files.
- `uc proxy` command for local port‑forwarding of cluster services (similar to `kubectl port-forward`).
- Prometheus metrics endpoint (`http://<machine>:51090/metrics`) on each daemon.
Full changelog
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.0
Pass -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 imageghcr.io/unlabs-dev/corrosion:2026.6.15. Make sure your machines can reachghcr.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 | sh
Machine 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 uncloud
After 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
- c95136eae61557063627e8b488f314c8376c2da2 BREAKING CHANGE: resolve gRPC API proxy targets (machines) on server instead of client, needs upgrade to v0.20 (#247)
- 03ff4cd51d59e1cade05d2720cd6106b00d69400 add netbird embed experiment
- eebb7b51edc4096ccd12d57f50049f2a1b9d02e8 add tsnet experiment
- e9762dda218243c8c1b539ea13d48c7cb5096e10 chore(api): add TODO to refactor Machine* domain types in api/client packages
- 15e3e32b47b5ca735915f51d809e98b8a41937e5 chore(grpc): update min client/server versions to 0.20.0
- 0dd4bc9d364016ec72a1f701106345fb9026fa90 chore: build a use new corrosion image 2026.5.14 (pointing to v1.0.0 upstream) for ucind
- a040ce5f8eda4d1399b02456019fd953b8849c8c chore: make retry log debug
- 7f7d0914293205a7c7caf2e6bce0d4c3ee0060ff chore: make the reset machine prompt a bit clearer
- 2aaaa70b15e73d50823f23ae9c5ae5d8487a16eb chore: mod tidy
- e7a62f6908b2381b90263863d19e4ecb08c4b30d chore: use 'lb_policy first' for uncloud.run to always route traffic to local replica
- 73d515bb40de67a9dc02c4a79cde022675d56284 cli: add -m/--machine flag completion for 'uc machine logs' (#352)
- 2f717a10a7d9528c1a2dd5936e98d6cc00b88637 cli: do not use interactive TUI elements when control terminal (tty) is not available (fixes #386)
- e3823c29bfa2c0dd34046312fedaec94bd40a6ec cli: strip ANSI sequences (colors, styles) from table output when stdout is not a TTY
- 247154eeb31e758d7e3070b2ee4f0e24d91f18f8 cli: use forked bubbletea with a fix to drain pending TTY input on shutdown (#286)
- 7bac40ae3e2087cb12ce08c057e95a81482f7752 client: refactor ListImages to return an error per machine
- 0c42a7df654a8936ba29da842cfbdffb9569e5b7 cluster: derive default machine name from its hostname when adding to cluster
- 2f76187bf5dddf140f300a513e1b4ae38ff5add4 corrosion: use Corrosion 2026.6.15 with fixed API authz for CLI query/exec commands
- e01c8d3cc73667a861cf72b53889d275dbae6cdc docs(secrets): add new Concepts/Secrets doc about passing sensitive data to services, including x-command extension
- 1d5c71730fc2e7f3c6f9411af60e899bff113c2b feat(cli): add JSON output format for 'uc machine ls', remove MACHINE ID column from table output
- 8aa0a96c23c21be25f3a86c529d2051e22f11e3e feat(machine ls): add ARCH, VERSION, DOCKER columns in the output table
- ad43569fe5f061f74e21002e0a459889b1a599c9 feat(machine): add OS and kernel version information to machine info, show in 'machine ls'
- 8b95db66d10ff187d23531d85cb8d34b1fa639bb feat(machine): sync machine arch, daemon and docker versions to store, sync machine info on schedule
- 925695a44a858a3a5d6b4898fab234ed8e56e3f3 feat(secrets): initial support for 'x-command' and 'driver: exec' secrets, referenced as secret://name in environment (#403)
- 28c88e4e10511d098d797aa76feae6957275fc62 feat(version): collect build-time metadata for uncloud binaries in version pkg
- 2f159157cc243e0478f17b5f9ca016c60ad2e2cf feat(version): replace --version with 'version' command to print build information for uc and uncloudd
- 11df8e4a750395327da134054aaac5ee42c9a523 feat: add 'uc caddy logs' alias for 'uc logs caddy' (#381)
- 3586a329875bc59d90beb998dbaf8ceef50e95e4 feat: add 'uc proxy' command to proxy a service port to a local port (#384)
- 24b81af43cec4d83cd8b32e6ac271d7e429d7391 feat: add :51090/metrics endpoint with Prometheus metrics listening on machine IP (#304)
- 26951773d11eddffe1905a9d8be9b6bb84ba01f1 feat: add created_at/updated_at columns to cluster and machines store tables
- dd680790059982b27da513749f2b3f38073c95b4 feat: add shell completion for ucind CLI (#344)
- 4a12217e657d588d394b48a65bd84a5a6cc48e02 feat: add uc CLI argument completion for machines, services, volumes (#309)
- 1247f961c297491c6df84e014a1af3671143d7ea feat: allow to bind to multiple host IP addresses specified as CIDR prefix in x-ports (#358)
- 424263bdd444c1ee52714e91dc7b920ee6b54788 feat: allow to customise WireGuard listen port with --wg-port for machine init/add (#366)
- 9424daff28299b63005ca38afb71c256f237973b feat: enable auth for local requests to Corrosion API (resolves #110)
- a26af3b2ae1f35affae371cc29b46adaed601be4 feat: make machine init/add upgrade installed daemon binary to latest or specified version
- 4503b17046196a03142b7b397db5b7a8b9bfbfaf feat: support rootless docker when pushing images to cluster (#347)
- 35191c00e06da1686ebe5ff2263bf7b8362a982d fix(cluster): improve error handling on failed machine subscription in cluster store
- 83256b5bf7046dfb4377e5f43cf9990889242298 fix(corrosion): handle 404 errors during resubscription and stop retrying
- 99e1ae0edbdd8f014e88ecd979f10d05ce39987a fix(corrosion): handle resubscription with from_change=0 that streams rows in new corrosion version
- e5b205391e476d3520186af786c81a5126a7bc06 fix(install): install curl on machine init/add if missing (fixes #402)
- 5f07bc58324d09f0bed3c4d41d63f0b71e751870 fix(machine): correctly recreate machine in store if missing, improve context handling
- 328d17599ba3aa7d0a03c19ef731b199ebecf564 fix(machine): improve context cancellation handling during watch and sync operations
- 07acb31bf3064fc65df2f0badf7a81049130059c fix: do not include outdated containers from the removed machine in DNS and Caddy configs
- 7616f1365a3318cde5f0566dfc8240a5ac3e4f24 fix: reject extra args in context commands (#376)
- 5ecd09aa2b4f2709d4991cee52a7f13a5962cca9 fix: return non-exit code when uc commands cancelled by not confirming prompt (fixes #354)
- 7bb3cb9682ccf5fa9c63000a586aa9a976e52b49 logs: stream corrosion logs with 'uc machine logs corrosion' from container instead of journal (fixes #392)
- 23d6b4409a85ad3d97f514b4f6dbf066328f6771 machine: make each machine the source of truth for its configuration, sync it to cluster store
- 0c3b8b122f6c27430c064eeb125a0f347f7f3938 network: add auto-detection of optimal MTU for WireGuard interface, --wg-mtu flag, set max_mtu for Corrosion to 1232
- a5d73fe4d7b71259961b29dd1097d0aeb750261f refactor(cli): move cmd/uncloud to cmd/uc, change the artifact name uncloud_* -> uc_*
- 9ea30e6ee925ea4a7c79797ae49d27c70d071966 refactor(corrosion): reduce transport retry time for corrosion 10s->2s, move some logs to error level
- b08cee03e3b935d6ac3ac190c89817c8b9f9f48e refactor(machine): improve startup and shuitdown logic, shutdown corrosion in one place
- 442b5c62bf3a2a42f77ae8eb82f013544118efe5 refactor: cleanup Corrosion container on reset, move admin socket to runtime dir
- d23f1bdc0056e653739be50d227778ebe2622f48 refactor: manually modernize Go (#349)
- 9236aa341a73770ed27acd3522d34e22eaf2cfcb refactor: migrate to latest Corrosion version 2026.5.14 for ucind (compare versions vector for store sync)
- 71da32549ab3a615fd479c31e6c242293c6d9f89 refactor: migrate to run Corrosion service as managed container instead of systemd unit, automatically migrate data to v1
- cccb8635e14a74644e0a1d9e71a5a2c07bb8dcea ucind: add command aliases rm/delete (#396)
- 18b4112a3320277cea92cb65debaf8becdcef5c6 ucind: use corrosion image tag 2026.6.15
- d36ff674099df2c806722b327c04dd63931005f0 unregistry: change the listening port on each machine 5000 -> 51500
- cffa03007b337c6edecc52ef9b22cd6a6d9e31d9 volumes: error when using relative volumes sources in compose (#353)
- f275a31096800da585e74ced940076064e531b66 website: include js in the image
- fffc6a8b8a93eb60bade52783d498ce7bf0807af website: landing revamp with deploy demo and testimonial
- e622c06ca61e945a7b0309d713bc6427dfab8a11 website: landing update: Multi-machine Docker Compose for production with terminal examples
- 3841da9e1d89cf12f25114830f03e55ab89b3399 website: node instead of machine in header, cut subheader