Skip to content

terrariumctl

terrariumctl (or its shortcut alias trm) is the primary command-line tool for managing your Terrarium server. It handles installation, status checks, reconfiguration, proxy syncing, IDP syncing, backup exports, and restores.

Top-Level Commands

CommandArgumentsDefaultsMeaning
terrariumctl installoptional flags, see belowinteractive modeInstalls or bootstraps Terrarium on the current host, including preflight verification for external OIDC and S3 when enabled.
terrariumctl statusnonen/aShows Terrarium service status, management endpoints, IDP mode, admin group, and the oauth2-proxy state.
terrariumctl launchrequired: image and instance name; optional provisioning flagsnormal LXD launch with optional generated cloud-initLaunches an LXD container, optionally applying resource limits, generated Ansible/Docker Compose provisioning, and a Terrarium user.proxy label.
terrariumctl image createrequired: instance and image alias; optional: --snapshot, --live, --reusecreates a temporary snapshot and sanitized imagePublishes a reusable golden LXD image from a container or snapshot without carrying published-route proxy config into the image.
terrariumctl image launchrequired: image alias and new instance name; optional launch flagssame as terrariumctl launchLaunches a new container from a Terrarium golden image.
terrariumctl image list/deleteoptional image alias for deleten/aLists or removes local LXD images.
terrariumctl execrequired: instance name; optional command after --, --root, --userterrarium login shellOpens a shell or runs a command inside a container as the non-root terrarium user by default.
terrariumctl backup listnonen/aLists local ZFS snapshots and, when enabled, S3 manifests.
terrariumctl backup exportnonen/aUploads the current incremental ZFS backup chain to configured S3 storage.
terrariumctl backup restorerequired: --instance; optional: --source, --at, --as-new--source local, latest restore point, in-place restoreRestores an instance either in place by default or as a new instance when --as-new is provided.
terrariumctl reconfigurenonen/aRe-runs the local Ansible reconciliation using the saved config.
terrariumctl updateoptional: --ref, --skip-reconfigurelatest release, reconfigure after updateUpdates installed Terrarium code/assets, refreshes Ansible collections, and re-runs reconciliation using the saved config.
terrariumctl config importnonen/aImports /etc/terrarium/config.yaml into the LXD dqlite-backed config store.
terrariumctl config exportnonen/aRecreates /etc/terrarium/config.yaml from the LXD dqlite-backed config store.
terrariumctl cluster statusnonen/aShows LXD cluster state and the Terrarium OVN workload network.
terrariumctl cluster initoptional: --member, --wireguard-endpoint, --wireguard-cidr, advanced --address, --central-addresses, --peer-cidrmember from hostname, WireGuard endpoint auto-discovered, mesh CIDR 10.255.54.0/24Enables LXD clustering on the first member, creates the WireGuard mesh, and reconciles Terrarium cluster networking.
terrariumctl cluster inviterequired: member name; optional: peer IP/CIDR or --peer-cidrmember name resolution when possibleMints a single-use LXD cluster join token, creates a one-time WireGuard join bundle, temporarily opens the joining peer, and prints the join command.
terrariumctl cluster tokenrequired: member namen/aMints only the single-use LXD cluster join token for another member.
terrariumctl cluster joinrequired: --token; normally also --wireguard; optional: --yesstorage pool terrariumStarts the WireGuard mesh, joins this node to an existing LXD cluster, and exports the shared Terrarium config.
terrariumctl cluster evacuaterequired: member namen/aAsks LXD to evacuate workloads from a member for maintenance.
terrariumctl cluster restorerequired: member namen/aRestores an evacuated member to normal cluster service.
terrariumctl cluster moverequired: workload name, target membern/aMoves one workload to another LXD cluster member without renaming it.
terrariumctl cluster removerequired: member name; optional: --move, --target, --force, --yesprompts before workload moves and removalRemoves a member from the LXD cluster, optionally moving workloads first.
terrariumctl cluster ovn configureoptional: --central-addresses, --peer-cidrdiscovers LXD member addresses and keeps an odd OVN central setUpdates shared OVN and cluster firewall settings, then reconciles the host.
terrariumctl proxy syncnonen/aRebuilds Traefik dynamic config, host-loopback LXD proxy backend devices, and Terrarium-managed UFW rules from LXC user.proxy labels.
terrariumctl mount addrequired: protocol, hostPath, address, username; optional: -p/--password, --password-file, --container, --sealpassword prompt, host uid=0, host gid=0, container-aware uid/gid when --container is set, file_mode=0660, dir_mode=0770, --seal=trueCreates a managed host SMB/CIFS mount, stores credentials under /etc/terrarium/mounts, writes a managed /etc/fstab block, and mounts it immediately.
terrariumctl mount removerequired: hostPathn/aUnmounts a Terrarium-managed host mount, removes its managed /etc/fstab block, and deletes its managed credentials file.
terrariumctl mount listnonen/aLists Terrarium-managed host mounts, including whether each one is currently mounted.
terrariumctl idp syncnonen/aReconciles self-hosted ZITADEL applications, Terrarium management role claims, and related local OIDC settings. No-op unless ZITADEL mode is enabled.
terrariumctl idp statusnonen/aShows the managed terrarium-idp instance and its ZITADEL compose services.
terrariumctl idp logsoptional: --lines120Prints recent ZITADEL compose logs from inside the managed IDP instance.
terrariumctl idp backupnonelocal snapshot; exports to S3 when enabledCreates a manual recursive snapshot of the managed IDP instance.
terrariumctl idp restoreoptional: --source, --at, --as-new--source local, latest restore point, in-place restoreRestores the managed IDP instance through the normal Terrarium backup/restore flow.
terrariumctl set domainsoptional rootDomain, plus override flagsmanage.<rootDomain>, lxd.<rootDomain>, auth.<rootDomain> when applicableUpdates the root domain, derived Terrarium subdomains, re-verifies external OIDC when needed, and re-runs reconciliation.
terrariumctl set emailsoptional flagsexisting values when omittedUpdates Terrarium contact, ACME, and ZITADEL admin emails.
`terrariumctl set idp localoidc`mode plus optional flagsn/a
terrariumctl set s3optional flagskeeps current enable/disable state unless --enable or --disable is passedUpdates S3 backup settings, verifies the target with a real test operation, and can enable or disable S3 exports.
terrariumctl set syncoidoptional flagskeeps current enable/disable state unless --enable or --disable is passedUpdates syncoid replication settings and can enable or disable syncoid.
terrariumctl completionbash, zsh, fish, or all installprint script unless install is usedPrints or installs shell completion. Install/update also refresh completion for detected shells automatically.

