Docs/ Infrastructure/ Multi-Server

Multi-Server

Attach other servers you own to one sh0 installation and run some of your apps on them. The primary server keeps the dashboard, the builds and the routing.

Note
This page describes the behaviour of sh0 v1.10.2 as implemented in its code. Multi-server deployment requires a Business license.

Requirements

  • A Business license, and the admin or owner role: every node endpoint is refused otherwise.
  • On the remote server: Docker installed and running. sh0 does not install anything there.
  • SSH access from the primary server to the remote one, with a user that can use the Docker socket (/var/run/docker.sock): root, or a member of the docker group.
  • Either an SSH key registered beforehand in the dashboard (SSH key management), or the user's password.

How it works

  • The primary server keeps the database, the dashboard and Caddy. A node only runs containers; sh0 drives its Docker through an SSH tunnel.
  • Images are always built on the primary server, then transferred to the node (the deployment shows the transferring step).
  • Domains still point at the primary server. Its Caddy forwards each request to the container on the node through a relay carried by the same SSH tunnel.
  • Internal names (my-api.internal) resolve only inside one server: an app on a node cannot reach a service of the same project on another server by that name. The build log warns when a project spans several nodes.

Adding a node

  1. In the dashboard, open Nodes, then Add Node.
  2. Give a Name (lowercase letters, digits and hyphens, at most 64 characters) and the Hostname (IP address or DNS name; localhost and loopback addresses are refused).
  3. Set the SSH Port (22 by default), the Username (root by default) and the Authentication: SSH Key or Password.
  4. Save, then use Test Connection: sh0 checks the SSH connection and Docker on the node.

From the CLI (nodes create takes --port for a non-standard SSH port, and --password instead of --ssh-key):

Terminal
sh0 nodes create --name worker-1 --host 203.0.113.20 --ssh-user root --ssh-key <ssh-key-id>
sh0 nodes list
sh0 nodes info <node-id>
sh0 nodes test <node-id>

Through the API:

Terminal
curl -X POST https://panel.example.com/api/v1/nodes \
  -H "Authorization: Bearer $SH0_TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"worker-1","hostname":"203.0.113.20","port":22,"username":"root","auth_method":"ssh_key","ssh_key_id":"<ssh-key-id>"}'
  • port, username and auth_method are optional (22, root, ssh_key). With "auth_method":"password", send password instead of ssh_key_id; it is stored encrypted.
  • Since 1.10.3: sh0 tries to connect as soon as the node is created: the status becomes online on success, error with the message on failure. The attempt is bounded to 15 seconds: on an address that does not answer, the node is recorded in error and the CLI exits with code 1, naming nodes test to retry and nodes delete to remove it.
  • The node's SSH host key is recorded at the first connection and checked afterwards. If the server is reinstalled, its key changes and the connection is refused: Reset the fingerprint (POST /api/v1/nodes/<id>/reset-fingerprint) so that the next connection records the new key.

Deploying to a node

When you create an app from a Git repository, a Dockerfile, a Docker image, a framework or an archive, the Target Node field chooses the server; through the API, send node_id (the node's ID or name). An empty value means the primary server; a value that matches no node is refused rather than silently deployed on the primary.

Warning
Templates and Docker Compose stacks are deployed on the primary server only. Choosing a node for them is refused: “Templates and Compose stacks can only be deployed on the primary server for now.”

Moving an app

An app is moved by changing its node_id, with PATCH /api/v1/apps/<id>. The app must be stopped first; otherwise the change is refused, because its container would be left running on the old server. An empty string brings the app back to the primary server.

Terminal
curl -X PATCH https://panel.example.com/api/v1/apps/<app-id> \
  -H "Authorization: Bearer $SH0_TOKEN" -H "Content-Type: application/json" \
  -d '{"node_id":"worker-1"}'

Then redeploy the app: the image is built on the primary and transferred to the new node. The container on the old node is not migrated, and volume data does not follow: copy it yourself.

Node status

Nodes lists each server with its status (Online, Pending, Error, Offline), its Docker version, CPU and memory, and the last contact. A monitor re-checks the nodes every 30 seconds and re-opens the relays of running apps after a reconnection.

Removing a node

A node can be removed only when no app is assigned to it any more: “Cannot delete node: N app(s) still assigned. Migrate them first.” Move or delete those apps first, then delete the node from Nodes, with sh0 nodes delete <node-id> or DELETE /api/v1/nodes/<id>.

Limitations

  • The primary is a single point of failure. If it stops, the dashboard, the builds and the routing to every node stop with it.
  • All traffic goes through the primary. Requests reach a node over the SSH relay; plan the primary's bandwidth accordingly.
  • No automatic placement. sh0 does not choose a node for you and does not spread replicas across nodes: each app runs on one server.
  • No drain or automatic migration. Emptying a node means stopping, moving and redeploying its apps one by one.
  • No cross-server network. See internal names above.
  • Use the node ID in the CLI. sh0 nodes test, info and delete announce “Node ID or name”, but in v1.10.2 only the ID is found.