Skip to content
SHC Docs

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.


  • Linux — for any node. shc, shcd and shc-x9s are released for linux/amd64 and linux/arm64 only; 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 with X500354FABDEV (“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 status answers there — it reports shc0 absent, 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 syncsystemd-timesyncd, chrony, or ntp running. Cluster raft is sensitive to clock skew > 500ms between nodes (see cluster-join).
  • UDP port 4242 reachable between nodes (Nebula mesh; default in modules/cluster/types.go:NebulaListenPort, ce module). Only relevant for multi-node; single-node installs don’t need it.
  • UDP port 4243 reachable between nodes — the WireGuard fabric’s listener, needed only when the cluster runs --fabric shc. Scope it to the cluster’s own node addresses, never 0.0.0.0/0; the hetzner terraform module does this for you when bootstrap.fabric = "shc".
  • An shc system user and group for production (§2 below).

Pick the install method that matches your use case.

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.

Terminal window
git clone https://github.com/bitspur/selfhosted-cloud.git shc && cd shc
make 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.apk

1b. 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.

Terminal window
git clone https://github.com/bitspur/selfhosted-cloud.git shc && cd shc
make install

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.

Terminal window
git clone https://github.com/bitspur/selfhosted-cloud.git shc && cd shc
make prepare # one-time per machine
make dev # every session — builds and runs bin/shcd

make clean wipes ./.shc/ and leaves Docker Swarm; the next make dev re-bootstraps 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:

Terminal window
shc init

shc 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:

  1. 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 running shc vault to re-unlock. See vault for why we never persist the password.
  2. 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 --nebula or shc cluster mesh enable — not by a plain shc init.)
  3. Docker Swarm init. Overlay networking for cross-node traffic.
  4. 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.
  5. Group membership. If invoked via sudo, init adds the invoking user ($SUDO_USER) to the shc group so they can talk to the daemon socket without sudo. Re-login (or newgrp shc) is required for the new membership to take effect.
  6. Service activation. Init runs the equivalent of systemctl enable --now shc, then POSTs the password to /api/method/shc.vault.unlock so 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):

Terminal window
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 -y

Precedence (first match wins): --vault-password-file--vault-passwordSHC_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.


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:

Terminal window
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 shc

If 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.


Terminal window
shc --version
shc cluster status # should show one HEALTHY node
shc cluster nodes
shc repo list # system repos

Create your first tenant (the default tenant is auto-created, but adding one is a good end-to-end smoke test):

Terminal window
shc tenant create demo
shc install postgres --stack db --tenant demo
shc stack status --tenant demo

Terminal window
make uninstall # keep config + data
make purge # remove everything including /var/lib/shc, /etc/shc

The make purge target will prompt before deleting stateful directories.