launch

trm launch wraps lxc launch and adds Terrarium provisioning shortcuts. Basic LXD-style launches work as expected:

bash
trm launch ubuntu:24.04 web-01
trm launch ubuntu:24.04 web-01 --profile dev
trm launch ubuntu:24.04 web-01 --disk 40G --memory 4G --cpu 2

For a more practical walkthrough with Ansible, Docker Compose, variables, Git assets, and proxy labels, see Templating Containers with trm launch.

Provisioning flags generate cloud-init user-data that installs the needed packages and runs inside the new container:

bash
trm launch ubuntu:24.04 web-01 --playbook ./site.yml
trm launch ubuntu:24.04 web-01 --requirements ./requirements.yml --playbook ./site.yml
trm launch ubuntu:24.04 docker-01 --role geerlingguy.docker
trm launch ubuntu:24.04 app-01 --docker-compose ./docker-compose.yml

--requirements, --playbook, --role, and --docker-compose can each be passed multiple times and can be combined. Local files are embedded into cloud-init. Git assets use this form:

bash
trm launch ubuntu:24.04 app-01 \
  --requirements ./requirements.yml \
  --playbook git+https://github.com/org/repo.git//site.yml?ref=v1.0.0 \
  --docker-compose git+https://github.com/org/repo.git//docker-compose.yml?ref=v1.0.0

Launch variables can be passed inline or through dotenv files:

bash
trm launch ubuntu:24.04 app-01 \
  --vars ./app.env \
  --var APP_VERSION=1.2.3 \
  --playbook ./site.yml \
  --docker-compose ./docker-compose.yml

Variables are exported to generated provisioning commands, passed to Ansible as extra vars, and supplied to Docker Compose with --env-file. Inline --var KEY=value entries override the same key from earlier --vars files.

To provide your own cloud-init completely, use --cloud-init. It cannot be combined with the generated Ansible or Docker Compose shortcuts:

bash
trm launch ubuntu:24.04 raw-01 --cloud-init ./user-data.yml

--proxy validates and sets the Terrarium user.proxy label at launch time. Repeat it to publish multiple routes:

bash
trm launch ubuntu:24.04 app-01 \
  --docker-compose ./docker-compose.yml \
  --proxy https://app.example.com:8080 \
  --proxy https://admin.example.com:3000@auth:admins

image

trm image creates and manages named golden images. Use it when a container is configured exactly how you want and you want to launch more containers from that state later.

Create an image from the current instance state:

bash
trm image create web-01 golden-web

By default, Terrarium creates a temporary LXD snapshot, copies it to a temporary instance, removes user.proxy and LXD proxy devices from that copy, publishes the image, and then removes the temporary resources. This keeps the image from accidentally inheriting the source container's public route.

Create an image from an existing snapshot:

bash
trm image create web-01 golden-web --snapshot known-good

Use --live when you explicitly want to publish the current instance state without creating a temporary snapshot first:

bash
trm image create web-01 golden-web --live

