Server Environment
A server deployment runs the whole Runtime on a Linux host the tenant owns and is reached through the public Runtime Origin you confirm in the Control Plane. The examples use Ubuntu or Debian packages.
The copyable route on this page is Ubuntu 24.04 with systemd, not a generic Linux installation script. Other distributions require administrator-adapted repositories. After SSH login, run sudo -i and use the same root shell throughout.
Check first:
cat /etc/os-release
uname -m
ps -p 1 -o comm=
Expect Ubuntu 24.04, x86_64 or aarch64, and systemd. Stop and confirm the deployment plan for other results. Root can operate Docker without joining the docker group.
Capacity and Data Volume Planning
| Item | Planning guidance |
|---|---|
| Entry configuration for the complete single-machine stack | 2 vCPU and 4 GiB RAM; add 2 GiB swap on small hosts. Validate business capacity against actual workloads |
| System disk | At least 60 GiB recommended for public dependencies, product images, logs, offline transfer files, and upgrades |
| Tenant data volume | Prefer a separate encrypted data disk for new enterprise deployments, sized for database, attachments, and workflow growth. Small deployments can also use an encrypted file container with sufficient capacity |
| Larger workloads | 8 vCPU, 16 GiB RAM, and a 200 GiB SSD can be an initial planning point; adjust after representative concurrency, document parsing, and storage tests, rather than applying this to every tenant |
| CLI disk check | At least 10 GiB free on the deployment directory's filesystem; this is a free-space check, not the total installation size |
2 GiB RAM is no longer the recommended entry configuration for the current complete stack. The CLI does not enforce a 4 GiB RAM minimum. HTTP 200 from the health endpoint establishes readiness, not workload capacity. Swap does not replace physical RAM.
The complete stack includes document parsing. In one Linux AMD64 deployment, Docling reported about 8 GB and all images about 14 GB. Sizes vary with release, architecture, and shared layers. A 40 GiB system disk with an 8 GiB encrypted file container and 2 GiB swap had only about 11 GiB free after installation; prefer at least 60 GiB for new deployments. Import only the target architecture to avoid unnecessary disk use.
Small Hosts: Check and Create Swap
In the root shell, run free -h, swapon --show, and df -h /. Check existing swap before creating another file. If no swap is active and the system disk has sufficient space, create a new 2 GiB file:
(
set -e
if [ -e /swapfile ]; then
echo '/swapfile already exists; stop and inspect it first'
exit 1
fi
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
if ! grep -qE '^[[:space:]]*/swapfile[[:space:]]' /etc/fstab; then
printf '/swapfile none swap sw 0 0\n' >> /etc/fstab
fi
swapon --show
free -h
)
Expect /swapfile in the active swap list and about 2 GiB total swap. Stop on failure; do not overwrite an existing file. Swap does not replace physical RAM or production capacity planning.
Prerequisites
- Docker Engine, Docker Compose v2, and Docker Buildx
- Node.js 22, to run
npx @soln/cli - Cosign, to verify image signatures
- disk encryption (LUKS) enabled, and
SOLN_DATA_AT_REST_ENCRYPTED=truein the deployment.env - a trusted TLS certificate for the exact Runtime Origin
- outbound HTTPS access to the Control Plane, Soln’s private Registry, and selected public image registries; public images can also be imported as described in the installation guide
- at least 10 GiB free on the deployment directory filesystem; plan total capacity using the table above
- a synchronized system clock
Concept Notes
- Runtime Origin: the "origin" of the deployment address, meaning scheme + host + port, for example
https://runtime.customer.example.com(HTTPS defaults to port 443, so the port is optional). It never contains a path, query string, or#fragment, and it must match the address confirmed in the Control Plane exactly soln/npx @soln/cli: the command-line tool used for installation. The npm package is@soln/cliand the command issoln(aliassl). No global install is needed: runnpx @soln/cli <command>sl deployment prepare: generates the deployment files (docker-compose.yml,.env.example,agent.config.json, and the reverse proxy and tunnel configuration) directly in the deployment directory, so nothing has to be downloaded elsewhere and copied over. See Deployment Files and Directoriessoln doctor: the CLI's own self-check command, run assoln doctor --dir <deployment directory>. It verifies that Docker, Compose, Buildx, and Cosign are available, that file permissions are safe, that required environment values are present, and that the deployment directory has at least 10 GiB free. Installation and upgrade run it first and stop when any check fails
This page checks and installs host tools. On a new server without encrypted storage, prepare the tools first, then choose one storage route in Single-Machine Installation. A missing mount is not permission to guess a device or format the system disk. Encryption and HTTPS checks must pass before Runtime installation.
Step 1: Connect to the Server and Check Encryption
Every command on this page runs on the server, not on your own computer. ssh is the standard way to reach a Linux host from your machine: it opens a remote shell, so whatever you run afterwards runs on the server.
ssh deploy@runtime.example.com
deployis the account you use on that server, andruntime.example.comis its address or IP, both provided by your hosting provider. Replace them with your own values- Success: the prompt becomes something like
deploy@runtime:~$— from here on you are typing on the server - Already connected through a cloud console, a jump host, or a terminal on the server itself? Skip this command and start with the check below
Permission denied (publickey)— wrong user or key; tryssh -i ~/.ssh/id_ed25519 user@hostCould not resolve hostname— the address is wrong or DNS is unavailable- On a Windows computer, use the
sshclient that ships with PowerShell (Windows 10 1809 and newer).ssh : The term 'ssh' is not recognizedmeans the OpenSSH Client optional feature is missing
After connecting, enter the root shell before continuing:
sudo -i
lsblk -f
- A
crypto_LUKSblock device helps identify the encrypted container, but does not prove thatSOLN_DATA_ROOTis mounted on it. File-backed LUKS containers may not appear directly in this list - For a cloud-native encrypted disk, confirm the attached disk identity and encryption status in the cloud console. Linux may show ordinary ext4 on
/dev/vdbor a partition; this does not identify the provider's encryption status - Encryption not enabled yet — choose cloud-native disk encryption or LUKS in Single-Machine Installation. Set
SOLN_DATA_AT_REST_ENCRYPTED=trueonly after verifying encryption and the correct data mount command not found: lsblk— install theutil-linuxpackage, or check withsudo cryptsetup status
Single-machine deployments also need the tenant data directory that .env points at with SOLN_DATA_ROOT (default /srv/soln/data). It must sit on the encrypted volume and be private to the deployment user:
sudo install -d -m 0700 /srv/soln/data
findmnt /srv/soln/data
stat -c '%a %U:%G %n' /srv/soln/data
findmntmust identify the intended data filesystem mounted at/srv/soln/data. For cloud-native encryption, match its device and UUID to the attached cloud disk; for LUKS, check the mapping withsudo cryptsetup status soln-tenant-data, using your actual mapping namels -ld /srv/soln/datashould showdrwx------; correct permissions alone do not prove that the volume is encrypted or mounted- Mounted the encrypted volume elsewhere? Create the directory there and update
SOLN_DATA_ROOTin.envto match soln doctorrejects aSOLN_DATA_ROOTthat is not absolute, that is/, that does not exist, or that is readable by group or other users- External services mode stores state with the tenant's own PostgreSQL, Temporal, and object storage, so this directory is only required in single-machine mode
For unattended recovery on a cloud host, prefer cloud-native encrypted storage, configure its UUID mount in fstab, and give Docker a RequiresMountsFor dependency plus AssertPathIsMountPoint=/srv/soln/data on a dedicated host. The manual LUKS passphrase route still needs unlocking after every reboot, including instance resizing; an external key service requires separate configuration. The CLI does not create disks or configure unlocking. Follow the storage routes and restart verification before completing delivery.
Step 2: Install Docker Engine
2.1 Authorize sudo once
sudo -v
- Success: it asks for your password once, then returns to the prompt with no output
you are not in the sudoers file— use an administrator account or ask your administrator to grantsudo
Authorizing once means the rest of this step does not stop for a password in the middle.
2.2 Add the Docker package repository
sudo apt-get update
- Success: ends with
Reading package lists... Done E: Could not get lock /var/lib/dpkg/lock-frontend— another apt process is running; wait for it, then retryCould not resolve 'archive.ubuntu.com'— network or DNS problem; fix it before continuing
sudo apt-get install -y ca-certificates curl gnupg
- Success: ends with
Setting up ...lines for those packages E: Unable to locate package gnupg— runsudo apt-get updateagain first
sudo install -m 0755 -d /etc/apt/keyrings
- Success: no output; the directory now exists
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
- Success: no output
curl: (7) Failed to connect— cannot reach download.docker.com; check the network or proxygpg: no valid OpenPGP data found— the download failed; fix the network and run the command again
sudo chmod a+r /etc/apt/keyrings/docker.gpg
- Success: no output
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
- Success: no output (
teewrites the file) - On Debian, replace
linux/ubuntuwithlinux/debian
2.3 Install the packages
sudo apt-get update
- Success: ends with
Reading package lists... Doneand includes adownload.docker.comline NO_PUBKEY ...— the GPG key step failed; repeat 2.2
sudo apt-get install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
- Success: ends with
Setting up docker-compose-plugin ... E: Unable to locate package docker-ce— the repository line above is wrong orapt-get updatefaileddocker-buildx-pluginanddocker-compose-plugininstall Buildx and Compose as Docker CLI plugins; the CLI reads both plugin paths fromdocker infoand fails withDOCKER_PLUGIN_PATH_INVALIDwhen they are missing
sudo systemctl enable --now docker
- Success: prints
Created symlink ...for the docker service System has not been booted with systemd— this machine does not use systemd; start Docker with your init system instead
2.4 Confirm the engine answers
systemctl is-active docker
- Success:
active inactiveorfailed— start it withsudo systemctl start dockerand checksystemctl status docker
docker version
- Success:
Client:andServer:sections are both present permission denied while trying to connect to the Docker daemon socket— log out and back in (or runnewgrp docker) so thedockergroup applies
Step 3: Install Node.js 22
3.1 Add the NodeSource repository and install
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
- Success: prints lines starting with
## Installing the NodeSource Node.js 22.x repo...and ends with a hint to runsudo apt-get install -y nodejs curl: (7) Failed to connect— network or proxy problem
sudo apt-get install -y nodejs
- Success: ends with
Setting up nodejs ... E: Unable to locate package nodejs— the repository script above did not run; repeat 3.1
3.2 Confirm
node -v
- Success:
v22.x.y - A different major version — another Node.js install is first on
PATH; checkwhich -a node command not found: node— the install failed; repeat 3.1
Step 4: Install Cosign
4.1 Download the binary
For mainland China servers, use Single-Machine Installation: Install Cosign from a Mirror to install v3.1.2 from GitCode or the Soln GitHub backup. Choose either that route or the official-source route below.
Select a version from the official Cosign release page and record its version and the SHA-256 for the architecture-specific file. Do not guess the checksum. Replace both placeholders with values from that release:
SOLN_COSIGN_VERSION='REPLACE_WITH_RELEASE_TAG'
SOLN_COSIGN_SHA256='REPLACE_WITH_PUBLISHED_SHA256_FOR_THIS_ARCH'
(
set -e
case "$(uname -m)" in
x86_64) SOLN_COSIGN_ARCH=amd64 ;;
aarch64) SOLN_COSIGN_ARCH=arm64 ;;
*) echo 'Unsupported architecture'; exit 1 ;;
esac
test "$SOLN_COSIGN_VERSION" != REPLACE_WITH_RELEASE_TAG
test "$SOLN_COSIGN_SHA256" != REPLACE_WITH_PUBLISHED_SHA256_FOR_THIS_ARCH
SOLN_COSIGN_TMP=$(mktemp -d)
trap 'rm -rf "$SOLN_COSIGN_TMP"' EXIT
curl -fSL "https://github.com/sigstore/cosign/releases/download/$SOLN_COSIGN_VERSION/cosign-linux-$SOLN_COSIGN_ARCH" \
-o "$SOLN_COSIGN_TMP/cosign"
printf '%s %s\n' "$SOLN_COSIGN_SHA256" "$SOLN_COSIGN_TMP/cosign" | sha256sum -c -
install -m 0755 "$SOLN_COSIGN_TMP/cosign" /usr/local/bin/cosign
)
4.3 Confirm
cosign version
- Success: the output contains a
GitVersion:line command not found: cosign—/usr/local/binis not onPATH; run/usr/local/bin/cosign versionto check the binary itself
Verify the download against the checksums published with the Cosign release before installing it on a production host.
Step 5: Verify Every Tool
Each command must succeed before you continue; these are the same checks soln doctor performs.
docker version --format '{{.Server.Version}}'
docker compose version --short
docker buildx version
cosign version
node -v
timedatectl status
| Command | Success | Typical failure |
|---|---|---|
docker version --format '{{.Server.Version}}' | one version such as 27.5.1 | permission denied means the docker group change has not applied |
docker compose version --short | one line such as v2.32.4 | 'compose' is not a docker command means docker-compose-plugin is missing |
docker buildx version | a line starting with github.com/docker/buildx | 'buildx' is not a docker command means docker-buildx-plugin is missing |
cosign version | output containing GitVersion: | command not found means /usr/local/bin is not on PATH |
node -v | v22.x.y | a different version means another Node.js install is first on PATH |
timedatectl status | a line such as System clock synchronized: yes | command not found means the host is not systemd; check NTP another way |
Access tokens, session leases, Registry credentials, and TLS certificates are time-sensitive, so keep the clock synchronized.
Step 6: Prepare Domain and TLS
The Runtime Origin must be an absolute HTTPS Origin without path, query, fragment, username, or password, and identical to the address confirmed in the Control Plane. The Control Plane rejects anything else with INTEGRATION_RUNTIME_ORIGIN_INVALID, and another tenant cannot register the same Origin.
dig +short runtime.customer.example.com
- Success: prints the IP address the Origin resolves to
- No output — the DNS record is missing; create it before installing
curl -fsS -o /dev/null -w '%{http_code}\n' \
https://runtime.customer.example.com/health/ready
- Success: after the Runtime is installed this prints
200 Could not resolve host— DNS is not readySSL certificate problem— the certificate is missing or not trusted; install your certificate first
The reverse proxy inside the deployment binds to loopback, so terminate TLS on the host with your own certificate, or use the optional Cloudflare Named Tunnel described in Single-Machine Installation. Never expose the bundled PostgreSQL, Temporal, MinIO administration, or Temporal UI ports to the public Internet.
Step 7: Prepare Network and Ports
The host needs outbound HTTPS to CP_URL, Soln's private Registry, and Docker Hub (registry-1.docker.io and auth.docker.io). Single-machine deployment also pulls public dependency images such as PostgreSQL, MinIO, nginx, and Temporal. Only the Runtime Origin needs to be reachable by users.
curl -fsS -o /dev/null -w '%{http_code}\n' "$CP_URL"
- Success: prints an HTTP status such as
200or401 Could not resolve hostorConnection timed out— outbound access to the Control Plane is blocked; open it in your firewall or proxy
dig +short registry.example.com
- Success: prints the Registry address
- No output — the Registry host is unresolvable; fix DNS before installing
To check Docker Hub access, keep image download failures separate from Runtime configuration errors:
docker pull --platform linux/amd64 postgres:17-alpine
This command is for an x86_64 host; use linux/arm64 on an ARM64 host. An i/o timeout means the Docker daemon cannot reach Docker Hub. Use the cloud provider's image accelerator for your account or import a same-architecture image from a machine that can reach Docker Hub. Continue to verify Soln release image signatures through the normal CLI flow. A release can also depend on other public Registries; inspect the image list with the release Compose override applied.
| Port | Where | Purpose |
|---|---|---|
8080 | server deployment | reverse proxy listener inside the deployment, kept on loopback behind your TLS proxy or tunnel |
4100 | internal | Runtime Service API, reachable only inside the Compose network |
The CLI obtains short-lived, pull-only Registry credentials during soln install and soln upgrade, then logs out after pulling; manual docker login is not part of the supported path.
Step 8: Plan Disk Space
df -h /opt
- Success: the
Availcolumn is at least10Gi - Less than 10 GiB — installation stops with
DEPLOYMENT_DISK_INSUFFICIENT
soln doctor checks free space on the deployment directory filesystem; it does not calculate a full capacity budget from image sizes. Reserve space for public dependencies, product images, offline archives and import, database backups, and old and new images during upgrades. Check again after allocating swap, creating an encrypted file container, importing images, and before each upgrade.
df -h /opt /srv/soln/data
du -h --max-depth=1 /srv/soln/data
docker system df
Do not add system disk free space to data volume free space. An 8 GiB LUKS file container provides roughly 7.8 GiB after formatting, shared by database, attachments, and workflow data. Enlarging the system disk does not enlarge the container. Existing containers with sufficient capacity can remain in use; plan growth and migration and keep off-host backups. Docker's reclaimable size does not establish that deletion is safe; do not bulk-delete required images or tenant volumes to pass the check.
One caveat: the check measures the deployment directory, while the images live in the Docker data root (by default /var/lib/docker). When that directory is on a separate disk, size that disk as well; the check does not cover it.
Next
Continue with Deployment Files and Directories, then Single-Machine Installation or External Services Installation.