# Self-host and scale web apps without Kubernetes complexity | Uncloud
Take your Docker Compose apps to production with zero-downtime deployments, automatic HTTPS, and cross-machine scaling. Self-hosting made reliable without the complexity.

# Multi-machine Docker Compose for production

Uncloud creates a Docker cluster with self-configuring WireGuard mesh, zero-downtime deploys, cross-machine scaling, automatic HTTPS via Caddy, service discovery, and an intuitive CLI. Everything you need to run web apps on cloud VMs or your own hardware, using familiar Compose files. Without cluster-management overhead.

A simpler alternative to Kubernetes, Swarm, Nomad, and Kamal.

Deploy your first app
Star on GitHub
Your browser does not support the video tag.

#### 5.2K+

GitHub stars

#### 21K+

Downloads

#### 2x

Hacker News front page

#### 380

Fans on Discord

“

> We’re moving 300+ websites onto a dozen Uncloud machines, where each owner gets their own machines to run whatever they need.

Read Radboud University’s announcement
Miek Gieben
Creator of CoreDNS · ex-Google SRE · Radboud University

## Enjoy a Heroku-like workflow on your own servers

Create a cluster

## Turn any Linux machines into a Docker cluster

Uncloud creates a lightweight decentralised cluster of Docker hosts that share their state peer-to-peer. The cluster keeps running even if some machines go down.
Each machine joins a secure WireGuard mesh network, so services can talk to each other across machines.

- Mix cloud VMs and bare-metal servers
- No control plane or quorum to maintain
- Cheap enough to run a cluster per team, project, or client
- SSH is all you need

    $ uc machine init --name prod-1 root@137.123.45.67
    ...
    ✔ Docker installed and configured
    ✔ Uncloud machine daemon installed and started
    Cluster initialised with machine prod-1 and saved as context default
    in your local config (~/.config/uncloud/config.yaml)

    $ uc machine add --name prod-2 ubuntu@65.21.45.67
    ...
    ✔ Uncloud machine daemon installed and started
    Machine prod-2 joined the cluster.

    $ uc machine ls
    NAME    STATE  ADDRESS        PUBLIC IP      WIREGUARD ENDPOINTS
    prod-1  Up     10.210.0.1/24  137.123.45.67  10.0.0.95:51820, 137.123.45.67:51820
    prod-2  Up     10.210.1.1/24  65.21.45.67    192.168.40.243:51820, 65.21.45.67:51820

> “In my opinion, Swarm is dead, Nomad is not anymore a free software since it was bought by IBM. So the only robust alternative to the bloat of K8s is Uncloud.”

> Frédéric Logier (@fredix)

Build, push, deploy

## Deploy local Compose to production with one command

Run `uc deploy` with your standard `compose.yaml`. It builds the images, pushes them straight to your machines (transferring only the missing layers), and shows a plan of exactly what will change. Confirm, and it rolls out with zero downtime. And all this takes seconds.

- Standard Compose format, no new spec to learn
- No registry to set up, pay for, or authenticate against. Powered by Unregistry
- Terraform-style plan: review every change before applying
- Zero-downtime rolling updates with health checks
- Run locally or in your CI/CD pipelines

> “Uncloud checks almost all the boxes for me: an imperative push model, Compose Specification, WireGuard out of the box, Caddy out of the box, Unregistry (a big one), and a great CLI UX with excellent documentation.”

> @pvalders

    $ cat compose.yaml
    services:
      web:
        build: .
        scale: 2

    $ uc deploy
    [+] Building 1/1
     ✔ app/web:2026-04-23-053642.833b6c1  Built

    [+] Pushing image app/web:2026-04-23-053642.833b6c1 to cluster 2/2
     ✔ Pushing app/web:2026-04-23-053642.833b6c1 to prod-1  Pushed
     ✔ Pushing app/web:2026-04-23-053642.833b6c1 to prod-2  Pushed

    Deployment plan
    ~ update service web
      │ ~ image: app/web:2026-04-10-070838.07e380b → 2026-04-23-053642.833b6c1
      │   replicas: 2
      ├── +/- replace container web/2de0ebf7eld5 on prod-1
      ╰── +/- replace container web/4c908f74ced5 on prod-2
    ───────────────────────────────────────────
    2 replace (start-first) · across 2 machines

    Proceed with deployment? [y/N] y

    [+] Deploying 4/4
     ✔ Container web/5e2becd8ba1b on prod-1  Healthy
     ✔ Container web/2de0ebf7eld5 on prod-1  Removed
     ✔ Container web/c086013b5879 on prod-2  Healthy
     ✔ Container web/4c908f74ced5 on prod-2  Removed