If the alias already exists and you intentionally want to replace it, add --reuse:

bash
trm image create web-01 golden-web --snapshot known-good --reuse

Launch from a golden image:

bash
trm image launch golden-web web-02 --profile dev

image launch accepts the same basic launch flags as trm launch: --profile, --disk, --memory, --cpu, and --proxy.

List or remove images:

bash
trm image list
trm image delete golden-web

install

FlagArgumentRequiredDefaultMeaning
--non-interactivenonenointeractive mode if omittedDisables prompts and requires all needed config through flags.
--yesnonenoprompt before destructive actionsAuto-confirms destructive or confirmation prompts.
--refgit branch or tagnomain when invoking terrariumctl directly; release-selected tag when run through install.shInstalls a specific Terrarium release tag, or builds from a branch-like source ref such as main.
--emailemail addressyes in non-interactive mode; no in interactive modeprompted in interactive modeSets the Terrarium contact/admin email and default ZITADEL bootstrap admin email.
--acme-emailemail addressnofalls back to --emailSets the ACME account identity for Traefik and LXD certificate automation.
--domainroot domainnoservice domains default to <service>.<dashed-public-ip>.traefik.me when omittedSets the root domain used to derive service subdomains.
--manage-domaindomainnomanage.<domain> when --domain is set, otherwise manage.<dashed-public-ip>.traefik.meOverrides the Cockpit domain.
--proxy-domaindomainnoproxy.<domain> when --domain is set, otherwise proxy.<dashed-public-ip>.traefik.meOverrides the Traefik dashboard domain.
--lxd-domaindomainnolxd.<domain> when --domain is set, otherwise lxd.<dashed-public-ip>.traefik.meOverrides the LXD domain.
--idplocal or oidcyes in non-interactive mode; no in interactive modeprompted in interactive modeSelects whether Terrarium uses self-hosted ZITADEL or an external OIDC issuer.
--admin-groupgroup nameyes when --idp=oidc; no otherwiseterrarium-admins when --idp=local, otherwise prompted in interactive modeSets the management admin group that is allowed into Cockpit and LXD.
--oidcissuer URLyes when --idp=oidc; no otherwisederived from https://<auth-domain> when --idp=localSets the OIDC issuer URL.
--oidc-clientclient IDyes when --idp=oidc; no otherwisenoneSets the external OIDC client ID used by Cockpit's oauth2-proxy, LXD, and published-route auth.
--oidc-secretclient secretyes when --idp=oidc and --oidc-secret-file is omitted; no otherwisenoneSets the external OIDC client secret used by Cockpit's oauth2-proxy, LXD, and published-route auth. Prefer --oidc-secret-file for automation.
--oidc-secret-filepathyes when --idp=oidc and --oidc-secret is omitted; no otherwisenoneReads the external OIDC client secret from a root-readable file.
--lxd-oidc-clientclient IDnofalls back to --oidc-clientUses a separate external OIDC client for LXD.
--lxd-oidc-secretclient secretnofalls back to --oidc-secretSets the separate LXD OIDC client secret. Prefer --lxd-oidc-secret-file for automation.
--lxd-oidc-secret-filepathnononeReads the separate LXD OIDC client secret from a root-readable file.
--auth-domaindomainnoauth.<domain> when --domain is set and self-hosted ZITADEL is enabled, otherwise auth.<dashed-public-ip>.traefik.meOverrides the ZITADEL auth domain.
--zitadel-admin-emailemail addressnofalls back to --emailSets the initial admin email for self-hosted ZITADEL.
--generate-root-pwdnoneyes in non-interactive mode when root has no usable local password unless --root-pwd-file is passed; no otherwisenoneGenerates a strong Cockpit root password and saves it to /etc/terrarium/secrets/cockpit_root_password with root-only permissions.
--root-pwd-filepathyes in non-interactive mode when root has no usable local password unless --generate-root-pwd is passed; no otherwisenoneReads the Cockpit root password from a local file.
--storage-modedisk, partition, or fileyes in non-interactive mode; no in interactive modeprompted or auto-selected in interactive modeSelects how the LXD ZFS pool is created.
--storage-sourcepath or autoyes for disk and partition in non-interactive installs; no in interactive modeprompted when needed in interactive modeSets the source disk or partition for disk or partition mode, or uses auto to pick the largest valid target.
--storage-sizesize stringonly for file mode when overriding the default64G in interactive prompts and non-interactive fallbackSets the size of the file-backed ZFS pool for file mode.
--enable-s3nonenodisabledEnables S3-backed archive exports.
--s3-endpointURLonly when using a custom S3-compatible providerhttps://s3.amazonaws.com when omittedSets a custom S3-compatible API endpoint.
--s3-bucketbucket nameyes if --enable-s3 is setnoneSets the destination bucket for S3 exports.
--s3-regionregion namenous-east-1 when omittedSets the S3 region.
--s3-prefixprefixnoterrariumSets the object prefix under the bucket.
--s3-access-keyaccess keyyes if --enable-s3 is setnoneSets the S3 access key.
--s3-secret-keysecret keyyes if --enable-s3 is set and --s3-secret-key-file is omittednoneSets the S3 secret key. Prefer --s3-secret-key-file for automation.
--s3-secret-key-filepathyes if --enable-s3 is set and --s3-secret-key is omittednoneReads the S3 secret key from a root-readable file.
--enable-syncoidnonenodisabledEnables syncoid replication to a second ZFS host.
--syncoid-targethostyes if --enable-syncoid is setnoneSets the remote SSH target for syncoid replication.
--syncoid-target-datasetdatasetyes if --enable-syncoid is setbackup/terrarium in interactive promptsSets the remote target dataset for syncoid replication.
--syncoid-ssh-keypathyes if --enable-syncoid is set/root/.ssh/id_ed25519 in interactive promptsSets the SSH key used for syncoid replication.

