v0.21.0

parruda/swarmv0.21.0Oct 2, 2026by psviderski

Upgrading?

AI Summary

Adds shared TLS certificate storage for Caddy, systemd socket activation, runtime templates for bind mounts, and custom cluster domain support.

Key Highlights

  • API socket moved from /run/uncloud/uncloud.sock to /run/uncloud/api/uncloud.sock
  • Shared Caddy certificates through cluster store with distributed locks
  • uc caddy cert ls command for certificate inventory
  • Systemd socket activation for stable API socket
  • Runtime templates for bind mount paths using container metadata

Breaking Changes

  • API socket path changed from /run/uncloud/uncloud.sock to /run/uncloud/api/uncloud.sock

New Features

  • uc caddy cert ls for certificate inventory
  • uc dns set for custom cluster domain configuration
  • uc ls faster service listing with single broadcast request
  • Compose support for stdin_open and tty
  • uc machine init/add UNCLOUD_DAEMON_VERSION support
  • Caddy deployment healthchecks
  • Installation docs updated with mise
  • Proxy connection error improvements
  • Container log streaming with TTY support
  • Shell completion improvements

Full Release Notes

This release brings **shared TLS certificate storage for Caddy**, **systemd socket activation**, **runtime templates**, and a way to use **your own cluster domain**. It also adds a certificate inventory command, faster service listing, and fixes for proxying, container logs, and terminal output.

## ⚠️ Breaking changes

