docs: combine landing page and docusaurus together and deploy as one image

This commit is contained in:
Pasha Sviderski
2025-07-31 18:43:06 +10:00
parent cac1550b7d
commit 4fe360c573
47 changed files with 19097 additions and 19102 deletions
+151
View File
@@ -0,0 +1,151 @@
Output demo.mp4
Set Shell "bash"
Set FontSize 15
Set FontFamily "SauceCodePro Nerd Font"
Set Width 1200
Set Height 700
Set WindowBar Colorful
Set Theme "Solarized Dark Higher Contrast"
Set Padding 20
Type "# 🚀 Uncloud demo: Docker simplicity with multi-machine power"
Enter
Sleep 1s
Type "# Let's deploy a highly available web app with automatic HTTPS in just a couple minutes"
Enter
Enter
Sleep 1s
Type "# First, let's initialise our first machine using a free Oracle Cloud VM"
Enter
Sleep 1s
Type "uc machine init --name oracle-vm ubuntu@152.67.101.197"
Sleep 1s
Enter
# This takes time, so we wait for it to complete.
Wait@1m
Sleep 3s
Enter
Type "# Let's see our machine in the cluster"
Enter
Type "uc machine ls"
Enter
Wait
Sleep 3s
Ctrl+L
Type "# Now let's deploy a simple web app with automatic HTTPS called myapp"
Enter
Sleep 1s
Type "uc run -n myapp -p myapp.psviderski.name:80/https traefik/whoami"
Sleep 1s
Enter
Wait
Sleep 2s
Enter
Type "# The -p flag publishes the container port 80 as HTTPS via reverse proxy Caddy using the provided domain"
Enter
Type "# Let's check it out! First using the auto-generated uncloud domain"
Enter
Sleep 1s
Hide
Sleep 5s
Show
Type "curl https://myapp.xuw3xd.cluster.uncloud.run"
Sleep 1s
Enter
Wait
Sleep 2s
Enter
Type "# For the provided domain to work, we need to manually add a DNS record pointing to the machine IP"
Enter
Type "# To simplify IP management, I added a CNAME record: myapp.psviderski.name -> myapp.xuw3xd.cluster.uncloud.run"
Enter
Sleep 2s
Type "curl https://myapp.psviderski.name"
Sleep 1s
Enter
Wait
Sleep 1s
Enter
Type "# Sweet!"
Enter
Sleep 2s
Ctrl+L
Type "# Let's add redundancy by adding another machine, a Hetzner server in a different region"
Enter
Sleep 1s
Type "uc machine add --name hetzner-server root@5.223.45.199"
Sleep 1s
Enter
Wait@1m
Sleep 3s
Enter
Type "# Now the cool part - let's add a powerful server from my home network to the same cluster"
Enter
Type "# Uncloud lets you mix cloud VMs with your own hardware to save costs on resource-intensive workloads"
Enter
Sleep 2s
Type "uc machine add --name home-server --public-ip none spy@192.168.40.243"
Sleep 2s
Enter
Wait@1m
Sleep 3s
Enter
Type "# Let's see our hybrid cluster spanning cloud providers and home network"
Enter
Sleep 1s
Type "uc machine ls"
Sleep 1s
Enter
Wait
Sleep 3s
Ctrl+L
Type "# Time to scale our service across all machines"
Enter
Sleep 1s
Type "uc scale myapp 3"
Sleep 1s
Enter
Wait
Sleep 1s
Enter
Type "uc inspect myapp"
Sleep 1s
Enter
Wait
Sleep 2s
Enter
Type "# Let's see load balancing in action"
Enter
Sleep 1s
Type "for i in {1..5}; do curl -s https://myapp.psviderski.name | grep Hostname; done"
Sleep 1s
Enter
Wait
Sleep 3s
Ctrl+L
Type "# ✨ And that's it! We've deployed a highly available web app across providers in just a couple minutes"
Enter
Type "# Your service stays up even if the home server and any cloud machine go down"
Enter
Type "# No master nodes to maintain, no complex cluster setup. Try Uncloud today! 🚀"
Enter
Sleep 1s
+157
View File
@@ -0,0 +1,157 @@
# Uncloud design
Uncloud aims to provide a solution for deploying and running containerized applications and services across various
platforms — from cloud VPSs to Raspberry Pis and powerful servers — or any combination of them.
While existing prominent PaaS solutions like https://coolify.io/ and https://dokploy.com/ use a single machine or Docker
Swarm as their underlying infrastructure, Uncloud takes a different approach. It aims to offer a multi-host experience
similar to the serverless solutions provided by cloud providers but on users' own machines. Users should be able to add
machines as compute resources without worrying about control plane high availability and cluster management. For
example, a user could combine a cloud VPS with a spare Raspberry Pi to create a unified computing environment.
Uncloud aims to replace the concept of a "cluster" with a "network" of machines. It's similar to Tailscale's design
where users can add machines to a network to enable secure communication between them. The primary challenges in this
design are:
1. Building such a network
2. Orchestrating containers across it
3. Exposing services to the internet
## Network of machines
While Tailscale is an obvious solution for establishing an overlay network, it has some drawbacks:
1. Users must register an account and generate a key for each machine.
2. Every container requires a Tailscale proxy to communicate with other machines and containers.
The Tailscale subnet router feature could potentially be used to route traffic from the machine to the internal Docker
network.
As an alternative, we can configure a simple flat WireGuard mesh network during the machine setup process. For example:
| CIDR | Description |
|-----------------|----------------------------------------------------------|
| `10.210.0.0/16` | The entire WireGuard mesh network |
| `10.210.X.0/24` | `/24` subnet assigned to machine `X` |
| `10.210.X.1/32` | Machine `X` address is the first address from the subnet |
| `10.210.X.Y/32` | Container `Y` address running on machine `X` |
Testing has shown that inter-container and machine routing works well when `/24` is assigned to the Docker network
bridge, while the WireGuard interface is configured with just the `/32` machine address.
This setup allows containers to communicate with each other on the same machine or across machines without address
translation. This design also enables the future implementation of [ACLs](https://tailscale.com/kb/1018/acls) and
security groups to restrict traffic between machines and containers if needed.
The peer discovery and NAT traversal techniques for the WireGuard mesh are heavily inspired by the
Talos [KubeSpan](https://www.talos.dev/v1.7/talos-guides/network/kubespan/) design.
## Orchestration
The main question that drives the design of the orchestration system is can we build a system that doesn't require a
centralised control plane? The short answer is yes, we can. However, it's not easy as decentralised systems are
inherently more complex than centralised ones.
The existing solutions such as Kubernetes, Docker Swarm, and Nomad use a centralised control plane to provide a single
point of entry for the user to interact with the cluster. I'm envisioning a system where all machines are equal, yet
each one can represent the entire cluster and receive commands from the user. Where a user can be a CLI tool or a web
interface that can run on any machine in the network. In case of network partitioning, the user should continue to be
able to interact with each partition separately to manage services running on them. The system should be able to
reconcile the state of the network when the partition is healed. Such an architecture favors Availability and Partition
tolerance (AP) over Consistency and Availability (CA) in the [CAP theorem](https://en.wikipedia.org/wiki/CAP_theorem).
### Shared state
One of the ways to achieve this is to store the entire shared state on every machine and keep them all in sync. A couple
of techniques can be used to assist with this:
a [Conflict-Free Replicated Data Type](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type) (CRDT) and a
peer-to-peer [gossip protocol](https://en.wikipedia.org/wiki/Gossip_protocol).
By composing the state using CRDT data structures allows any machine to modify its own copy of the state independently,
concurrently, and without coordination with other machines. The CRDT automatically resolves conflicts that might arise
from concurrent updates. Although the machines may have different states at any given time, they're guaranteed to
eventually converge to the *same* state. The tradeoff is that the *same* state doesn't always mean the intended one from
each user's point of view.
There is a distributed key-value store implementation in Go ([ipfs/go-ds-crdt](https://github.com/ipfs/go-ds-crdt)) from
the IPFS ecosystem that uses
[Merkle CRDTs](https://research.protocol.ai/publications/merkle-crdts-merkle-dags-meet-crdts/psaras2020.pdf). It's
independent of the permanent storage implementation and the transport layer for broadcasting and receiving updates from
peers.
The gossip protocol can be used to propagate state updates across the network. There is a great project from HashiCorp
called [Serf](https://www.serf.io/) that provides a gossip protocol implementation in addition to cluster membership,
failure detection, and basic orchestration that is decentralized, fault-tolerant and highly available. It's used in
Nomad and Consul internally.
[`experiment/serf_crdt.go`](../experiment/serf_crdt.go) is an experimental distributed key-value store
using [BadgerDB](https://github.com/dgraph-io/badger) as the persistent storage for
[ipfs/go-ds-crdt](https://github.com/ipfs/go-ds-crdt) and Serf as the gossip protocol that uses its custom user events
and queries features. The implementation is very inefficient but the results are promising. A couple of machines are
able to replicate changes to the store across the internet in a fraction of a second and converge their copies if they
get out of sync. Although, the convergence is quite slow at the moment due to the inefficient broadcast and query
implementation.
There is a hypothesis that in practice there shouldn't be much churn and conflicts in the state of relatively small
deployments. The updates should be propagated quickly enough thus the eventually consistent behaviour should be
acceptable.
### Container scheduling
There are two fundamental approaches to design an orchestration system: declarative and imperative. I believe that
Uncloud should use a hybrid approach that favours the imperative over the declarative one where possible.
The imperative approach allows the errors to be handled in a more predictable way. While the declarative approach allows
to decouple components of the system by using a shared state and events for communication.
For example, the user can run a command to start a container on a specific machine. The command can call the target
machine directly to start the container, handle the errors accordingly, and return the result to the user.
Alternatively, the command can update the local state declaring that a container should be started on a specific machine
and wait until it's propagated to the target machine and reconciled. Arguably, the latter approach is more complex, less
predictable, and has more edge cases to handle.
However, asynchronously updating the configuration of DNS servers or reverse proxies in accordance to the state changes
caused by started or stopped containers is likely a more reliable approach than doing it imperatively.
Not sure if we really need this yet, but more complex scenarios like running and watching a replica set of containers
might require a sort of coordination between machines. I believe this can be achieved by using consensus and leader
election algorithms on demand.
Maybe for now we should only aim for a simpler and more static container orchestrator where container scheduling can
only be initiated by a user. Docker on each machine will ensure that the containers are running and restarted on
failures, of course. But they won't be moved to other machines automatically.
## Service discovery
Service discovery using DNS has its own drawbacks but it's likely the simplest way to implement it. Like in Docker and
Docker Swarm, the Uncloud agent on every machine will expose an internal DNS server to the machine itself and the
containers running on it. The DNS server will resolve machine, container, and service names to their respective IP
addresses within the mesh network.
DNS servers will watch running containers through the shared state and update their DNS records accordingly. The
following table is an example list of available DNS names:
| DNS name | Resolves to |
|----------------------------------------------------|----------------------------------------------------------------------------|
| `<machine-name>.machine.internal` | Mesh IP of the machine. Machine names must be unique within the org |
| `<container-name>.<machine-name>.machine.internal` | Mesh IP of the container on the machine |
| `<service-name>.internal` | Mesh IPs of all containers for the service |
| `lb.<service-name>.internal` | Virtual IP / LB IP that balances traffic to all containers for the service |
A proper thinking about the name design is needed. Most likely we need to namespace them by project or environment name
to allow for multiple instances of the same service and avoid conflicts.
Virtual IPs for services using IPVS, ip/nftables, or eBPF can be implemented later if we see fit.
## Ingress
Services can be exposed to the internet by running a reverse proxy on a machine(s) that listens on the public IP
address. A user should be able to control which machine(s) should run a reverse proxy by assigning an appropriate role
to them.
Traefik or Caddy can be used as a reverse proxy that can automatically discover services running on the network using
the internal DNS server. They both support automatic TLS certificate generation and renewal using Let's Encrypt.
In case of network partitioning, DNS servers should adjust their records in accordance with what containers are
available in their partition. Consequently, the reverse proxy should adjust its configuration to route traffic to only
the available containers by resolving the updated DNS records.
+138
View File
@@ -0,0 +1,138 @@
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0" version="26.0.6">
<diagram id="l8hXMBHkgcEJcSW0mbfh" name="Page-1">
<mxGraphModel dx="2522" dy="1435" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="anjOE1Lszn3xuFwhIM3--51" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;curved=1;startArrow=classic;startFill=1;strokeColor=#871619;strokeWidth=2;" edge="1" parent="1" source="anjOE1Lszn3xuFwhIM3--15" target="anjOE1Lszn3xuFwhIM3--47">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--15" value="" style="rounded=1;whiteSpace=wrap;html=1;strokeWidth=1;glass=0;shadow=0;sketch=1;curveFitting=1;jiggle=2;strokeColor=#71717A;" vertex="1" parent="1">
<mxGeometry x="40" y="110" width="200" height="230" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--16" value="&lt;font face=&quot;Indie Flower&quot;&gt;Virtual machine&lt;/font&gt;" style="text;html=1;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=18;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="40" y="73" width="120" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--30" value="" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;curveFitting=1;jiggle=2;strokeWidth=1;strokeColor=#71717A;" vertex="1" parent="1">
<mxGeometry x="540" y="110" width="200" height="270" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--31" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=default;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cdn.worldvectorlogo.com/logos/digitalocean-2.svg;" vertex="1" parent="1">
<mxGeometry x="40" y="40" width="177.34" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--32" value="&lt;font face=&quot;Indie Flower&quot;&gt;Dedicated server&lt;br&gt;&lt;/font&gt;" style="text;html=1;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=18;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="540" y="73" width="170" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--35" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=default;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Logo_Hetzner.svg/512px-Logo_Hetzner.svg.png;" vertex="1" parent="1">
<mxGeometry x="540" y="43.5" width="186.92" height="23" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--36" value="&lt;div&gt;&lt;font face=&quot;Indie Flower&quot; style=&quot;font-size: 22px;&quot;&gt;&lt;b&gt;Office / Homelab&lt;/b&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font face=&quot;Indie Flower&quot;&gt;Mac mini / PC&lt;br&gt;&lt;/font&gt;&lt;/div&gt;" style="text;html=1;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=18;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="500" y="555" width="170" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--37" value="$ &lt;font face=&quot;JetBrains Mono&quot; data-font-src=&quot;https://fonts.googleapis.com/css?family=JetBrains+Mono&quot;&gt;uc CLI&lt;br&gt;&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;curveFitting=1;jiggle=2;strokeWidth=1;fontColor=#FFFFFF;fillColor=#27272A;fillStyle=solid;fontSize=16;" vertex="1" parent="1">
<mxGeometry x="42" y="470" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--50" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;curved=1;startArrow=classic;startFill=1;strokeColor=#871619;strokeWidth=2;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="anjOE1Lszn3xuFwhIM3--47" target="anjOE1Lszn3xuFwhIM3--30">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--47" value="" style="rounded=1;whiteSpace=wrap;html=1;sketch=1;curveFitting=1;jiggle=2;strokeWidth=1;strokeColor=#71717A;" vertex="1" parent="1">
<mxGeometry x="290" y="300" width="200" height="290" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--52" value="" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=default;verticalAlign=top;aspect=fixed;imageAspect=0;image=https://cdn.icon-icons.com/icons2/2699/PNG/512/wireguard_logo_icon_167956.png;" vertex="1" parent="1">
<mxGeometry x="324" y="163" width="148" height="74" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--53" value="&lt;font face=&quot;Indie Flower&quot;&gt;Private network&lt;br&gt;&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=18;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="338" y="230" width="120" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--74" value="https://app.example.com" style="text;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=18;fontStyle=1;html=1;fontFamily=Indie Flower;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DIndie%2BFlower;spacing=2;spacingTop=0;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="295.89" y="80" width="194.23" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--76" value="" style="rounded=1;whiteSpace=wrap;html=1;strokeWidth=1;dashed=1;shadow=0;glass=0;strokeColor=#71717A;" vertex="1" parent="1">
<mxGeometry x="52.11" y="122" width="175.77" height="138" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--78" value="containers" style="shape=image;verticalLabelPosition=middle;labelBackgroundColor=default;verticalAlign=middle;aspect=fixed;imageAspect=0;image=https://static-00.iconduck.com/assets.00/docker-icon-1024x739-rivf80b4.png;labelPosition=right;align=left;fontFamily=Indie Flower;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DIndie%2BFlower;fontSize=16;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="84.99999999999994" y="127.55000000000001" width="34" height="24.55" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--79" value="&lt;font face=&quot;Indie Flower&quot;&gt;app&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=1;fillStyle=hachure;sketch=1;curveFitting=1;jiggle=2;fillColor=#e1d5e7;labelBackgroundColor=none;labelBorderColor=none;textShadow=0;fontSize=14;align=center;verticalAlign=middle;strokeColor=#9673a6;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="74.99999999999994" y="210" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--80" value="&lt;font face=&quot;Indie Flower&quot;&gt;caddy&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=1;fillStyle=hachure;fillColor=#d5e8d4;labelBackgroundColor=none;labelBorderColor=none;textShadow=0;fontSize=14;align=center;verticalAlign=middle;sketch=1;curveFitting=1;jiggle=2;strokeColor=#82b366;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="74.99999999999994" y="170" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--83" value="&lt;font face=&quot;Indie Flower&quot;&gt;uncloud daemon&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=1;fillStyle=cross-hatch;sketch=1;curveFitting=1;jiggle=2;fillColor=#D6D6D6;labelBackgroundColor=none;labelBorderColor=none;textShadow=0;fontSize=14;strokeColor=#27272A;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="328" y="545" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--92" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;sketch=1;curveFitting=1;jiggle=2;exitX=0.407;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;strokeColor=#18181B;" edge="1" parent="1" source="anjOE1Lszn3xuFwhIM3--74" target="anjOE1Lszn3xuFwhIM3--80">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--69" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;edgeStyle=orthogonalEdgeStyle;curved=1;sketch=1;curveFitting=1;jiggle=2;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeColor=#18181B;" edge="1" parent="1" source="anjOE1Lszn3xuFwhIM3--37" target="anjOE1Lszn3xuFwhIM3--83">
<mxGeometry relative="1" as="geometry">
<mxPoint x="150.0000000000001" y="560.03" as="sourcePoint" />
<mxPoint x="316.12" y="606" as="targetPoint" />
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--94" value="&lt;div&gt;Control the cluster and services&lt;/div&gt;&lt;div&gt;through the closest machine&lt;/div&gt;" style="edgeLabel;html=1;align=left;verticalAlign=middle;resizable=0;points=[];fontFamily=Indie Flower;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DIndie%2BFlower;fontSize=16;fontColor=#18181B;" vertex="1" connectable="0" parent="anjOE1Lszn3xuFwhIM3--69">
<mxGeometry x="0.312" y="-2" relative="1" as="geometry">
<mxPoint x="-212" y="8" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--114" value="&lt;div&gt;&lt;font face=&quot;Indie Flower&quot; data-font-src=&quot;https://fonts.googleapis.com/css?family=Indie+Flower&quot;&gt;RPC over SSH&lt;/font&gt;&lt;/div&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];labelBackgroundColor=none;rotation=13;fontSize=14;fontColor=#18181B;" vertex="1" connectable="0" parent="anjOE1Lszn3xuFwhIM3--69">
<mxGeometry x="-0.0843" y="-4" relative="1" as="geometry">
<mxPoint x="-11" y="-20" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--97" value="" style="rounded=1;whiteSpace=wrap;html=1;strokeWidth=1;dashed=1;shadow=0;glass=0;strokeColor=#71717A;" vertex="1" parent="1">
<mxGeometry x="552.69" y="122" width="175.77" height="178" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--98" value="containers" style="shape=image;verticalLabelPosition=middle;labelBackgroundColor=default;verticalAlign=middle;aspect=fixed;imageAspect=0;image=https://static-00.iconduck.com/assets.00/docker-icon-1024x739-rivf80b4.png;labelPosition=right;align=left;fontFamily=Indie Flower;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DIndie%2BFlower;fontSize=16;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="585.5799999999999" y="129.55" width="34" height="24.55" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--99" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.01;entryY=0.422;entryDx=0;entryDy=0;curved=1;startArrow=classic;startFill=1;strokeColor=#871619;strokeWidth=2;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryPerimeter=0;" edge="1" parent="1" source="anjOE1Lszn3xuFwhIM3--15" target="anjOE1Lszn3xuFwhIM3--30">
<mxGeometry relative="1" as="geometry">
<mxPoint x="323.75" y="183" as="sourcePoint" />
<mxPoint x="473.75" y="307" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--102" value="&lt;font face=&quot;Indie Flower&quot;&gt;uncloud daemon&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=1;fillStyle=cross-hatch;sketch=1;curveFitting=1;jiggle=2;fillColor=#D6D6D6;labelBackgroundColor=none;labelBorderColor=none;textShadow=0;fontSize=14;strokeColor=#27272A;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="575" y="330" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--103" value="&lt;font face=&quot;Indie Flower&quot;&gt;uncloud daemon&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=1;fillStyle=cross-hatch;sketch=1;curveFitting=1;jiggle=2;fillColor=#D6D6D6;labelBackgroundColor=none;labelBorderColor=none;textShadow=0;fontSize=14;strokeColor=#27272A;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="75" y="290" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--104" value="&lt;font face=&quot;Indie Flower&quot;&gt;caddy&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=1;fillStyle=hachure;fillColor=#d5e8d4;labelBackgroundColor=none;labelBorderColor=none;textShadow=0;fontSize=14;align=center;verticalAlign=middle;sketch=1;curveFitting=1;jiggle=2;strokeColor=#82b366;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="574.9999999999999" y="170" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--105" value="&lt;font face=&quot;Indie Flower&quot;&gt;app&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=1;fillStyle=hachure;sketch=1;curveFitting=1;jiggle=2;fillColor=#e1d5e7;labelBackgroundColor=none;labelBorderColor=none;textShadow=0;fontSize=14;align=center;verticalAlign=middle;strokeColor=#9673a6;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="575" y="210" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--106" value="&lt;font face=&quot;Indie Flower&quot;&gt;app-db&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=1;fillStyle=hachure;sketch=1;curveFitting=1;jiggle=2;fillColor=#e1d5e7;labelBackgroundColor=none;labelBorderColor=none;textShadow=0;fontSize=14;align=center;verticalAlign=middle;strokeColor=#9673a6;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="575" y="250" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--107" value="" style="rounded=1;whiteSpace=wrap;html=1;strokeWidth=1;dashed=1;shadow=0;glass=0;strokeColor=#71717A;" vertex="1" parent="1">
<mxGeometry x="302.12" y="312" width="175.77" height="218" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--108" value="containers" style="shape=image;verticalLabelPosition=middle;labelBackgroundColor=default;verticalAlign=middle;aspect=fixed;imageAspect=0;image=https://static-00.iconduck.com/assets.00/docker-icon-1024x739-rivf80b4.png;labelPosition=right;align=left;fontFamily=Indie Flower;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DIndie%2BFlower;fontSize=16;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="335.00999999999993" y="319.55" width="34" height="24.55" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--109" value="&lt;font face=&quot;Indie Flower&quot;&gt;caddy&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=1;fillStyle=hachure;fillColor=#d5e8d4;labelBackgroundColor=none;labelBorderColor=none;textShadow=0;fontSize=14;align=center;verticalAlign=middle;sketch=1;curveFitting=1;jiggle=2;strokeColor=#82b366;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="324.4299999999999" y="360" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--110" value="&lt;font face=&quot;Indie Flower&quot;&gt;app-staging&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=1;fillStyle=hachure;sketch=1;curveFitting=1;jiggle=2;fillColor=#ffe6cc;labelBackgroundColor=none;labelBorderColor=none;textShadow=0;fontSize=14;align=center;verticalAlign=middle;strokeColor=#d79b00;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="324.43" y="400" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--111" value="&lt;font face=&quot;Indie Flower&quot;&gt;app-db-staging&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=1;fillStyle=hachure;sketch=1;curveFitting=1;jiggle=2;fillColor=#ffe6cc;labelBackgroundColor=none;labelBorderColor=none;textShadow=0;fontSize=14;align=center;verticalAlign=middle;strokeColor=#d79b00;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="324.43" y="440" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--112" value="&lt;font face=&quot;Indie Flower&quot;&gt;monitoring stack&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=1;fillStyle=hachure;sketch=1;curveFitting=1;jiggle=2;fillColor=#dae8fc;labelBackgroundColor=none;labelBorderColor=none;textShadow=0;fontSize=14;align=center;verticalAlign=middle;strokeColor=#6c8ebf;fontColor=#18181B;" vertex="1" parent="1">
<mxGeometry x="325" y="480" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="anjOE1Lszn3xuFwhIM3--93" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;sketch=1;curveFitting=1;jiggle=2;exitX=0.608;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;strokeColor=#18181B;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="anjOE1Lszn3xuFwhIM3--74" target="anjOE1Lszn3xuFwhIM3--104">
<mxGeometry relative="1" as="geometry">
<mxPoint x="481" y="200" as="sourcePoint" />
<mxPoint x="470" y="170" as="targetPoint" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
+60
View File
@@ -0,0 +1,60 @@
# User Guide
## Initialising a cluster
To begin setting up a new Uncloud cluster, create the desired nodes. Ensure that their firewall allows the required ports.
### Ports
Nodes running Uncloud with a standard configuration must have the following inbound ports allowed in the firewall:
* 51820/udp (WireGuard meshnet)
* 22/tcp (default SSH management port)
* 80/tcp (required for challenge if uncloud.run DNS is enabled, pass --no-dns during init to disable)
In addition, any ports for any workloads you want to expose will need to be open.
### Configuration
Uncloud stores its configuration in `~/.config/uncloud/config.yaml`. If you wish to reinitialise a cluster, simply remove it from this config.
### Initialisation
Begin by initialising the first node in your cluster with `uc machine init [USER@HOST:PORT]`. If you do not have a need for Caddy reverse proxy, you may disable this feature with `--no-caddy`. If you want to avoid using uncloud's managed DNS service, add the `--no-dns` flag.
This command will idempotently install Docker, uncloudd, uncloud-corrosion. If Caddy is enabled, it will set up a reverse proxy. If Uncloud DNS is enabled, it will create a DNS A record for the machine's public IP address under `*.[CLUSTER ID].cluster.uncloud.run`.
If you wish to uninstall Uncloud and its components, run `uncloud-uninstall`.
### Adding a node
Just like the initialisation of the first node, a node can be added to the cluster with `uc machine add`.
### DNS
Uncloud (uncloud.run) DNS can be managed with the `uc dns` subcommand.
* To reserve a domain name, run `uc dns reserve`
* To release a domain name, run `uc dns release`.
* To see the domain name, run `uc dns show`
* To avoid using the Uncloud managed DNS service, use the `--no-dns` flag on your `uc machine init` command.
### Running a service
Services on an Uncloud cluster can be managed with `uc service`.
You can run a service with two replicas that expose port 80 like the following:
```
uc service run -p 80/http --replicas 2 nginxdemos/hello
```
This requires the Caddy reverse proxy to be deployed. If it wasn't previously, it can be deployed with `uc caddy deploy`.
Since this doesn't specify a service name, a random one will be generated.
The service can be deleted by its service name:
```
uc service rm hello-gsdo
```