Install verification notes:

  • Interactive password and secret prompts are masked.
  • For automation, use --generate-root-pwd or --root-pwd-file for Cockpit, and prefer --oidc-secret-file, --lxd-oidc-secret-file, and --s3-secret-key-file over argv secrets.
  • In interactive mode, external OIDC is not accepted until Terrarium can reach the issuer, confirm the callback flow looks valid, and probe the client credentials.
  • In interactive mode, S3 is not accepted until Terrarium can reach the bucket and complete a write/delete verification object cycle.
  • In non-interactive mode, the same checks run once and the install exits on failure.

config import/export

Terrarium keeps its canonical day-2 config in LXD's dqlite-backed project metadata:

  • project: terrarium-system
  • key: user.terrarium.config_b64
  • value: base64-encoded YAML

/etc/terrarium/config.yaml is a root-only YAML export, not the steady-state store. Terrarium creates it only as a transient Ansible input or when you explicitly run terrariumctl config export.

Use terrariumctl config import to copy a local export into the dqlite-backed store. Terrarium runs this automatically after LXD has been initialized during install and removes the transient export afterwards.

Use terrariumctl config export to recreate the local export from the dqlite-backed store for recovery or debugging. terrariumctl reconfigure uses a temporary export before invoking Ansible and removes it afterwards.

update

Use terrariumctl update on an existing Terrarium host when you want newer Terrarium code, Ansible roles, managed LXD profiles, systemd units, or package lists without going through the installer again.

bash
terrariumctl update
terrariumctl update --ref 0.0.21
terrariumctl update --skip-reconfigure

The command updates /opt/terrarium, refreshes Ansible collections, and then runs terrariumctl reconfigure with OS hardening skipped. It reuses the saved configuration from LXD's config store or the legacy /etc/terrarium/config.yaml fallback; it does not ask storage, domain, IDP, S3, or syncoid setup questions.

When using the bootstrap installer, pass --update to get the same behavior from a release bundle:

bash
curl -fsSL https://github.com/terion-name/terrarium/releases/latest/download/install.sh | sudo bash -s -- --update

proxy labels

Terrarium publishes container services from the LXD user.proxy config key. The value is one route or a comma/newline-separated list of routes.

bash
lxc config set my-app user.proxy "https://app.example.com:8080"
lxc config set my-app user.proxy "https://app.example.com:8080,https://api.example.com:3000/api"
terrariumctl proxy sync

HTTP(S) route format:

text
https://<public-host>[:container-port][/path][@auth[:group[,group...]][~callback-host]]
http://<public-host>[:container-port][/path][@auth[:group[,group...]][~callback-host]]

Use https:// for normal public routes. It creates a HTTPS router with Let's Encrypt and redirects plain HTTP to HTTPS. Use http:// only when you intentionally want a plain HTTP public route. The port is the port inside the container; the public listener is still 80/443. If the port is omitted, Terrarium targets container port 80. A path, when present, is matched as a prefix. Query strings and fragments are not supported.

Wildcard HTTPS hosts such as https://*.example.com:8080 require DNS-01 ACME. Configure the single Traefik DNS provider with lego environment variable names:

bash
terrariumctl set dns provider cloudflare CF_DNS_API_TOKEN:your-token
terrariumctl set dns provider route53 AWS_ACCESS_KEY_ID:your-key AWS_SECRET_ACCESS_KEY:your-secret AWS_REGION:us-east-1
terrariumctl set dns provider

The last command disables DNS-01 and returns Traefik to HTTP-01. Traefik supports one DNS challenge provider per instance; see the lego DNS provider list for provider codes and required environment variables.

Authentication suffixes are supported only on HTTP(S) routes:

