Single-host installation (Docker Compose)
Availability: the container images for this method are published with the 1.0 release. Until then, install on Ubuntu with the native bundle (no Docker needed).
The yarma-single-host-X.Y.Z.tar.gz release bundle (source:
deployments/single-host/) runs all of YARMA on one Linux
server: PostgreSQL 17, Redis, NATS JetStream, SeaweedFS (S3), ClamAV, coturn, the API, the
console and Caddy for public TLS. The quick start is the short version of
this page.
Prerequisites
- Linux x86_64 or arm64 with Docker Engine 24+ and the Compose plugin, plus
openssl. - See sizing and network requirements.
- A DNS name for the console. Agents can use the same name or a separate one
(
--agents-host), e.g. when TCP 18443 is reached through a different address. - The license issuer's public key,
license.pub(licensing).
Install
sudo tar -C /opt -xzf yarma-single-host-X.Y.Z.tar.gz
cd /opt/yarma-single-host-X.Y.Z
sudo ./init.sh --domain yarma.example.com --license-pub /path/to/license.pub \
--acme-email [email protected]
sudo docker compose up -d
init.sh refuses to run twice. Its options:
| Option | Default | Meaning |
|---|---|---|
--domain HOST |
required | public name of the console |
--version X.Y.Z |
the bundle's version | image tag to run |
--agents-host HOST |
--domain |
name in the agent-service certificate; agents must connect with this name |
--license-pub FILE |
none | license issuer public key; without it no license can be installed and enrollment stays blocked |
--acme-email EMAIL |
none | Let's Encrypt contact |
--tls-cert FILE --tls-key FILE |
Let's Encrypt | use your own certificate (PEM, full chain) |
--tls-internal |
off | self-signed console certificate; for labs only |
--turn-ip IP |
none | public IP to advertise when the server is behind NAT |
--https-port, --http-port, --agent-port, --turn-port |
443, 80, 18443, 3478 | published ports |
--registry REG |
ghcr.io/javadmohebbi |
image registry, e.g. an internal mirror |
--subnet CIDR |
172.30.80.0/24 |
internal Docker network; change it if it overlaps your LAN |
Let's Encrypt needs TCP 80 and 443 reachable from the internet. For an internal-only server,
use --tls-cert/--tls-key with a certificate from your internal CA.
What init.sh creates
| Path | Contents |
|---|---|
.env |
Compose settings: version, registry, ports, database and Redis passwords |
config/config.yaml |
API configuration (reference) |
config/Caddyfile |
public TLS and reverse proxy |
secrets/api.env |
API secrets: database, Redis, cookie key, TURN secret, file-encryption master key, S3 keys |
secrets/keys/signing.key |
ECDSA P-256 key that signs access tokens |
secrets/pki/ |
agent CA and agent-service certificate (agent-ca.crt goes to devices) |
secrets/internal-ca/, secrets/api/, secrets/postgres/ |
private CA and certificates for TLS between the containers |
secrets/license/license.pub |
license issuer public key |
secrets/s3/, secrets/turn/ |
object-store credentials, coturn configuration |
Files are owned by the user IDs that read them inside the containers and are not readable by
other host users. Back up secrets/ and .env: losing the file master key makes stored
files and recordings unreadable, and losing the agent CA means re-enrolling every device.
Security properties
- Only Caddy (80/443), the agent service (18443) and coturn are published. PostgreSQL, Redis, NATS, the object store, ClamAV and the API's HTTPS port stay on the internal network.
- The API runs read-only, as a non-root user, with no Linux capabilities. It requires TLS,
and PostgreSQL connections use
verify-fullagainst the internal CA. - The console verifies the API's certificate. The API trusts
X-Forwarded-Foronly from the internal subnet. - Agents authenticate with per-device certificates from the agent CA (mutual TLS) and trust only that CA, not public roots.
- coturn refuses to relay to private, loopback and link-local addresses.
Create the first organization
sudo docker compose exec -e YARMA_BOOTSTRAP_ADMIN_PASSWORD='<password>' api \
/yarma-api -config-dir /etc/yarma bootstrap -tenant acme -name "Acme Corp" -admin-email [email protected]
Without YARMA_BOOTSTRAP_ADMIN_PASSWORD, a password is generated and printed once. For
OIDC sign-in from the start, add -issuer https://idp.example.com/realms/x -client-id yarma -client-secret-ref env:YARMA_IDP_SECRET (and put YARMA_IDP_SECRET=… in
secrets/api.env). Continue with first-time setup.
Operations
| Task | Command (in the bundle directory) |
|---|---|
| Status | docker compose ps |
| Logs | docker compose logs -f api (JSON lines; also web, caddy, postgres, …) |
| Readiness | curl https://yarma.example.com/api/readyz |
| Metrics | Prometheus format on port 19091 of the api container; attach Prometheus to the yarma-server_yarma network to scrape it |
| Restart | docker compose restart api |
| Stop / start | docker compose stop / docker compose up -d |
| Audit chain check | docker compose exec api /yarma-api -config-dir /etc/yarma verify |
Configuration
Edit config/config.yaml, then docker compose up -d --force-recreate api. Any key can
also be set as an environment variable in secrets/api.env: files.max_bytes becomes
YARMA_FILES_MAX_BYTES. Common settings:
| Key | Purpose |
|---|---|
files.max_bytes, files.recording_retention_days, files.require_scan |
file transfer limit (default 100 MiB), recording retention (90 days), refuse files when ClamAV is down (on) |
notify.smtp_addr, notify.smtp_from, notify.smtp_username (+ YARMA_NOTIFY_SMTP_PASSWORD) |
email notifications |
pam.* |
credential vaults (privileged access) |
auth.block_private_directories |
stop tenant-configured identity providers from reaching private addresses (set it for MSP or multi-customer servers) |
license.center_url |
License Center for online activation |
license.server_token (YARMA_LICENSE_SERVER_TOKEN), license.report_interval |
only on the License Center operator's own cloud servers: report health and usage and receive provisioning jobs (default every 5m) |
agents.public_url, agents.downloads_dir |
address devices connect to (default https://<console host>:18443); agent packages offered for download |
turn.urls |
TURN URLs handed to browsers and agents; add turns: if you terminate TLS for TURN elsewhere |
telemetry.mode, telemetry.url |
anonymous usage statistics: ask (default, an administrator decides), on, off (details) |
log.level |
debug, info, warn, error |
The full list of keys, with defaults and validation, is in
backend/internal/config/config.go. In
production the API refuses to start without TLS, a signing key, a cookie key, a TURN secret,
a file master key or with database TLS disabled.
TURN behind NAT
If the server has a private address and the public IP is NATed to it, run
init.sh --turn-ip <public IP>. On an existing install, add external-ip=<public IP> to
secrets/turn/turnserver.conf with root, then docker compose restart coturn. Every
concurrent session can use relay ports; widen min-port/max-port there (and in the
firewall) for more than about 20 simultaneous relayed sessions.
Upgrade
- Read the release notes and run
backup.sh(backup). - Set the new version in
.env(YARMA_VERSION=X.Y.Z). sudo docker compose pull && sudo docker compose up -d
Database migrations run automatically (the migrate service) before the API starts; a
failed migration leaves the old API stopped and is shown by docker compose logs migrate.
Agents keep running during the upgrade and reconnect when the API is back. To roll back,
restore the database backup taken before the upgrade and set the previous version.
Backup and restore
backup.sh writes the database dump, the object store (files, recordings) and .env,
config/ and secrets/ into a time-stamped directory with a SHA256SUMS file:
cd /opt/yarma-single-host-X.Y.Z
sudo [email protected] RETENTION_DAYS=30 ./backup.sh /var/backups/yarma
secrets.tgz holds every key of the installation: always set BACKUP_GPG_RECIPIENT (or
encrypt the copies yourself) and keep the backups off the server. Schedule it daily, e.g.
/etc/cron.d/yarma-backup:
30 2 * * * root cd /opt/yarma-single-host-X.Y.Z && [email protected] ./backup.sh /var/backups/yarma >/dev/null
To restore (same server after a loss, or a new server), unpack the bundle of the same
version and run restore.sh instead of init.sh:
sudo tar -C /opt -xzf yarma-single-host-X.Y.Z.tar.gz && cd /opt/yarma-single-host-X.Y.Z
sudo ./restore.sh /var/backups/yarma/yarma-<stamp> # gpg must be able to decrypt
It checks the checksums, restores secrets and configuration (unless .env already exists),
the database into an empty PostgreSQL and the object store, starts everything and runs
verify, which fails if the schema is not current or an audit hash chain is broken. It
refuses to overwrite a database that already has data. Keep the same DNS names: enrolled
agents then reconnect with their existing certificates. See also
backup and DR.
Uninstall
cd /opt/yarma-single-host-X.Y.Z
sudo docker compose down # stop and remove containers; data volumes are kept
sudo docker compose down -v # also delete all data (database, files, recordings)
sudo rm -r /opt/yarma-single-host-X.Y.Z # configuration and keys
Uninstall the agents first if the server is going away for good (agents), otherwise they keep retrying the connection.
Documentation for Yarma 0.1.0-alpha.4. Something unclear? .