- The API socket moved from `/run/uncloud/uncloud.sock` to `/run/uncloud/api/uncloud.sock` ([f68c985](https://github.com/psviderski/uncloud/commit/f68c985)). Update your custom tools and container mounts if you used the API socket directly. Both old and new `uc` versions handle the socket move automatically.

## Shared certificate storage for Caddy

Caddy module: [caddy-uncloud](https://github.com/unlabs-dev/caddy-uncloud), issue: [#31](https://github.com/psviderski/uncloud/issues/31)

If you run Caddy on multiple machines behind a load balancer or a DNS record with multiple IPs, certificate issuance may not work correctly.

The new Uncloud storage module lets Caddy instances share certificates, private keys, and challenge tokens through the cluster store. Any instance can read the token to answer a challenge, and once one obtains a certificate, the others can use it too. Distributed locks coordinate issuance across machines.

Shared storage is **opt-in** for now. See [Cluster storage for Caddy](https://uncloud.run/docs/concepts/ingress/cluster-storage-for-caddy) for more details.

## Inspect Caddy certificates

Changes: [930e7ea](https://github.com/psviderski/uncloud/commit/930e7ea), [9c21ae6](https://github.com/psviderski/uncloud/commit/9c21ae6)

The new [`uc caddy cert ls`]((https://uncloud.run/docs/cli-reference/uc_caddy_cert_ls)) command lists certificates in Caddy's shared cluster storage. This is handy for checking which certificates have been issued and when they expire.

```shell
uc caddy cert ls

ID             NAME                            ISSUER                    EXPIRES
c111e23615f7   dns.uncloud.run                 Let's Encrypt             2026-12-31 (2 months)
b5dcd2a03e1b   nginx.2t5ex2.uncld.dev          Let's Encrypt             2026-12-31 (2 months)
70707fd2fea2   test-staging.2t5ex2.uncld.dev   Let's Encrypt (staging)   2026-12-31 (2 months)
c5f9301e1c06   uncloud.run                     Let's Encrypt             2026-12-31 (2 months)
```

```shell
uc caddy cert ls --machine machine-1
uc caddy cert ls -o json
```

This requires Caddy to first be deployed with [cluster storage](https://uncloud.run/docs/concepts/ingress/cluster-storage-for-caddy).

## Systemd socket activation

Change: [f68c985](https://github.com/psviderski/uncloud/commit/f68c985)

Systemd now creates the Uncloud API socket and passes it to the daemon when it starts. The socket is available before Docker starts and stays in place across daemon restarts. This gives Caddy's cluster storage module and other extensions a stable API socket, including after a reboot.

## Runtime templates for bind mounts

Change: [5c33e4b](https://github.com/psviderski/uncloud/commit/5c33e4b)

You can now use per-container metadata in bind mount paths. Uncloud renders these Go templates on the destination machine just before creating each container, so every replica can get its own host directory:

```yaml
services:
  app:
    image: app:latest
    scale: 2
    volumes:
      - "/var/lib/app/{{.Container.Name}}:/data"
```

For now, runtime templates expose `.Container.Name` and work only in the host and container paths of bind mounts. They could easily be extended to other metadata fields and Compose attributes. Please share your use case on [#431](https://github.com/psviderski/uncloud/issues/431).

See [Runtime templates](https://uncloud.run/docs/concepts/services/runtime-templates) for more details and examples.

## Use your own cluster domain

PR: [#365](https://github.com/psviderski/uncloud/pull/365). Thanks to @miekg for the contribution ❤️

The new [`uc dns set`](https://uncloud.run/docs/cli-reference/uc_dns_set) command lets you use a domain you manage yourself as the default cluster domain for generated service hostnames:

```shell
uc dns set apps.example.com
```

Configure wildcard DNS records for `*.apps.example.com` with your DNS provider, pointing to machines running Caddy. New services published without an explicit hostname can then use addresses like `web.apps.example.com`. Uncloud doesn't create or manage those external records.

If your cluster already has a reserved `*.uncld.dev` domain, release that reservation with `uc dns release` first.

## Improvements

- `uc ls` now collects services and containers with a single broadcast request instead of inspecting every service separately. This significantly speeds it up, especially when you have many services ([0e53f50](https://github.com/psviderski/uncloud/commit/0e53f50)).
- Compose now supports `stdin_open` and `tty` ([#419](https://github.com/psviderski/uncloud/pull/419)). Thanks to @miekg for the contribution ❤️
- Compose warns about more unsupported options, including external configs, `container_name`, and `deploy.placement` ([#429](https://github.com/psviderski/uncloud/pull/429)). Thanks to @miekg for the contribution ❤️
- `uc machine init/add` now accept `UNCLOUD_DAEMON_VERSION` as an alternative to `--version`, useful for scripts and nightly setups ([#409](https://github.com/psviderski/uncloud/pull/409)). Thanks to @tonyo for the contribution ❤️
- Caddy deployments now have a healthcheck that queries the admin API, so the deployment waits for Caddy to load its config before treating a new container as healthy ([70de7b1](https://github.com/psviderski/uncloud/commit/70de7b1)).
- The [installation docs](https://uncloud.run/docs/getting-started/install-cli) now include mise ([#421](https://github.com/psviderski/uncloud/pull/421)).

## Bug fixes

- Preserve custom global Caddy configuration during regeneration on Caddy restart. `uc caddy config` now warns when the last attempt to load the config failed ([ace8cbf](https://github.com/psviderski/uncloud/commit/ace8cbf)).
- Keep `uc proxy` running when an individual client disconnects or a forwarded connection fails. Connection errors now include more useful troubleshooting information ([fa77edf](https://github.com/psviderski/uncloud/commit/fa77edf)).
- Stream logs correctly from containers with an allocated TTY ([#419](https://github.com/psviderski/uncloud/pull/419)).
- Stop terminal capability replies from leaking into the shell after CLI spinners finish ([#435](https://github.com/psviderski/uncloud/pull/435)). Thanks to @tonyo for the contribution ❤️
- Fix shell completion when using `--connect`, `--context`, or `--uncloud-config` ([351698c](https://github.com/psviderski/uncloud/commit/351698c)).
- Return a non-zero exit code consistently when a command is cancelled by declining a confirmation prompt ([#406](https://github.com/psviderski/uncloud/pull/406)). Thanks to @miekg for the contribution ❤️
- Extend the systemd startup timeout while pulling the Corrosion image, allowing up to 5 minutes for a slow pull ([dc721e5](https://github.com/psviderski/uncloud/commit/dc721e5)).
- Fix daemon version comparisons when upgrading from a nightly build to the latest stable release ([50f8fbc](https://github.com/psviderski/uncloud/commit/50f8fbc)).
- Avoid waiting on replication gaps from unavailable cluster members after the requested store versions have been reached ([e4bd1ad](https://github.com/psviderski/uncloud/commit/e4bd1ad)).

## Upgrade to v0.21.0

Upgrade your local CLI and the daemon on every machine. If you're upgrading from before v0.20.0, follow the [v0.20.0 upgrade instructions](https://github.com/psviderski/uncloud/releases/tag/v0.20.0#upgrade-to-0200) first for the Corrosion migration.

### Uncloud CLI locally

```bash
# Homebrew (macOS, Linux)
brew upgrade uncloud

# Install script (macOS, Linux)
curl -fsS https://get.uncloud.run/install.sh | VERSION=0.21.0 sh
```

### Machine daemon

Run the following commands over SSH on each machine to upgrade the daemon binary:

```bash
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.21.0/uncloudd_linux_${ARCH}.tar.gz
tar -xf uncloudd.tar.gz
sudo install uncloudd /usr/local/bin/uncloudd
rm uncloudd uncloudd.tar.gz
```

**The following systemd unit updates are only needed on existing machines.** New machines set up with `uc machine init` or `uc machine add` using v0.21.0 or later install these units automatically. Replacing the daemon binary alone on an existing machine doesn't install the new `uncloud.socket` unit or update `uncloud.service`.

Create `/etc/systemd/system/uncloud.socket`:

```bash
sudo tee /etc/systemd/system/uncloud.socket >/dev/null <<'EOF'
[Unit]
Description=Uncloud API socket
Before=docker.service

[Socket]
ListenStream=/run/uncloud/api/uncloud.sock
SocketUser=root
SocketGroup=uncloud
SocketMode=0660
DirectoryMode=0750

[Install]
WantedBy=sockets.target
EOF
```

Run this command to add the socket dependency directly to `/etc/systemd/system/uncloud.service`:

```bash
sudo sed -i \
  's/^After=.*$/Requires=uncloud.socket\
After=network-online.target uncloud.socket docker.service/' \
  /etc/systemd/system/uncloud.service
```

Stop the daemon before starting the socket unit, then reload systemd, enable the socket, and start the upgraded daemon:

```bash
sudo systemctl stop uncloud.service && \
sudo systemctl daemon-reload && \
sudo systemctl enable uncloud.socket && \
sudo systemctl start uncloud.socket && \
sudo systemctl start uncloud.service
```

### After upgrading

Check your CLI and daemon versions:

```shell
uc version
uc machine ls
```

## Changelog
* f68c9859d922c53d0bc4442958c581eb0713e5f7 BREAKING CHANGE: move Uncloud API socket to /run/uncloud/api/uncloud.sock, activate it by systemd socket unit
* ad2e1a06d58a4bc1c101d9fe8523de28dbc1bc33 Ignore domain when there is no endpoint
* 81d36edc667197d0db89e434cf3e2177bd84378e No need to call out to update anything, as set doesnt use uncloud dns
* 3fc1cddf0192f928c26ff82b0ac4b1b89f9b8bf5 Rebase and and generate protos again
* b28c45802992d0489d0fee34111c2dfac8e106d8 Rebase and fix
* 5fb80e20714d2cc507496328c0677226968f4031 check validatity
* c58108aa829cca4fe3b5f837e0f1918be1687599 chore(.editorconfig): ignore formatting for release notes
* abb85f8734abd14b1fb887ca20093a63ba6f5719 chore(AGENTS): do not emphasize the importance of table driven tests
* 37f817aa588b39dc742dc65eac77cb8e5c712ae0 chore(caddystorage): adjust log levels
* 4dae187aa5770c5aecf6cac23aa1540f7b904dee chore(caddystorage): move storage module code to a separate unlabs-dev/caddy-uncloud repo
* 6b41340f1dba4bba9e40b913e2ef0c50c1b23308 chore(cli): use cli.Cancelled when cancellikng the command to return non-zero exit code in all cases (#406)
* ebecf334d77c16435a6465b919b7725738ebeda2 chore(dns): require minimum server version 0.21
* af277639dd29cb33abcf2f640ae0c6b3722a394f chore(dns): require minimum server version 0.21.0 for 'uc dns set' command
* f1555259de0a5b5b6ee3fe6d89a32cc72edcbd8a chore(docs): update command outputs in Deploy demo app doc, add section to view logs
* 79642735521c41698326a0d0d44289c107853904 chore(experiments): go mod tidy
* 88dda435e7d29b4290d54927c85ca5b605c90484 chore(gitignore): ignore /research dir
* 6266e9ec3abfe9699a4033e6c7154b5964fb27a7 chore(nightly-build): prettier warning in description (#410)
* b9c54f1ff5c1815c5773d83b5936c2a30be83657 chore(website): configure ingress Caddy as trusted proxy to forward client IPs
* e525bdeb7c2fe57057de1b2773203575b5641f97 chore(website): update discord members
* db55df64a273fb21d0a1f448931ee134fca5df4f chore: bump caddy Go pkg dependency to v2.11.4
* 3482eeefab9c977aee4b5890978bb1e802bcf8b7 chore: go mod tidy
* e81e130544094ba0c396c22a844d0ffcc136ff9e chore: go mod tidy
* e9e7b3a61e19506772f11338e1e4d3e7396b62c1 chore: move legacy unused WG tunnel and client connector to experiment/wg
* d1fe23dec0b8c4092e56095908e8cee22cafd973 docs
* 5d7952952a0127752784ec042418cc181ddb3120 docs(README): list cluster storage for certificates in features
* 87e99ae3696ccf3efbb7ec159c1efbad0d877ef3 docs(caddy): new page Cluster storage for Caddy
* f78a8ca8b54530ec77912f275d37fc210acb892c docs(caddy): regenerate CLI reference for 'caddy cert ls' command
* 4b9ea43402b20e81ce67e9144368af2c07522957 docs(cli): update machine init/add help with info about installing Docker and uncloudd
* c2ae11a293a8e2614d84c6af1b9a543ef4688e56 docs(cluster): add section about SSH user permissions when connection to the cluster
* ac56754281d9c53556132f1567d48606a6b841ac docs(install): mise installation (#421)
* 3af9936d27af5a848b230d129398638ce7d3ca42 docs(install): update Debian repository domain (#413)
* a1957a156e6b09eceac758168785e559581f6c43 docs(install): update machine init/add output that doesn't install corrosion systemd service
* 45d33d87dd4dec3e9b5deedfcc2901d81f31541e docs(runtime-templates): add 'Added in v0.21.0' note
* 9c21ae6b08497a7f734eafe73f93e1db2a3e2b27 feat(caddy): CLI command to list certificates stored in the Uncloud cluster storage for Caddy
* 930e7ea637bf9075dc9bcabc73e2131ba2588f54 feat(caddy): add ListCertificates method, refactor Caddy client layout (#31)
* 70de7b1b8f74222ebf5665f36b4bf5baaf413157 feat(caddy): add healthcheck to default Caddy spec and Compose deployment in docs
* bc09954cbd139c29883a7d1c8261eee198ae68f8 feat(caddy): mount uncloud API socket for storage module (#31)
* c7e0e36daa6735ec28021269c647cffaa667674e feat(caddystorage): define CaddyStorage gRPC service API for distributed Caddy storage
* a24fc11caf0762c1dd4edbbb782c131dc66fffc4 feat(caddystorage): generic namespace-scoped key-value storage using cluster table in Corrosion
* 7a88865f64a47ab5f2450964c11a1adddb1953c8 feat(caddystorage): implement the machine-local CaddyStorage gRPC service
* 9a4098694c0ec02490f20536563c108afe93091c feat(caddystorage): init Caddy storage module backed by an Uncloud cluster
* c6d0371f15693c6668f4f202d02fb737023f6118 feat(caddystorage): integrate CaddyStorage gRPC service in machine API
* 1e471d3ec41e6390610683c7de8c7bfc88eece20 feat(caddystorage): lint
* f7be7c55c98f6ce17b3e6055d88339fa6dd1c503 feat(caddystorage): minor logging formatting
* 992b819f5b560f802f401870727e5159b47197f6 feat(caddystorage): simplify CaddyStorage gRPC API to allow only one2one calls
* 6ab02f95fe9e82c3dd26f053e05c6f6d8bb55ede feat(caddystorage): simplify active locks tracking and cleanup
* 7c4fcde88d4f0eb09ad99d388b7b8bfee114b102 feat(cli): support env var UNCLOUD_DAEMON_VERSION to specify daemon version for "uc machine add/init" (#409)
* b7e224a1eff98813b1d1a32034d977be24be994e feat(compose): support stdin_open and tty (#419)
* 7a7a313426f5ebd42ba50d494bc32805e3ee94cb feat(distlock): add gRPC Lease server that adapts a machine-local Store
* 3f86b3de4420c7074b312000c802028d0abea116 feat(distlock): add integration tests for grpc transport
* 01426ca03d4c5ae7594a90e164ac0ea5f89f4a65 feat(distlock): add smoke e2e test for distributed lock (lease) in cluster
* 5a956e72dec278e678fd81853c9b34ad7eed45fc feat(distlock): declare Lease gRPC service API for distributed locks
* 43bf2baaf7aade7adb6eb76ee33afc99781e9ee7 feat(distlock): implement distributed Locker based on Redlock algorithm
* a5ab6e3788efdb1833c858b92bdb1d652b6e7e49 feat(distlock): implement in-memory lease store
* 1c29d4d5d03a244d3b58d1418d0b1b09f2a2a4b0 feat(distlock): integrate distributed lease management into Machine API
* 613cd6e41880f48bb573110d5d224062730a72a9 feat(distlock): refactor into a standalone package
* 9ee3ca15eb09fa6d46e2b69031c6041a8d6f5ec6 feat(dns): allow setting an externally managed cluster domain
* 417e402c73edb3fef6f8b9962472d0bc0794122f feat(dns): allow setting an externally managed cluster domain (uc dns set)
* 5c33e4be869936c8b1fe37e4f08f89889e9b26d0 feat(runtime-templates): add support for runtime templates in bind mounts (#412)
* 0f6c03240bd925ff3d6a792ad48f9f3dd44c6aa8 feat(store): add WaitForStoreVersion API to wait for cluster store replication up to target version
* e4bd1ad4439a878cda2b999c39439b184b5e0dae feat(store): improve waiting for store replication to skip unavailable members
* cec3c75a103a84e4f99621be938c7c704c7ad7b6 feat: add uc dns set
* aaf67687d2481cbf78fdbad28e33dfd813003266 feat: validate remaining unsupported items of the support-matrix (#429)
* ace8cbf974af19d93e3309956a0e1b3a5136e3a0 fix(caddyconfig): custom global Caddy config is preserved on regeneration,warn about last load error in 'uc caddy config' (fixes #412)
* 751ea97f4750ec6eebd1f158d8857728e1148236 fix(caddystorage): error formatting
* 351698c280e1a2368bbb5dde3e3a4c8991b15bb9 fix(cli): completion with direct connections (--connect, --context, --uncloud-config) (fixes #377)
* e4455e936aa367752669647246ca6221be24ea64 fix(cli): stop leaking terminal capability replies into the shell (#435)
* 5e01db5e20e4c8c2cef6fef64b80eebd43ae6156 fix(compose): remove unnecessary warning validating deploy.mode that is error
* dc721e511e59c437eabfeaca32369cb0932fc257 fix(daemon): extend systemd service start timeout when pulling Docker image for corrosion service
* d36b28c55ae3f066b515a12379ad28f0554cf004 fix(docs): version command instead of --version in docs (#408)
* e5f23a91894b3f86f9f2e229370c8edc384915b9 fix(install): allow to install uc CLI version <0.20 after the artifact renaming
* 50f8fbcfda9154d3d09bf7a9700faa51c43f5066 fix(install): correctly compare installed nightly daemon version to upgrade to latest
* fa77edf53e7dd9dd4f607a40b7c11964f1edb150 fix(proxy): don't shutdown 'uc proxy' when a client connection aborts
* ae940c3003c0266605d0886e557fff52f4bed080 fix(test): flaky distributed lock tests by waiting for WG mesh to become ready
* 47a4a9158ec316a49d29cefc58c1463b6c3874fa from main
* 25ca0a57e80a3f2578f92270f44d2fd7f1c67219 lint
* 54a7328a343149c7f859e9c097308937639bb23c mise proto && make cli-docs
* e50c5fbe578dfdc4ed1aad5d0e488122b0d4b41f refactor(api): replace map[string]uint64 with api.StoreVersion for store version handling
* 00d68d9465220fc12ec48b87b455c8297e16b178 refactor(client): make ProxySingleMachineContext and ProxyMachinesContext package functions as well
* 0e53f505c0da57ac926e306bcc53260bf625358b refactor(client): speed up service list call (N -> 1 broadcast RPC)
* 8aedc2bf19945ae84cae225607998d5760a956e7 refactor(proto): move protobuf generated API to the top-level api/pb package
* 842f796e3838091cf82393deba20e20b3f51e4cb refactor(store): unify logic and enhance logging for initial store sync (waitStoreSync)
* c65f3522504c24fcea9fcd072da3b0571a6763b3 test(connector): test UnixConnector reconnects after socket replacement
* d670d18810e80d09b0656e79352ae43d92480915 website: add new Hub page
* 054b3b8fd426fe1c52c6ca89782a960e5ccedc4f website: change CTA link to getting started docs
* b3896ff6422d4afd41d8a99a7e8ea016db49762d website: minor
* 4ad4bce29d7e3b7bc05842ed05dee3a3ac439dcb website: refine FAQ and form on hub page
* 4d76dd601c07df50c37af238403c0d2a98f8f91e website: refine faq open source question, update og image
* e684795aee1354a67451da988fa3a38a01969f09 website: refine styles for index and hub
* f8e6d1caaab30b66d971c27821528803203019c8 website: send Hub early access form submission to posthog
* 0007f76d0956ba0095f2c27d97d5e055fdad01e3 website: serve hub as /hub
* 08b24af3414f573ed76faf995fd22f0839cdcd26 website: split newsletter subscription into a separate section
* b99abb7c390290a184c1d2d98161ba957c6ffe3a website: update docs social card and favicons
* 5b2823de1e5e90c386bf3aaef64a8b0896159b28 website: update meta/og tags and images

Related Repositories