bash
lxc config set grafana user.proxy "https://grafana.example.com:3000@auth"
lxc config set admin-tool user.proxy "https://admin.example.com:8080@auth:admins,devops"
lxc config set wildcard-admin user.proxy "https://*.example.com:8080@auth:admins~auth.example.com"
terrariumctl proxy sync

@auth allows any authenticated user. @auth:admins,devops allows users in any listed group. Group names may contain letters, numbers, dots, underscores, and hyphens. Wildcard auth routes must add ~callback-host; Terrarium always uses HTTPS and oauth2-proxy's callback path for that host. External OIDC providers must emit a groups claim for group-restricted routes.

TCP and UDP route formats:

text
tcp://<public-port>:<container-port>
udp://<public-port>:<container-port>

Examples:

bash
lxc config set postgres user.proxy "tcp://15432:5432"
lxc config set game user.proxy "udp://25565:25565"
terrariumctl proxy sync

TCP/UDP routes are raw transport routes. They do not support @auth, hostnames, paths, or TLS termination. Terrarium creates a Traefik entrypoint and opens the matching managed UFW port.

Operational rules:

  • The container must have an IPv4 address on the Terrarium network, or a managed host-loopback proxy backend.
  • The service inside the container must listen on 0.0.0.0, not only 127.0.0.1.
  • Run terrariumctl proxy sync after changing labels, or wait for the automatic sync timer.
  • Duplicate HTTP host/path claims and duplicate TCP/UDP public ports are rejected during sync.

exec

terrariumctl exec is Terrarium's safer wrapper around lxc exec.

By default it opens a login shell as the terrarium user inside the container:

bash
trm exec my-stack

To run a command, put the container command after -- so flags are passed to the container command instead of Terrarium:

bash
trm exec my-stack -- bash -lc 'docker compose ps'

For recovery or system administration, use root explicitly:

bash
trm exec my-stack --root
trm exec my-stack --root -- systemctl status ssh

You can also choose another container user:

bash
trm exec my-stack --user ubuntu

cluster

Terrarium cluster commands wrap LXD's native clustering flow. They do not create a separate Terrarium consensus layer.

cluster status

bash
terrariumctl cluster status

Prints lxc cluster list and, when present, the Terrarium OVN workload network definition.

cluster init

bash
terrariumctl cluster init

Manual override example:

bash
terrariumctl cluster init \
  --member node1 \
  --wireguard-endpoint 10.0.0.11:51820 \
  --wireguard-cidr 10.255.54.0/24
FlagArgumentRequiredDefaultMeaning
--membernamenohostname -sLocal LXD cluster member name.
--addressIP/DNS with optional portnolocal WireGuard tunnel IPAdvanced escape hatch for the LXD listener address. Normal clusters should omit this.
--networkLXD network namenoterrarium-ovnTerrarium OVN workload network.
--parentLXD network namenolxdbr0Managed parent/uplink network for OVN.
--central-addressescomma-separated IPsnolocal WireGuard tunnel IPAdvanced OVN central member addresses. Use tunnel IPs and an odd-sized set for production.
--peer-cidrcomma-separated IPs/CIDRsnoexact local WireGuard tunnel addressAdvanced source addresses allowed through UFW for LXD and OVN cluster traffic. Normal clusters should let Terrarium maintain exact tunnel peers.
--wireguard-endpointIP/DNS with optional portnoauto-discovered host address, port 51820Public or provider-private endpoint other members use for WireGuard handshakes.
--wireguard-cidrIPv4 CIDRno10.255.54.0/24Tunnel subnet for Terrarium cluster members.
--wireguard-portUDP portno51820WireGuard listen port.
--skip-reconfigurenonenoreconfigure after cluster changesSaves cluster config without reconciling the host.

cluster invite

bash
terrariumctl cluster invite node2

Explicit peer example:

bash
terrariumctl cluster invite node2 10.0.0.12

Prints a copy-paste join command:

bash
terrariumctl cluster join --token '<token-from-existing-member>' --wireguard '<join-bundle>'

When node2 resolves to an IP address, Terrarium pre-opens exact WireGuard endpoint firewall rules for that joining node and stores the generated WireGuard peer in the shared config. If the name is not resolvable, Terrarium asks for the joining node address in interactive terminals. For automation, pass the joining node explicitly:

bash
terrariumctl cluster invite node2 10.0.0.12

Invite peer rules are temporary until the LXD token expires. Terrarium schedules a one-shot systemd cleanup; joined peers are kept, and expired never-joined WireGuard peers are removed from UFW and the shared config.

Use this for normal operations. Use cluster token only when another tool will consume the raw token.

cluster token

bash
terrariumctl cluster token node2

Prints the single-use token returned by lxc cluster add node2.

cluster join

bash
terrariumctl cluster join --token '<token-from-existing-member>' --wireguard '<join-bundle>'

Manual override example:

bash
terrariumctl cluster join \
  --token '<token-from-existing-member>' \
  --wireguard '<join-bundle>' \
  --yes
FlagArgumentRequiredDefaultMeaning
--tokentokenyesnoneSingle-use token created by terrariumctl cluster invite or terrariumctl cluster token.
--wireguardbundleyes for normal Terrarium clustersnoneOpaque join bundle printed by terrariumctl cluster invite; contains this node's temporary WireGuard join secret.
--addressIP/DNS with optional portnoWireGuard tunnel IP from the bundleAdvanced LXD listener override.
--storage-poolpool namenoterrariumMember-local storage pool name used in the LXD join preseed.
--peer-cidrcomma-separated IPs/CIDRsnoexact existing WireGuard peer from the bundleAdvanced LXD/OVN tunnel peers allowed through UFW before the LXD join runs.
--yesnonenoprompt before joinConfirms the destructive LXD cluster join operation.
--skip-exportnonenoexport shared config after joinLeaves /etc/terrarium/config.yaml untouched after joining.
--skip-reconfigurenonenoreconfigure after joinJoins without running local Terrarium reconciliation.

cluster evacuate

bash
terrariumctl cluster evacuate node2

Asks LXD to evacuate workloads from node2 for planned maintenance.

cluster restore

bash
terrariumctl cluster restore node2

Restores an evacuated member to normal service. This does not guarantee that workloads evacuated earlier move back automatically.

cluster move

bash
terrariumctl cluster move app1 node2

Moves workload app1 to cluster member node2 without renaming the workload. This follows normal LXD move behavior; stop the workload first if your storage or runtime cannot migrate it while running.

cluster remove

bash
terrariumctl cluster remove node2

If node2 still has workloads, Terrarium asks whether to move them first. In automation:

bash
terrariumctl cluster remove node2 --move --yes

When --target is omitted, Terrarium creates a best-effort distribution plan across the remaining online members. It prefers members with fewer existing workloads, and uses lower memory pressure as a tie-breaker when LXD reports resource data. Add --target node1 when you intentionally want every workload to land on one member.

Running workloads are stopped before the move and started again on the target member. Use application-level failover or LXD evacuation policy when a workload needs stricter availability behavior.

For dead members:

bash
terrariumctl cluster remove node2 --force

Force removal updates cluster metadata only. Workloads that only existed on the dead member's local storage must be recovered from backups or replicated/shared storage.

FlagArgumentRequiredDefaultMeaning
--movenonenoprompt when workloads existMoves workloads off the member before removing it.
--targetmember namenobest-effort distribution across remaining online membersTarget member for all moved workloads when you do not want automatic distribution.
--forcenonenoclean member removalForce-removes an unreachable member from LXD metadata.
--yesnonenoprompt before movement/removalConfirms prompts for automation.
--skip-reconfigurenonenoreconfigure after cluster config changesSaves shared cluster config without reconciling the host.

cluster ovn configure

bash
terrariumctl cluster ovn configure

Updates the shared Terrarium config and reconciles:

  • OVN central service membership
  • Open vSwitch southbound connection
  • LXD OVN northbound connection
  • Terrarium-managed OVN CA and per-node TLS certificates
  • terrarium-ovn workload network
  • UFW rules for peer-only cluster traffic

Without flags, Terrarium reads LXD cluster membership, uses an odd number of online member addresses as OVN central addresses, and adds exact member CIDRs to the shared peer firewall list. If a larger manually configured central set already exists, Terrarium keeps it instead of shrinking it because one member is temporarily unreachable.

Manual override example:

bash
terrariumctl cluster ovn configure \
  --central-addresses 10.255.54.1,10.255.54.2,10.255.54.3 \
  --peer-cidr 10.255.54.1/32,10.255.54.2/32,10.255.54.3/32

Use an odd number of central addresses and prefer WireGuard tunnel IPs. If there is no LXD cluster membership yet and --central-addresses is omitted, Terrarium keeps the existing local OVN setting.

This command reconciles the node where it runs. If the central set changes after other members have already joined, run terrariumctl reconfigure on those members so their local OVN services consume the shared config.

OVN database remotes are rendered as ssl: endpoints and require certificates issued by the Terrarium OVN CA. The peer firewall rules remain necessary, but they are no longer the only control protecting 6641/tcp and 6642/tcp.

backup restore

FlagArgumentRequiredDefaultMeaning
--sourcelocal or s3nolocalChooses whether restore data comes from local ZFS snapshots or from S3 manifests and streams.
--instanceinstance nameyesnoneNames the source instance to restore from.
--atsnapshot fragment or timestampnolatest local snapshot or latest S3 manifest chainSelects the restore point to match.
--as-newnew instance namenoin-place restoreRestores into a new dataset and then hands off into interactive lxd recover.