Publish

## Connect and publish your services

Services on different machines reach each other by name over a private WireGuard mesh, so your database never needs a public port. Publish the ones that should be public, and the integrated Caddy handles HTTPS automatically.

- Cross-machine communication and load balancing
- Caddy keeps its config up to date on any container change
- Certificates issued and renewed automatically

compose.yaml

    services:
      web:
        build: .
        environment:
          # Reach db on another machine by its service name.
          DATABASE_URL: postgres://db:5432/app
        # Uncloud-specific extension to publish via the Caddy ingress.
        x-ports:
          - app.example.com:8080/https
        scale: 2  # Two replicas spread across machines.

      db:
        image: postgres:18
        volumes:
          - db-data:/var/lib/postgresql

> “Before Uncloud, I was already using Wireguard, Caddy and docker-compose. Now I just have a simpler way to set it up and have it all be almost-automatically managed, plus I get DNS resolution for microservices for free.”

> Paweł Zmarzły (@szawo)

Scale

## Scale and grow without the ops overhead

Run more replicas spread across machines for high availability. If a machine goes down, the remaining replicas keep serving requests. Need capacity? Add a machine. Retiring one? Remove it and redeploy.

Nothing scales or reschedules on its own. It's predictable, boring infrastructure for your peace of mind.

    $ uc machine add --name prod-3 root@201.45.91.123
    ...
    Machine prod-3 joined the cluster.

    $ uc scale web 3
    Scaling plan
    ~ update service web
      │ ~ replicas: 2 → 3
      ╰── +   run container web on prod-3
    ───────────────────────────
    1 create · across 1 machine

    Proceed with scaling? [y/N] y

    $ uc ls
    NAME   MODE        REPLICAS  ENDPOINTS
    caddy  global      3
    db     replicated  1
    web    replicated  3         https://app.example.com → :8080

Troubleshoot

## Troubleshoot with tools you already know

When something breaks, use `uc` to list containers, read logs, and inspect both the WireGuard network and the live Caddy config. Or SSH in and use the familiar Docker and Linux tools. It's just Docker, WireGuard and Caddy underneath, exactly as you'd expect.

> “It's all built on top of simple and familiar technologies. … This gives me confidence that if something goes wrong, I will be able to debug myself. I cannot say the same for SwarmKit or etcd.”

> Paweł Zmarzły (@szawo)

    $ uc logs -f web
    Jun 11 09:14:02.113 prod-1 web/5e2be GET /api/products 200 12ms
    Jun 11 09:14:02.864 prod-3 web/9f1c2 GET /healthz 200 1ms

    $ uc caddy config
    app.example.com {
        reverse_proxy 10.210.0.5:8080 10.210.1.7:8080 10.210.2.4:8080
    }
    ...

    $ uc exec web -- ping -c1 db
    PING db (10.210.1.3): 56 data bytes
    64 bytes from 10.210.1.3: seq=0 ttl=64 time=1.42 ms

    $ uc wg show --machine prod-1
    PEER    ENDPOINT             HANDSHAKE  RTT    RECEIVED  SENT
    prod-2  65.21.45.67:51820    14s ago    3ms    27.51MB   40.69MB
    prod-3  201.45.91.123:51820  1m56s ago  11ms   5.12MB    9.34MB

    $ ssh root@prod-1 docker ...

## Frequently asked questions

Have another questions? Ask us on Discord.

How is this different from Docker Swarm?

Swarm needs a Raft quorum of manager nodes and has been in maintenance mode for years. Uncloud has no control plane at all. Machines sync state peer to peer, and any subset keeps working. You also get WireGuard networking across clouds and even behind NAT, automatic HTTPS via Caddy, and image push without a registry. Swarm gives you none of that out of the box.

What happens when a machine goes down?

The rest of the cluster keeps working. There's no control plane to lose, and healthy replicas on other machines keep serving traffic. Uncloud deliberately doesn't auto-reschedule containers. You decide when to replace the machine and redeploy, so nothing unpredictable happens without your control.

Will it replace my CI/CD?

No, and that's deliberate. `uc` is just a CLI that connects to your cluster over SSH. Run `uc deploy` locally or in GitHub Actions to integrate with existing workflows. There are no agents to install and no pipelines to migrate.

## Ready to make deploying to your own servers fun?

Deploy your first app
Join Discord

Subscribe to follow the development journey and get early insights into new features.
See previous newsletters.