Compare commits

...
16 Commits
5 changed files with 173 additions and 32 deletions
+22 -3
View File
@@ -40,7 +40,9 @@ archives:
- id: uncloud
builds:
- uncloud
format: gz
formats:
# gz is not compatible with homebrew releases, so we use tar.gz
- tar.gz
name_template: >-
{{ .Binary }}_
{{- if eq .Os "darwin"}}macos
@@ -49,14 +51,16 @@ archives:
# use zip for windows archives
format_overrides:
- goos: windows
format: zip
formats:
- zip
files:
- none*
- id: uncloudd
builds:
- uncloudd
format: gz
formats:
- tar.gz
name_template: "{{ .Binary }}_{{ .Os }}_{{ .Arch }}"
files:
- none*
@@ -70,3 +74,18 @@ changelog:
exclude:
- "^docs:"
- "^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 }}"
+139 -17
View File
@@ -1,16 +1,73 @@
<div align="center">
<img src="./website/images/logo.svg" height="100" alt="Uncloud logo"/>
<h1>Uncloud</h2>
A lightweight Docker clustering tool for running web apps on your own servers — from cloud VMs to bare metal.<br>
Replace Heroku and Render, no Kubernetes required.
<h1>Uncloud</h1>
<p><strong>Docker simplicity. Multi-machine power.</strong></p>
</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:
![Diagram: multi-provider cluster of 3 machines](website/images/diagram.webp)
## 💫 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:
```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:
@@ -19,26 +76,91 @@ curl -fsSL https://raw.githubusercontent.com/psviderski/uncloud/main/scripts/ins
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
automatic HTTPS:
3. Create a DNS A record in your domain registrar that points `app.example.com` to your server's IP address. Allow a few
minutes for DNS propagation.
4. Deploy your app from a Docker image:
```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 ✨
## Project status
## ⚙️ How it works
Uncloud is an open source project I'm ([@psviderski](https://github.com/psviderski)) actively developing. I'd love to
share this journey with you. [Subscribe](https://uncloud.run/) to follow the progress, get early insights into new
features, and be the first to know when it's ready for production use.
Check out the [design document](docs/design.md) to understand Uncloud's design philosophy and goals. Here, let's peek
under the hood to see what happens when you run certain commands.
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)
- 💡 Have ideas? [Join the discussion](https://github.com/psviderski/uncloud/discussions)
```bash
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
View File
@@ -112,17 +112,17 @@ install_uncloud_binaries() {
local uncloudd_url
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
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
local uncloudd_download_path="${tmp_dir}/uncloudd.gz"
local uncloudd_download_path="${tmp_dir}/uncloudd.tar.gz"
log "⏳ Downloading uncloudd binary: ${uncloudd_url}"
if ! curl -fsSL -o "${uncloudd_download_path}" "${uncloudd_url}"; then
error "Failed to download uncloudd binary."
fi
gzip -d "${uncloudd_download_path}"
tar -xf "${uncloudd_download_path}"
if ! install "${uncloudd_download_path%.gz}" "${uncloudd_install_path}"; then
error "Failed to install uncloud binary to ${uncloudd_install_path}"
fi
@@ -196,7 +196,7 @@ install_corrosion() {
if ! curl -fsSL -o "${corrosion_download_path}" "${corrosion_url}"; then
error "Failed to download uncloud-corrosion binary."
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
error "Failed to install uncloud-corrosion binary to ${corrosion_install_path}"
fi
+3 -3
View File
@@ -64,7 +64,7 @@ esac
if [ "$VERSION" = "latest" ]; then
VERSION=$(latest_version)
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}"
CHECKSUM_URL="https://github.com/${GITHUB_REPO}/releases/download/$VERSION/checksums.txt"
@@ -89,14 +89,14 @@ cd "$TMP_DIR"
#echo "Checksum is valid."
# Decompress and install the binary.
gzip -d "${BINARY_NAME}"
tar -xf "${BINARY_NAME}"
if [ -z "${SUDO}" ]; then
echo "Installing uncloud binary to ${INSTALL_DIR}"
else
echo "Installing uncloud binary to ${INSTALL_DIR} using sudo. You may be prompted for your password."
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}"
print_manual_install
exit 1
+4 -4
View File
@@ -45,7 +45,7 @@
</li>
<li class="ml-1">
<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>
</ul>
</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>
<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>
<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 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> </pre>
@@ -505,7 +505,7 @@
</section>
<!-- CTA -->
<section>
<section id="subscribe">
<div class="py-12 md:py-20">
<div class="max-w-6xl mx-auto px-4 sm:px-6">
<div class="relative max-w-3xl mx-auto text-center">