Restore behavior:

  • terrariumctl backup restore --instance NAME restores from the latest local snapshot in place by default after confirmation.
  • terrariumctl backup restore --source local|s3 --instance NAME [--at ...] --as-new NEWNAME restores the chosen point and then launches interactive lxd recover.

mount add

FlagArgumentRequiredDefaultMeaning
positional protocolsmb or cifsyesnoneChooses the SMB/CIFS mount handler. Both values map to a managed CIFS mount.
positional hostPathabsolute host pathyesnoneThe mount point to create on the Terrarium host.
positional addressshare addressyesnoneThe SMB share address, //server/share. Hetzner Storage Box main accounts usually use //u12345.your-storagebox.de/backup; subaccounts usually use //u12345-sub1.your-storagebox.de/u12345-sub1.
positional usernameusernameyesnoneThe SMB/CIFS username written to the managed credentials file.
-p, --passwordpasswordnoprompt if omittedThe SMB/CIFS password. Omit it to let Terrarium prompt securely instead of putting it in shell history.
--password-filepathnononeReads the SMB/CIFS password from a root-readable file for non-interactive runs without putting it in shell history or process args.
--containercontainer namenononeAlso attach the mounted share to this LXD container using Terrarium's unprivileged-container-safe disk device settings.
--container-pathabsolute pathno/mnt/<mount-name>Path where --container attaches the share inside the container.
--devicedevice namenogeneratedLXD disk device name used by --container or mount attach.
--uiduidno0Advanced: UID presented for files on the host-side mount.
--gidgidno0Advanced: GID presented for files on the host-side mount.
--file-modeoctal modeno0660File permissions presented on the mounted share.
--dir-modeoctal modeno0770Directory permissions presented on the mounted share.
--sealtrue or falsenotrueEnables or disables the SMB encryption option explicitly.

Example:

bash
terrariumctl mount add cifs /srv/shared/storage-box //u12345.your-storagebox.de/backup u12345

For a Hetzner Storage Box subaccount:

bash
terrariumctl mount add cifs /srv/shared/storage-box //u12345-sub1.your-storagebox.de/u12345-sub1 u12345-sub1

Attach the share to a container in the same step:

bash
terrariumctl mount add cifs /srv/shared/storage-box //u12345.your-storagebox.de/backup u12345 --container app --container-path /mnt/shared

Behavior:

  • Terrarium creates the mount point if it does not exist.
  • Terrarium writes credentials under /etc/terrarium/mounts/.
  • Terrarium adds or updates a Terrarium-managed block in /etc/fstab.
  • If --container is set, Terrarium keeps the LXD container unprivileged, maps the host-side CIFS ownership for that container, and attaches the share as a disk device.
  • If the path is already mounted, Terrarium remounts it cleanly.

mount attach

Attach an existing Terrarium-managed host mount to an LXD container:

bash
terrariumctl mount attach /srv/shared/storage-box app /mnt/shared

For Terrarium-managed CIFS mounts, mount attach remaps the host-side mount for the target unprivileged container before attaching the disk device.

mount remove

FlagArgumentRequiredDefaultMeaning
positional hostPathabsolute host pathyesnoneThe Terrarium-managed mount point to remove from the host.

Example:

bash
terrariumctl mount remove /srv/shared/storage-box

Behavior:

  • Terrarium unmounts the path if it is currently mounted.
  • Terrarium removes the managed block from /etc/fstab.
  • Terrarium deletes the managed credentials file for that mount.

mount list

Example:

bash
terrariumctl mount list

Behavior:

  • Shows all Terrarium-managed mounts discovered in /etc/fstab.
  • Reports the share address, host path, protocol, and whether the path is mounted right now.

set domains

FlagArgumentRequiredDefaultMeaning
positional rootDomaindomainnoprompted when omittedSets the new root domain.
--manage-domaindomainnomanage.<rootDomain>Overrides the Cockpit domain.
--proxy-domaindomainnoproxy.<rootDomain>Overrides the Traefik dashboard domain.
--lxd-domaindomainnolxd.<rootDomain>Overrides the LXD domain.
--auth-domaindomainnoauth.<rootDomain> when self-hosted ZITADEL is enabledOverrides the ZITADEL domain.

set emails

FlagArgumentRequiredDefaultMeaning
--emailemail addressnoexisting configured valueUpdates the Terrarium contact/admin email.
--acme-emailemail addressnoexisting configured value or falls back to --emailUpdates the ACME account email.
--zitadel-admin-emailemail addressnoexisting configured value or falls back to --emailUpdates the self-hosted ZITADEL bootstrap admin email.

set idp

