Docs/ Infrastructure/ Multi-Server

Multi-Server

Manage and deploy across multiple servers from a single sh0 dashboard using secure SSH tunnels.

Overview

sh0 supports a Bring Your Own Server (BYOS) model where you can connect multiple remote servers (nodes) to your primary sh0 instance. The primary server acts as the control plane, managing deployments and routing across all connected nodes.

This architecture lets you:

  • Distribute workloads across multiple servers for better performance
  • Place apps closer to users in different geographic regions
  • Isolate workloads (e.g., databases on dedicated hardware)
  • Scale beyond the resources of a single server
Multi-server architecture diagram showing primary node and remote nodes
Note
Remote nodes only need Docker installed and SSH access. sh0 handles everything else -- you do not need to install the sh0 binary on remote nodes.

Adding Remote Nodes

To add a remote server as a node:

  1. Navigate to Settings → Nodes in the dashboard.
  2. Click Add Node.
  3. Enter the node details: hostname or IP, SSH port, and SSH user.
  4. Provide the SSH private key or select an existing key from your key manager.
  5. Click Connect. sh0 will verify the connection and register the node.
Add node form with hostname, SSH port, and authentication fields

Via the API:

Terminal
curl -X POST https://your-server:9000/api/nodes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "eu-node-1",
    "hostname": "10.0.1.50",
    "ssh_port": 22,
    "ssh_user": "deploy",
    "ssh_key_id": "key_abc123"
  }'
Warning
The SSH user must have permission to run Docker commands on the remote node. Either add the user to the docker group or configure passwordless sudo for Docker.

SSH Tunnel Setup

sh0 communicates with remote nodes via SSH tunnels. The tunnel forwards Docker API requests from the primary server to the remote node's Docker socket, keeping all traffic encrypted.

The tunnel is established automatically when you add a node. sh0 uses SSH TOFU (Trust On First Use) for host key verification -- you will be shown the server's fingerprint on first connection and asked to confirm it.

SSH Tunnel Architecture
Primary Server (sh0)
  |
  |-- SSH Tunnel (encrypted) --> Remote Node 1
  |                                 |-- Docker Socket
  |                                 |-- Container A
  |                                 |-- Container B
  |
  |-- SSH Tunnel (encrypted) --> Remote Node 2
                                    |-- Docker Socket
                                    |-- Container C
SSH tunnel status showing connected nodes and latency
Tip
sh0 automatically reconnects tunnels if they drop. The reconnection happens within seconds, and running containers on the remote node are unaffected -- only management operations are briefly unavailable.

Node Health Monitoring

sh0 continuously monitors the health of all connected nodes. The monitoring includes:

  • SSH connectivity: Checks that the tunnel is active and responsive.
  • Docker status: Verifies the Docker daemon is running on the remote node.
  • Resource usage: Tracks CPU, memory, and disk usage on each node.
  • Container health: Monitors all containers running on the node.
Node health dashboard showing CPU, memory, disk, and container status for each node

When a node goes unhealthy, sh0 sends alerts through your configured notification channels (email, Slack, Discord, or webhooks). You can configure alert thresholds per node in the node settings.

Deploying to Specific Nodes

When creating or configuring an app, you can specify which node it should run on. This is configured in the app's deployment settings.

Node selection dropdown in the app deployment settings

Options:

  • Primary (default): Deploy on the main sh0 server.
  • Specific node: Pin the app to a named remote node.
  • Any node: Let sh0 choose the node with the most available resources.
Terminal
curl -X PUT https://your-server:9000/api/apps/my-app \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"node_id": "node_eu1"}'
Note
When deploying to a remote node, sh0 transfers the built Docker image from the primary server to the remote node via the SSH tunnel. This happens automatically -- no container registry is required.

Node-Aware Routing

The primary server's Caddy reverse proxy handles routing to containers on all nodes. When a request comes in for a domain, Caddy routes it to the correct node and container, regardless of where the container is running.

For apps with replicas spread across nodes, Caddy load-balances across all healthy replicas on all nodes. Latency between the primary proxy and remote nodes should be considered when planning your architecture.

Tip
For the lowest latency, place your reverse proxy (primary sh0 server) in the same region as the majority of your users, and use remote nodes for background processing or regional deployments.

Managing Multiple Servers

The Nodes panel in the dashboard provides a centralized view of all servers in your fleet:

Nodes management panel showing all connected servers with their status and resource usage

From this panel you can:

  • View node metrics: CPU, memory, disk, and network usage for each node.
  • List containers: See all containers running on a specific node.
  • Drain a node: Gracefully move all containers off a node before maintenance.
  • Remove a node: Disconnect a remote node (containers must be migrated or stopped first).
  • Terminal access: Open an SSH terminal to any node directly from the dashboard.
Danger
Removing a node that still has running containers will make those containers unreachable. Always drain a node before removing it to ensure all apps are migrated to another node.