psviderski
That's how I see it as well but it's really tough to go against the grain. I have a small enthusiastic community of users around Uncloud (https://github.com/psviderski/uncloud) who went full circle - fed up with k8s and came back to simple, boring declarative Compose deployments across a handful of interconnected hosts.
Uncloud is essentially a cluster version of Docker Compose without a control plane and cluster management overhead.
There are no benefits to scaling down in this case. And scaling up won't help handle more load if you've already allocated all resources to running replicas. You need more machines, not more replicas on the existing machine(s).
It all comes down to simple, boring capacity planning and static resource allocation. Fewer moving parts results in fewer failure modes, hence more robust infra and less ops and maintenance work.
Radboud University recently announced they're rolling it out for managing containers across the faculty which is the most "serious install" I know about, but there could be other: https://cncz.science.ru.nl/en/news/2026-04-15_uncloud/
And I’m building and happily using Uncloud (https://github.com/psviderski/uncloud) for this (inspired by Kamal). It makes multi-machine setups as simple as a single VM. Creates a zero-config WireGuard overlay network and uses the standard Docker Compose spec to deploy to multiple VMs. There is no orchestrator or control plane complexity. Start with one VM, then add another when needed, can even mix cloud VMs and on-prem.
Yeah, you absolutely could and someone on our Discord has written a 30-line bash script that essentially runs a reconciliation loop with the CLI.
Thanks for the point on underselling by calling it imperative rather than declarative!
On the bright side, you can always use both
It also has the WireGuard overlay networking built in so containers across machines get direct connectivity without having to map ports to the host. For example, securely access a database running on another machine. This also allows you to horizontally scale your services to multiple replicas on different machines and distribute traffic between them with minimal configuration.
The current state of Uncloud is the primitives and foundation that could be used to build a more higher-level PaaS-like solution such as Coolify.
Yep, you define the mapping between the domain name and the internal container port as `x-ports: app.example.com:8000/https` in the compose file. Or you can specify a custom Caddy config for the service as `x-caddy: Caddyfile` which allows to customise it however you like. See https://uncloud.run/docs/concepts/ingress/publishing-service...
>if I deploy 2 compose 'stacks', do all containers have access to all other containers, even in the other stack?
Yes, there is no network isolation between containers from different services/stacks at the moment. Here is an open discussion on stack/namespace/environment/project concepts and isolation: https://github.com/psviderski/uncloud/discussions/94.
What's your use case and how would you want this to behave?
A proper load balancer or Cloudflare DNS proxy would handle this.
The private container IPs will get NATed to the underlying EC2 IPs so requests to RDS will appear as coming from those instances. The appropriate Security Group(s) need to be configured as well. The limitation is that you can't segregate access at the service level, only at the EC2 instance level.
Your setup sounds like single-node or nodes that don't need to discover each other. If you ever need multi-node with service-to-service communication, that's where stitching together Ansible + Terraform + quadlets + some networking layer starts to get tedious. Uncloud tries to make that part simple out of the box.
You also get the reverse proxy (Caddy) that automatically reconfigures depending on what containers are running on machines. You just deploy containers and it auto-discovers them. If a container crashes, the configuration is auto-updated to remove the faulty container from the list of upstreams.
Plus a single CLI you run locally or on CI to manage everything, distribute images, stream logs. A lot of convenience that I'm putting together to make the user experience more enjoyable.
But if you don't need that, keep doing what works.
I'm afraid I have to disappoint you
I know how to use k8s but I really don't enjoy it. It feels so distasteful to me that it triggered me to make an attempt at designing a nicer experience, because why not. I remember how much fun I had trying Docker when it first came out. That inspires me to at least try. It doesn't seem like the k8s community is even trying unfortunately.
No UI yet (planned) so if that's critical, Dokploy is likely a better choice for now.
However, some unique features like building and pushing images directly to your nodes without an external registry give Uncloud a PaaS-like feel, just CLI-first. Really depends on what you're hosting and what you're optimising for.
See short deploy demo: https://uncloud.run/docs/guides/deployments/deploy-app
When you run 'uc deploy' command:
- it reads the spec from your compose.yaml
- inspects the current state of the services in the cluster
- computes the diff and deployment plan to reconcile it
- executes the plan after the confirmation
Please see the docs and demo: https://uncloud.run/docs/guides/deployments/deploy-app
The main difference with Docker Swarm is that the reconciliation process is run on your local/CI machine as part of the 'uc deploy' CLI command execution, not on the control plane nodes in the cluster.
And it's not running in the loop automatically. If the command fails, you get an instant feedback with the errors you can address or rerun the command again.
It should be pretty straightforward to wrap the CLI logic in a Terraform or Pulumi provider. The design principals are very similar and it's written in Go.
Note that a DNS A record with multiple IPs doesn't provide failover, only round robin. But you can use the Cloudflare DNS proxy feature as a poor man's LB. Just add 2+ proxied A records (orange cloud) pointing to different machines. If one goes down with a 52x error, Cloudflare automatically fails over to the healthy one.
Also another community member shared his homelab with a couple dozen services migrated from Docker Compose to Uncloud: https://github.com/dasunsrule32/docker-compose-configs/tree/...
There is no automatic rescheduling in uncloud by design. At least for now. We will see how far we can get without it.
If you want your service to tolerate a host going down, you should deploy multiple replicas for that service on multiple machines in advance. 'uc scale' command can be used to run more replicas for an already deployed service.
Longer term, I'm thinking we can have a concept of primary/standby replicas for services that can only have one running replica, e.g. databases. Something similar to how Fly.io does this: https://fly.io/docs/apps/app-availability/#standby-machines-...
Regarding deploying on the less filled machine first is doable but not supported right now. By default, it picks the first machine randomly and tries to distributes replicas evenly among all available machines. You can also manually specify what target machine(s) each service should run on in your Compose file.
I want to avoid recreating the complexity with placement constraints, (anti-)affinity, etc. that makes K8s hard to reason about. There is a huge class of apps that need more or less static infra, manual placement, and a certain level of redundancy. That's what I'm targeting with Uncloud.
You can either specify one of the machine SSH target in the config.yaml or pass it directly to the 'uc' CLI command, e.g.
uc --connect user@host deploy
1. External requests, e.g. from the internet via the reverse proxy (Caddy) running in the cluster.
The rollout works on the container, not the server level. Each container registers itself in Caddy so it knows which containers to forward and distribute requests to.
When doing a rollout, a new version of container is started first, registers in caddy, then the old one is removed. This is repeated for each service container. This way, at any time there are running containers that serve requests.
It doesn’t say any server that requests shouldn’t go there. It just updates upstreams in the caddy config to send requests to the containers that are up and healthy.
2. Service to service requests within the cluster. In this case, a service DNS name is resolved to a list of IP addresses (running containers). And the client decides which one to send a request to or whether to distribute requests among them.
When the service is updated, the client needs to resolve the name again to get the up-to-date list of IPs. Many http clients handle this automatically so using http://service-name as an endpoint typically just works. But zero downtime should still be handled by the client in this case.
It’s a similar experience when a cloud provider manages the control plane for you. But you have to worry about the availability when you host everything yourself. Losing etcd quorum results in an unusable cluster.
Many people want to avoid this, especially when running at a smaller scale like a handful of machines.
The cluster network can even partition and each partition continues to operate allowing to deploy/update apps individually.
That’s essentially what we all did in a pre-k8s era with chef and ansible but without the boilerplate and reinventing the wheel, and using the learnings from k8s and friends.
I’m working full time on this, yes. Funding from my savings at the moment and don’t have plans for any external funding or VC.
For monetisation, considering building a self-hosted and managed (SaaS) webUI for managing remote clusters and apps on them with value-added PaaS-like features.