FlagArgumentRequiredDefaultMeaning
positional modelocal or oidcyesnoneSwitches the Terrarium IDP mode.
--auth-domaindomainnoderived from the current root domain or IP when mode is localOverrides the self-hosted ZITADEL auth domain.
--admin-groupgroup namerequired when mode is oidc; optional otherwiseexisting configured value, or terrarium-admins when mode is localSets the management admin group for Cockpit and LXD authorization.
--oidcissuer URLrequired when mode is oidc and no issuer is already configuredexisting configured issuer, or derived from auth-domain when mode is localSets the OIDC issuer URL.
--oidc-clientclient IDrequired when mode is oidc and no client ID is already configuredexisting configured valueSets the external OIDC client ID shared by Cockpit's oauth2-proxy, LXD, and published-route auth.
--oidc-secretclient secretrequired when mode is oidc and no client secret is already configured and --oidc-secret-file is omittedexisting configured valueSets the external OIDC client secret shared by Cockpit's oauth2-proxy, LXD, and published-route auth. Prefer --oidc-secret-file for automation.
--oidc-secret-filepathrequired when mode is oidc, no client secret is already configured, and --oidc-secret is omittednoneReads the external OIDC client secret from a root-readable file.
--lxd-oidc-clientclient IDnofalls back to --oidc-clientUses a separate external OIDC client for LXD.
--lxd-oidc-secretclient secretnofalls back to --oidc-secretSets the separate LXD OIDC client secret. Prefer --lxd-oidc-secret-file for automation.
--lxd-oidc-secret-filepathnononeReads the separate LXD OIDC client secret from a root-readable file.
--zitadel-admin-emailemail addressnoexisting configured value or --emailUpdates the ZITADEL bootstrap admin email when mode is local.

External OIDC notes:

  • Terrarium configures both management oauth2-proxy hosts and LXD from the same external issuer and client settings.
  • The external client must allow:
    • https://<manage-domain>/oauth2/callback
    • https://<proxy-domain>/oauth2/callback
    • https://<lxd-domain>/oidc/callback
  • Published-route auth with @auth also requires the external client to allow each route callback. A root route is rendered as https://<route-host>/oauth2/callback; a path route like /admin is rendered as https://<route-host>/oauth2/admin/callback.
  • The external provider must emit a groups claim that contains the configured admin group as a JSON string array. For ZITADEL Cloud, a project role assignment is not enough by itself; add a Complement Token Action that copies granted role keys into groups. See Domains and auth for details
  • terrariumctl set idp oidc ... verifies the issuer, callback flow, and client credentials before persisting the new settings.
  • If your provider needs separate OIDC clients for Cockpit/published routes and LXD, pass --lxd-oidc-client plus --lxd-oidc-secret-file.

Local ZITADEL notes:

  • Local ZITADEL runs in the terrarium-idp LXD system instance, so its data is part of the LXD/ZFS backup set instead of host Docker state.
  • Terrarium auto-provisions a management role named after terrarium_admin_group, defaulting to terrarium-admins.
  • The bootstrap admin is granted that role automatically.
  • Terrarium also installs a small ZITADEL Action that flattens Terrarium role assignments into a groups claim for oauth2-proxy and LXD.

set s3

FlagArgumentRequiredDefaultMeaning
--enablenonenokeeps current stateEnables S3 exports.
--disablenonenokeeps current stateDisables S3 exports.
--s3-endpointURLnoexisting configured valueUpdates the S3 endpoint.
--s3-bucketbucket namerequired when S3 is enabledexisting configured valueUpdates the S3 bucket.
--s3-regionregion namenoexisting configured valueUpdates the S3 region.
--s3-prefixprefixnoexisting configured value or terrariumUpdates the S3 object prefix.
--s3-access-keyaccess keyrequired when S3 is enabledexisting configured valueUpdates the S3 access key.
--s3-secret-keysecret keyrequired when S3 is enabled and no existing secret is configured and --s3-secret-key-file is omittedexisting configured valueUpdates the S3 secret key. Prefer --s3-secret-key-file for automation.
--s3-secret-key-filepathrequired when S3 is enabled, no existing secret is configured, and --s3-secret-key is omittednoneReads the S3 secret key from a root-readable file.

S3 verification notes:

  • When S3 is enabled or updated, Terrarium verifies the target bucket with a real write/delete probe.
  • This catches wrong endpoint, wrong credentials, wrong bucket, and missing write permissions before backup settings are persisted.

set syncoid

FlagArgumentRequiredDefaultMeaning
--enablenonenokeeps current stateEnables syncoid replication.
--disablenonenokeeps current stateDisables syncoid replication.
--syncoid-targethostrequired when syncoid is enabledexisting configured valueUpdates the remote syncoid SSH target.
--syncoid-target-datasetdatasetrequired when syncoid is enabledexisting configured valueUpdates the remote syncoid dataset.
--syncoid-ssh-keypathnoexisting configured value or /root/.ssh/id_ed25519Updates the SSH key used by syncoid.

Built with VitePress