Installation
This page covers installing SHC on a single node. For multi-node clusters, come back after finishing here and follow Multi-node cluster setup.
Requirements
Section titled “Requirements”- Linux — for any node.
shc,shcdandshc-x9sare released forlinux/amd64andlinux/arm64only; there is no published macOS build of anything. The daemon is Linux-only by design, not by omission: the WireGuard fabric’s data plane is kernel WireGuard over netlink plus nftables, so an applier constructed off Linux refuses withX500354FABDEV(“the wireguard fabric is linux-only”). macOS is a client platform: the CLI cross-builds from source and can drive a remote estate (shc cluster fabric statusanswers there — it reportsshc0absent, which is the true answer for a Mac). Running a node on macOS means running Linux in a VM, which is a Linux node. - Go 1.23+ — SHC is written in Go; the build toolchain requires Go 1.23 or later.
- Docker — either
docker-ce(default) or Podman configured in Docker-compat mode. - Clock sync —
systemd-timesyncd,chrony, orntprunning. Cluster raft is sensitive to clock skew > 500ms between nodes (see cluster-join). - UDP port
4242reachable between nodes (Nebula mesh; default inmodules/cluster/types.go:NebulaListenPort, ce module). Only relevant for multi-node; single-node installs don’t need it. - UDP port
4243reachable between nodes — the WireGuard fabric’s listener, needed only when the cluster runs--fabric shc. Scope it to the cluster’s own node addresses, never0.0.0.0/0; the hetzner terraform module does this for you whenbootstrap.fabric = "shc". - An
shcsystem user and group for production (§2 below).
1. Install the code
Section titled “1. Install the code”Pick the install method that matches your use case.
1a. Native package (recommended for production)
Section titled “1a. Native package (recommended for production)”Builds .deb / .rpm / .apk via goreleaser. All formats include the
systemd unit, sysusers.d + tmpfiles.d snippets, and the bundled
rqlited raft engine — files land in the canonical FHS locations.
git clone https://github.com/bitspur/selfhosted-cloud.git shc && cd shcmake package# Debian/Ubuntu:sudo dpkg -i dist/shc_*_linux_amd64.deb# Fedora/RHEL:sudo rpm -i dist/shc_*_linux_amd64.rpm# Alpine:sudo apk add --allow-untrusted dist/shc_*_linux_amd64.apk1b. make install (source install on Linux)
Section titled “1b. make install (source install on Linux)”Installs the binaries, systemd unit, sysusers.d / tmpfiles.d snippets, and apps catalog directly into the system. Useful for dev hosts that shouldn’t go through a package.
git clone https://github.com/bitspur/selfhosted-cloud.git shc && cd shcmake install1c. Development mode
Section titled “1c. Development mode”For hacking on SHC itself. make prepare installs the system
toolchain (Go, golangci-lint, docker, shfmt, bats, …); make dev
builds bin/shcd and runs it against ./.shc/, auto-bootstrapping a
single-node dev cluster (CA, node certificate, Docker Swarm) on first
invocation.
git clone https://github.com/bitspur/selfhosted-cloud.git shc && cd shcmake prepare # one-time per machinemake dev # every session — builds and runs bin/shcdmake clean wipes ./.shc/ and leaves Docker Swarm; the next
make dev re-bootstraps the cluster.
2. Initialize the cluster
Section titled “2. Initialize the cluster”The .deb ships the systemd unit but does not auto-enable or
auto-start it — the daemon will not run until node.id and the
vault password exist, and auto-starting before that just causes a
crashloop until systemd gives up. shc init is the bootstrap step:
shc initshc init writes /var/lib/shc, sets up the container runtime, and enables
the system service, so it needs root — it auto-elevates via sudo when run
as a normal user (so you type plain shc init, not sudo shc init), and the
elevation still sets $SUDO_USER for the group-membership step below.
What runs as part of init:
- Vault password prompt. Init asks for a master password
(
Set vault password:+Confirm vault password:). The password is held in memory only — it is not written to disk, so every daemon restart requires runningshc vaultto re-unlock. See vault for why we never persist the password. - Cluster CA + node certificate. The internal cluster CA and this
node’s certificate for mTLS cluster-internal traffic. (The separate
Nebula mesh CA + mesh cert are minted only when you bring the overlay
up —
shc init --nebulaorshc cluster mesh enable— not by a plainshc init.) - Docker Swarm init. Overlay networking for cross-node traffic.
- Database bootstrap. The control-plane database is created as
a plain SQLite file; when a later node joins via
shc node join <token>, it is promoted into the raft engine automatically. - Group membership. If invoked via
sudo, init adds the invoking user ($SUDO_USER) to theshcgroup so they can talk to the daemon socket withoutsudo. Re-login (ornewgrp shc) is required for the new membership to take effect. - Service activation. Init runs the equivalent of
systemctl enable --now shc, then POSTs the password to/api/method/shc.vault.unlockso the daemon comes up with the vault already unlocked. System apps (postgres, keycloak, openobserve, traefik) then deploy automatically.
For non-interactive flows (cloud-init, ansible, CI):
shc init --vault-password "$VAULT_PASSWORD"# or, read from stdin:echo "$VAULT_PASSWORD" | shc init --vault-password-file -# or, read from a file:shc init --vault-password-file /etc/shc/vault.pass# or via env (this form keeps `sudo` so the variable survives the# privilege elevation — a plain `shc init` would re-exec via sudo and# drop it):sudo SHC_VAULT_PASSWORD="$VAULT_PASSWORD" shc init -yPrecedence (first match wins): --vault-password-file → --vault-password
→ SHC_VAULT_PASSWORD env → interactive prompt.
Re-running shc init on an already-initialized cluster is a no-op.
To force a clean re-init, see
cluster recovery and the
shc cluster repair command.
3. After every reboot: unlock the vault
Section titled “3. After every reboot: unlock the vault”The vault password is not persisted — every fresh boot of the
shc service starts with the vault locked, and the daemon will block
system-app reconciliation at wait_for_vault() until you unlock it:
shc vault # interactive prompt# or:shc vault -p "$VAULT_PASSWORD"# or via env (e.g. in a systemd drop-in):SHC_VAULT_PASSWORD=... systemctl restart shcIf you’d rather have systemd auto-unlock at boot, drop an
EnvironmentFile= line in a systemd override referencing a
root-owned 0600 file containing SHC_VAULT_PASSWORD=.... That’s
a deliberate trade-off — the secret on disk is now protected only
by file perms, not by the vault primitive itself.
4. Verify
Section titled “4. Verify”shc --versionshc cluster status # should show one HEALTHY nodeshc cluster nodesshc repo list # system reposCreate your first tenant (the default tenant is auto-created, but adding one is a good end-to-end smoke test):
shc tenant create demoshc install postgres --stack db --tenant demoshc stack status --tenant demo5. Remove
Section titled “5. Remove”make uninstall # keep config + datamake purge # remove everything including /var/lib/shc, /etc/shcThe make purge target will prompt before deleting stateful
directories.
Next steps
Section titled “Next steps”- Quick start — install your first real app
- Core concepts — tenant / environment / stack / deployment
- Multi-node cluster setup — add more nodes
- File locations — where SHC puts things
- Mesh architecture — how nodes find each other