mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d25bbafdf | ||
|
|
58e1c2eea3 | ||
|
|
6c791840bd | ||
|
|
3af97306e1 | ||
|
|
d645958631 | ||
|
|
48bc94828e | ||
|
|
a43f4bf5df | ||
|
|
22f705d6c9 | ||
|
|
efcb9ee947 | ||
|
|
a347b32b65 | ||
|
|
7e865b61c2 | ||
|
|
b959f5d9f3 | ||
|
|
df64a00813 | ||
|
|
07dc5c373d | ||
|
|
a6e8616b36 | ||
|
|
7041244728 |
+22
-3
@@ -40,7 +40,9 @@ archives:
|
|||||||
- id: uncloud
|
- id: uncloud
|
||||||
builds:
|
builds:
|
||||||
- uncloud
|
- uncloud
|
||||||
format: gz
|
formats:
|
||||||
|
# gz is not compatible with homebrew releases, so we use tar.gz
|
||||||
|
- tar.gz
|
||||||
name_template: >-
|
name_template: >-
|
||||||
{{ .Binary }}_
|
{{ .Binary }}_
|
||||||
{{- if eq .Os "darwin"}}macos
|
{{- if eq .Os "darwin"}}macos
|
||||||
@@ -49,14 +51,16 @@ archives:
|
|||||||
# use zip for windows archives
|
# use zip for windows archives
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
format: zip
|
formats:
|
||||||
|
- zip
|
||||||
files:
|
files:
|
||||||
- none*
|
- none*
|
||||||
|
|
||||||
- id: uncloudd
|
- id: uncloudd
|
||||||
builds:
|
builds:
|
||||||
- uncloudd
|
- uncloudd
|
||||||
format: gz
|
formats:
|
||||||
|
- tar.gz
|
||||||
name_template: "{{ .Binary }}_{{ .Os }}_{{ .Arch }}"
|
name_template: "{{ .Binary }}_{{ .Os }}_{{ .Arch }}"
|
||||||
files:
|
files:
|
||||||
- none*
|
- none*
|
||||||
@@ -70,3 +74,18 @@ changelog:
|
|||||||
exclude:
|
exclude:
|
||||||
- "^docs:"
|
- "^docs:"
|
||||||
- "^test:"
|
- "^test:"
|
||||||
|
|
||||||
|
brews:
|
||||||
|
- name: uncloud
|
||||||
|
description: "Uncloud CLI"
|
||||||
|
homepage: "https://uncloud.run"
|
||||||
|
ids:
|
||||||
|
- uncloud
|
||||||
|
install: |
|
||||||
|
bin.install "uncloud"
|
||||||
|
bin.install_symlink "uncloud" => "uc"
|
||||||
|
skip_upload: false
|
||||||
|
repository:
|
||||||
|
owner: psviderski
|
||||||
|
name: homebrew-tap
|
||||||
|
token: "{{ .Env.GITHUB_TOKEN_HOMEBREW_TAP }}"
|
||||||
|
|||||||
@@ -1,16 +1,73 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="./website/images/logo.svg" height="100" alt="Uncloud logo"/>
|
<img src="./website/images/logo.svg" height="100" alt="Uncloud logo"/>
|
||||||
<h1>Uncloud</h2>
|
<h1>Uncloud</h1>
|
||||||
A lightweight Docker clustering tool for running web apps on your own servers — from cloud VMs to bare metal.<br>
|
<p><strong>Docker simplicity. Multi-machine power.</strong></p>
|
||||||
Replace Heroku and Render, no Kubernetes required.
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Quick start
|
Uncloud is a lightweight clustering and container orchestration tool that lets you deploy and manage web apps across
|
||||||
|
cloud VMs and bare metal with minimized cluster management overhead. It creates a secure WireGuard mesh network between
|
||||||
|
your Docker hosts and provides automatic service discovery, load balancing, ingress with HTTPS, and simple CLI commands
|
||||||
|
to manage your apps.
|
||||||
|
|
||||||
|
Unlike traditional orchestrators, there's no central control plane and quorum to maintain. Each machine maintains a
|
||||||
|
synchronized copy of the cluster state through peer-to-peer communication, keeping cluster operations functional even if
|
||||||
|
some machines go offline.
|
||||||
|
|
||||||
|
Uncloud aims to be the solution for developers who want the flexibility of self-hosted infrastructure without the
|
||||||
|
operational complexity of Kubernetes.
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
* **Deploy anywhere**: Combine cloud VMs, dedicated servers, and bare metal into a unified computing environment —
|
||||||
|
regardless of location or provider.
|
||||||
|
* **Zero-config private network**: Automatic WireGuard mesh with peer discovery and NAT traversal. Containers get unique
|
||||||
|
IPs for direct cross-machine communication.
|
||||||
|
* **No control plane**: Fully decentralized design eliminates single points of failure and reduces operational overhead.
|
||||||
|
* **Imperative over declarative**: Favoring imperative operations over state reconciliation simplifies both the mental
|
||||||
|
model and troubleshooting.
|
||||||
|
* **Service discovery**: Built-in DNS server resolves service names to container IPs.
|
||||||
|
* **Automatic HTTPS**: Built-in Caddy reverse proxy handles TLS certificate provisioning and renewal using Let's
|
||||||
|
Encrypt.
|
||||||
|
* **Docker-like CLI**: Familiar commands for managing both infrastructure and applications.
|
||||||
|
* **Remote management**: Control your entire infrastructure through SSH access to any single machine in the cluster.
|
||||||
|
|
||||||
|
Coming soon:
|
||||||
|
|
||||||
|
* Project isolation through environments/namespaces
|
||||||
|
* Infrastructure as Code using Docker Compose format
|
||||||
|
* Persistent volumes and secrets management
|
||||||
|
* Monitoring and log aggregation
|
||||||
|
* Database deployment and management
|
||||||
|
* Curated application catalog
|
||||||
|
|
||||||
|
Here is a diagram of an Uncloud multi-provider cluster of 3 machines:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 💫 Why Uncloud?
|
||||||
|
|
||||||
|
Modern cloud platforms like Heroku and Render offer amazing developer experiences but at a premium price. Traditional
|
||||||
|
container orchestrators like Kubernetes provide power and flexibility but require significant operational expertise. I
|
||||||
|
believe there's a sweet spot in between — a pragmatic solution for the majority of us who aren't running at Google
|
||||||
|
scale. You should be able to:
|
||||||
|
|
||||||
|
* **Own your infrastructure and data**: Whether driven by costs, compliance, or flexibility, run applications on any
|
||||||
|
combination of cloud VMs and personal hardware while maintaining the cloud-like experience you love.
|
||||||
|
* **Stay simple**: Don't worry about control planes, highly-available clusters, or complex YAML configurations for
|
||||||
|
common use cases.
|
||||||
|
* **Build with proven primitives**: Get production-grade networking, deployment primitives, service discovery, load
|
||||||
|
balancing, and ingress with HTTPS out of the box without becoming a distributed systems expert.
|
||||||
|
* **Support sustainable computing** 🌿: Minimize system overhead to maximize resources available for your applications.
|
||||||
|
|
||||||
|
Uncloud's goal is to make deployment and management of containerized applications feel as seamless as using a cloud
|
||||||
|
platform, whether you're running on a $5 VPS, a spare Mac mini, or a rack of bare metal servers.
|
||||||
|
|
||||||
|
## 🚀 Quick start
|
||||||
|
|
||||||
1. Install Uncloud CLI:
|
1. Install Uncloud CLI:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://raw.githubusercontent.com/psviderski/uncloud/main/scripts/install_cli.sh | sh
|
curl -fsS https://get.uncloud.run/install.sh | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Initialize your first machine:
|
2. Initialize your first machine:
|
||||||
@@ -19,26 +76,91 @@ curl -fsSL https://raw.githubusercontent.com/psviderski/uncloud/main/scripts/ins
|
|||||||
uc machine init root@your-server-ip
|
uc machine init root@your-server-ip
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Create a DNS A record pointing `app.example.com` to your server's IP address, then deploy your app with
|
3. Create a DNS A record in your domain registrar that points `app.example.com` to your server's IP address. Allow a few
|
||||||
automatic HTTPS:
|
minutes for DNS propagation.
|
||||||
|
4. Deploy your app from a Docker image:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uc run --name my-app -p app.example.com:8000/https registry/app
|
uc run -p app.example.com:8000/https my-app-image
|
||||||
```
|
```
|
||||||
|
|
||||||
That's it! Your app is now running and accessible at https://app.example.com ✨
|
That's it! Your app is now running and accessible at https://app.example.com ✨
|
||||||
|
|
||||||
## Project status
|
## ⚙️ How it works
|
||||||
|
|
||||||
Uncloud is an open source project I'm ([@psviderski](https://github.com/psviderski)) actively developing. I'd love to
|
Check out the [design document](docs/design.md) to understand Uncloud's design philosophy and goals. Here, let's peek
|
||||||
share this journey with you. [Subscribe](https://uncloud.run/) to follow the progress, get early insights into new
|
under the hood to see what happens when you run certain commands.
|
||||||
features, and be the first to know when it's ready for production use.
|
|
||||||
|
|
||||||
I'd also love your input:
|
**When you initialize a new cluster on a machine:**
|
||||||
|
|
||||||
- 🐛 Found a bug? [Open an issue](https://github.com/psviderski/uncloud/issues)
|
```bash
|
||||||
- 💡 Have ideas? [Join the discussion](https://github.com/psviderski/uncloud/discussions)
|
uc machine init root@your-server-ip
|
||||||
|
```
|
||||||
|
|
||||||
## Motivation
|
1. The CLI SSHs into the machine and installs Docker, the `uncloudd` machine daemon and
|
||||||
|
[corrosion](https://github.com/superfly/corrosion) service, managed by systemd.
|
||||||
|
2. Generates a unique WireGuard key pair, allocates a dedicated subnet `10.210.0.0/24` for the machine and its
|
||||||
|
containers, and configures `uncloudd` accordingly. All subsequent communication happens with `uncloudd`
|
||||||
|
through its gRPC API over SSH.
|
||||||
|
3. Configures and starts `corrosion`, a CRDT-based distributed SQLite database to share cluster state between machines.
|
||||||
|
4. Creates a Docker bridge network connected to the WireGuard interface.
|
||||||
|
5. This machine becomes an entry point for the newly created cluster which is stored in the cluster config under
|
||||||
|
`~/.config/uncloud` on your local machine.
|
||||||
|
|
||||||
TBD
|
**When you add another machine:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uc machine add ubuntu@second-server-ip
|
||||||
|
```
|
||||||
|
|
||||||
|
1. The second machine gets provisioned just like the first. A non-root SSH user will need `sudo` access.
|
||||||
|
2. Allocates a new subnet `10.210.1.0/24` for the second machine and its containers.
|
||||||
|
3. Registers the second machine in the cluster state and exchanges WireGuard keys with the first machine.
|
||||||
|
4. Both machines establish a WireGuard tunnel between each other, allowing Docker containers connected to the bridge
|
||||||
|
network to communicate directly across machines.
|
||||||
|
5. Configures and starts `corrosion` on the second machine to sync the cluster state.
|
||||||
|
6. The second machine is added as an alternative entry point in the cluster config.
|
||||||
|
7. If one of the machines goes offline, the other machine can still serve cluster operations.
|
||||||
|
|
||||||
|
If one more machine is added, the process repeats with a new subnet. The new machine needs to establish a WireGuard
|
||||||
|
connection with only one of the existing machines. Other machines will learn about it through the shared cluster state
|
||||||
|
and automatically establish a WireGuard tunnel with it.
|
||||||
|
|
||||||
|
**When you run a service:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uc run -p app.example.com:8000/https my-app-image
|
||||||
|
```
|
||||||
|
|
||||||
|
1. CLI picks a machine to run your container.
|
||||||
|
2. `uncloudd` that the CLI communicates with uses [`grpc-proxy`](https://github.com/siderolabs/grpc-proxy) to forward
|
||||||
|
the request to the target machine to launch a container there.
|
||||||
|
3. `uncloudd` on the target machine starts the Docker container in the bridge network and stores its info in the
|
||||||
|
cluster's distributed state.
|
||||||
|
4. The container gets a cluster-unique IP address from the bridge network (in the `10.210.X.2-254` range) and becomes
|
||||||
|
accessible from other machines in the cluster.
|
||||||
|
5. Caddy reverse proxy which runs in [`global`](https://github.com/compose-spec/compose-spec/blob/main/deploy.md#mode)
|
||||||
|
mode on each machine watches the cluster state for new services and updates its configuration to route traffic to the
|
||||||
|
new container.
|
||||||
|
|
||||||
|
Look ma, no control plane or master nodes to maintain! Just a simple overlay network and eventually consistent state
|
||||||
|
sync that lets machines work together. Want to check on things or make changes? Connect to any machine either implicitly
|
||||||
|
using the CLI or directly over SSH. They all have the complete cluster state and can control everything. It's like each
|
||||||
|
machine is a full backup of your control plane.
|
||||||
|
|
||||||
|
## 🏗 Project status
|
||||||
|
|
||||||
|
Uncloud is currently in active development and is **not ready for production use**. Features may change significantly
|
||||||
|
and there may be breaking changes between releases.
|
||||||
|
|
||||||
|
I'd love your input! Here's how you can contribute:
|
||||||
|
|
||||||
|
* 🐛 Found a bug? [Open an issue](https://github.com/psviderski/uncloud/issues)
|
||||||
|
* 💡 Have ideas? [Join the discussion](https://github.com/psviderski/uncloud/discussions)
|
||||||
|
|
||||||
|
## 📫 Stay updated
|
||||||
|
|
||||||
|
* [Subscribe](https://uncloud.run/#subscribe) to my newsletter to follow the progress, get early insights into new
|
||||||
|
features, and be the first to know when it's ready for production use.
|
||||||
|
* Watch this repository for releases.
|
||||||
|
* Follow [@psviderski](https://github.com/psviderski) on GitHub.
|
||||||
|
|||||||
+5
-5
@@ -112,17 +112,17 @@ install_uncloud_binaries() {
|
|||||||
|
|
||||||
local uncloudd_url
|
local uncloudd_url
|
||||||
if [ "${UNCLOUD_VERSION}" == "latest" ]; then
|
if [ "${UNCLOUD_VERSION}" == "latest" ]; then
|
||||||
uncloudd_url="${UNCLOUD_GITHUB_URL}/releases/latest/download/uncloudd_linux_${file_arch}.gz"
|
uncloudd_url="${UNCLOUD_GITHUB_URL}/releases/latest/download/uncloudd_linux_${file_arch}.tar.gz"
|
||||||
else
|
else
|
||||||
uncloudd_url="${UNCLOUD_GITHUB_URL}/releases/download/${UNCLOUD_VERSION}/uncloudd_linux_${file_arch}.gz"
|
uncloudd_url="${UNCLOUD_GITHUB_URL}/releases/download/${UNCLOUD_VERSION}/uncloudd_linux_${file_arch}.tar.gz"
|
||||||
fi
|
fi
|
||||||
local uncloudd_download_path="${tmp_dir}/uncloudd.gz"
|
local uncloudd_download_path="${tmp_dir}/uncloudd.tar.gz"
|
||||||
|
|
||||||
log "⏳ Downloading uncloudd binary: ${uncloudd_url}"
|
log "⏳ Downloading uncloudd binary: ${uncloudd_url}"
|
||||||
if ! curl -fsSL -o "${uncloudd_download_path}" "${uncloudd_url}"; then
|
if ! curl -fsSL -o "${uncloudd_download_path}" "${uncloudd_url}"; then
|
||||||
error "Failed to download uncloudd binary."
|
error "Failed to download uncloudd binary."
|
||||||
fi
|
fi
|
||||||
gzip -d "${uncloudd_download_path}"
|
tar -xf "${uncloudd_download_path}"
|
||||||
if ! install "${uncloudd_download_path%.gz}" "${uncloudd_install_path}"; then
|
if ! install "${uncloudd_download_path%.gz}" "${uncloudd_install_path}"; then
|
||||||
error "Failed to install uncloud binary to ${uncloudd_install_path}"
|
error "Failed to install uncloud binary to ${uncloudd_install_path}"
|
||||||
fi
|
fi
|
||||||
@@ -196,7 +196,7 @@ install_corrosion() {
|
|||||||
if ! curl -fsSL -o "${corrosion_download_path}" "${corrosion_url}"; then
|
if ! curl -fsSL -o "${corrosion_download_path}" "${corrosion_url}"; then
|
||||||
error "Failed to download uncloud-corrosion binary."
|
error "Failed to download uncloud-corrosion binary."
|
||||||
fi
|
fi
|
||||||
tar -xzf "${corrosion_download_path}" -C "${tmp_dir}"
|
tar -xf "${corrosion_download_path}" -C "${tmp_dir}"
|
||||||
if ! install "${tmp_dir}/corrosion" "${corrosion_install_path}"; then
|
if ! install "${tmp_dir}/corrosion" "${corrosion_install_path}"; then
|
||||||
error "Failed to install uncloud-corrosion binary to ${corrosion_install_path}"
|
error "Failed to install uncloud-corrosion binary to ${corrosion_install_path}"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ esac
|
|||||||
if [ "$VERSION" = "latest" ]; then
|
if [ "$VERSION" = "latest" ]; then
|
||||||
VERSION=$(latest_version)
|
VERSION=$(latest_version)
|
||||||
fi
|
fi
|
||||||
BINARY_NAME="uncloud_${BINARY_OS}_${BINARY_ARCH}.gz"
|
BINARY_NAME="uncloud_${BINARY_OS}_${BINARY_ARCH}.tar.gz"
|
||||||
BINARY_URL="https://github.com/${GITHUB_REPO}/releases/download/${VERSION}/${BINARY_NAME}"
|
BINARY_URL="https://github.com/${GITHUB_REPO}/releases/download/${VERSION}/${BINARY_NAME}"
|
||||||
CHECKSUM_URL="https://github.com/${GITHUB_REPO}/releases/download/$VERSION/checksums.txt"
|
CHECKSUM_URL="https://github.com/${GITHUB_REPO}/releases/download/$VERSION/checksums.txt"
|
||||||
|
|
||||||
@@ -89,14 +89,14 @@ cd "$TMP_DIR"
|
|||||||
#echo "Checksum is valid."
|
#echo "Checksum is valid."
|
||||||
|
|
||||||
# Decompress and install the binary.
|
# Decompress and install the binary.
|
||||||
gzip -d "${BINARY_NAME}"
|
tar -xf "${BINARY_NAME}"
|
||||||
|
|
||||||
if [ -z "${SUDO}" ]; then
|
if [ -z "${SUDO}" ]; then
|
||||||
echo "Installing uncloud binary to ${INSTALL_DIR}"
|
echo "Installing uncloud binary to ${INSTALL_DIR}"
|
||||||
else
|
else
|
||||||
echo "Installing uncloud binary to ${INSTALL_DIR} using sudo. You may be prompted for your password."
|
echo "Installing uncloud binary to ${INSTALL_DIR} using sudo. You may be prompted for your password."
|
||||||
fi
|
fi
|
||||||
if ! $SUDO install "${BINARY_NAME%.gz}" "${INSTALL_DIR}/uncloud"; then
|
if ! $SUDO install "${BINARY_NAME%.tar.gz}" "${INSTALL_DIR}/uncloud"; then
|
||||||
echo "Failed to install uncloud binary to ${INSTALL_DIR}"
|
echo "Failed to install uncloud binary to ${INSTALL_DIR}"
|
||||||
print_manual_install
|
print_manual_install
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
+4
-4
@@ -45,7 +45,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="ml-1">
|
<li class="ml-1">
|
||||||
<a class="btn-sm text-zinc-100 bg-zinc-900 hover:bg-zinc-800 w-full shadow"
|
<a class="btn-sm text-zinc-100 bg-zinc-900 hover:bg-zinc-800 w-full shadow"
|
||||||
href="https://github.com/psviderski/uncloud?tab=readme-ov-file#quick-start">Get Started</a>
|
href="https://github.com/psviderski/uncloud/?tab=readme-ov-file#-quick-start">Get Started</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
<div class="max-w-xs mx-auto sm:max-w-none sm:inline-flex sm:justify-center space-y-4 sm:space-y-0 sm:space-x-4">
|
<div class="max-w-xs mx-auto sm:max-w-none sm:inline-flex sm:justify-center space-y-4 sm:space-y-0 sm:space-x-4">
|
||||||
<div>
|
<div>
|
||||||
<a class="btn text-zinc-100 bg-zinc-900 hover:bg-zinc-800 w-full shadow"
|
<a class="btn text-zinc-100 bg-zinc-900 hover:bg-zinc-800 w-full shadow"
|
||||||
href="https://github.com/psviderski/uncloud?tab=readme-ov-file#quick-start">Quick Start</a>
|
href="https://github.com/psviderski/uncloud/?tab=readme-ov-file#-quick-start">Quick Start</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn text-zinc-600 bg-white hover:text-zinc-900 w-full shadow flex items-center justify-center"
|
<a class="btn text-zinc-600 bg-white hover:text-zinc-900 w-full shadow flex items-center justify-center"
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
|
|
||||||
<pre> </pre>
|
<pre> </pre>
|
||||||
<pre class="text-zinc-500 select-none"># Deploy your app with automatic HTTPS</pre>
|
<pre class="text-zinc-500 select-none"># Deploy your app with automatic HTTPS</pre>
|
||||||
<pre><span class="text-emerald-500 select-none">$ </span>uc run --name my-app -p app.example.com:8000/https registry/app</pre>
|
<pre><span class="text-emerald-500 select-none">$ </span>uc run --name my-app -p app.example.com:8000/https app-image:latest</pre>
|
||||||
<pre class="text-zinc-200">✨ Your app is available at <span class="underline">https://app.example.com</span></pre>
|
<pre class="text-zinc-200">✨ Your app is available at <span class="underline">https://app.example.com</span></pre>
|
||||||
|
|
||||||
<pre> </pre>
|
<pre> </pre>
|
||||||
@@ -505,7 +505,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- CTA -->
|
<!-- CTA -->
|
||||||
<section>
|
<section id="subscribe">
|
||||||
<div class="py-12 md:py-20">
|
<div class="py-12 md:py-20">
|
||||||
<div class="max-w-6xl mx-auto px-4 sm:px-6">
|
<div class="max-w-6xl mx-auto px-4 sm:px-6">
|
||||||
<div class="relative max-w-3xl mx-auto text-center">
|
<div class="relative max-w-3xl mx-auto text-center">
|
||||||
|
|||||||
Reference in New Issue
Block a user