Install¶
Prepare the host first, build second. The order below is the order things depend on each other in; the daemon will start with several of these missing and degrade quietly, which is why they come first.
Everything here assumes you have been through Requirements.
1. Storage¶
Substitute your pool name if it is not zroot, and remember to set zfs_root in satld.toml in step 6 if you do.
satld creates the raft, images, layers, containers and volumes children itself on first start.
2. IP forwarding¶
Container traffic is routed between the bridge and the egress interface, so the host has to forward it.
sysrc gateway_enable=YES # persistent, applies at next boot
sysctl net.inet.ip.forwarding=1 # applies now
Skipping this produces the single most misleading symptom in SatL: inbound published ports answer and containers cannot reach anything.
satld checks the sysctl at startup and warns, so the answer is in the log — but the shape of the failure looks like a container problem, not a host one.
3. pf anchors¶
SatL owns the satl/* anchors and never writes a rule outside them.
Declare them once — translation anchors before any filter rule:
# SatL owns the satl/* anchors and never writes a rule outside them.
# Translation anchors must be declared before any filter rule.
nat-anchor "satl/*"
rdr-anchor "satl/*"
anchor "satl/*"
# A host with no firewall policy of its own adds exactly one line after them.
pass all
An anchor that is not declared in pf.conf is never evaluated.
satld will happily load rules into it, report success, and change nothing about how packets move.
4. Boot-time tunables¶
# kern.racct.enable is a boot-time tunable: it cannot be switched on at
# runtime, and without it `--memory` and `--cpus` are accepted and never
# enforced. sysrc(8) refuses dotted names, so append the line directly.
echo 'kern.racct.enable=1' >> /boot/loader.conf
# Optional, and only if you will use overlay networks: if_vxlan is not in the
# GENERIC kernel. satld runs `kldload -n if_vxlan` itself before it creates the
# first tunnel, so this only moves the failure from the first overlay network
# to boot time -- which is where you want to find it.
echo 'if_vxlan_load="YES"' >> /boot/loader.conf
Reboot when convenient. See Requirements for what you lose until you do.
5. Install SatL¶
Download the package and add it:
fetch https://satl.cc/download/satl-freebsd.pkg
pkg add ./satl-freebsd.pkg # pulls ocijail if a pkg repository is configured
Two steps rather than one so you can see the file land before anything installs — and so you can check it, keep it, or copy it to the other nodes of a cluster instead of downloading it three times.
The package needs no repository of its own, and its post-install message recalls the host prerequisites you have just done (steps 1–4 of this page). It installs the same four files a source build does, listed below.
One package, every node
A cluster wants the same build everywhere.
fetch once, scp the file around, pkg add on each machine — mixing versions across nodes is not a configuration SatL is tested in.
From source instead, if you would rather build it — this is also how you get an unreleased fix, and it is the only path that needs a Rust toolchain:
git clone https://github.com/fredericalix/satl satl
cd satl
make install # builds in release mode, then installs
make package # or: build the package yourself, into dist/satl-<version>.pkg
make install needs root (it
writes under /usr/local), builds into target/install deliberately — so that
a build under sudo cannot leave root-owned artifacts in the tree your
unprivileged builds use — and installs four things:
| Path | What |
|---|---|
/usr/local/bin/satl |
the CLI |
/usr/local/sbin/satld |
the daemon |
/usr/local/etc/rc.d/satld |
the rc.d service |
/usr/local/etc/satl/satld.toml.sample |
a commented sample config |
Note the last line carefully.
6. Write satld.toml — do not skip this¶
make install ships a sample, not a config
It installs satld.toml.sample.
It does not create satld.toml.
A missing config file is perfectly legal — the daemon runs on built-in defaults — and the built-in default for pf_mode is check.
In check mode satld generates its pf rules and syntax-checks them, and never loads one.
So on a stock install, a published port is allocated, recorded, and shown by satl ps exactly as if it worked:
and no redirect exists. Nothing is logged as an error, because nothing failed. This catches essentially every first install.
Write the file:
# Every key is optional and a missing file means all defaults. These are the
# only two an ordinary first install has any reason to set.
# Load the satl/* pf anchors. The built-in default is "check", which generates
# the rules and syntax-checks them without ever loading one -- so published
# ports are allocated, shown by `satl ps`, and never redirected.
pf_mode = "enforce"
# Only if your pool is not named `zroot`. The dataset must already exist.
#zfs_root = "tank/satl"
pf_mode = "enforce" needs pf enabled (step 3) and the anchors declared.
The third mode, disabled, generates and logs the rules and never invokes pfctl at all — for hosts with no pf.
Every other key is optional.
The commented sample lists them, and the satld.toml reference documents all thirteen, including two the sample does not mention.
The ones you are most likely to need on a real host:
zfs_root— if your pool is notzroot.egress_if— on a multi-homed host, when containers must leave through a specific interface. Left unset,satldtakes the interface of the default route.advertise_addr— thehost:portpeers are told to dial. Only matters once you cluster, and it matters a lot then: unset, a node advertises whatever the default route leaves by, which on a cloud VM is usually its public interface.network_name— if you will run twosatldinstances on one host. They must differ, or each one's startup reconciliation destroys the other's interfaces.
Unknown keys are rejected at startup, so a typo fails loudly rather than being ignored.
7. Enable and start¶
The rc.d script runs satld under daemon(8) with --log-target syslog.
Three optional rc.conf knobs:
| Variable | Default | Use |
|---|---|---|
satld_config |
/usr/local/etc/satl/satld.toml |
point at another config |
satld_flags |
empty | extra flags, e.g. --log-format json |
satld_env |
empty | environment, e.g. RUST_LOG=satld=debug |
If you edit the rc.d script, keep --log-target syslog
It is a correctness requirement, not a preference.
With it, satld hands each log event to syslogd itself as its own datagram, so one event is one line.
Without it, daemon(8) forwards the output in chunks and syslogd rewrites the newlines inside a chunk as spaces — measured on FreeBSD 15.1, that merged 3.9 % of lines and a synthetic burst lost more than half its records outright.
Two timestamps on one line is this bug.
8. Read the startup lines¶
The daemon's log is the only place its output lands, under the tag satld, in
/var/log/messages and /var/log/daemon.log.
Always grep -a
One non-ASCII byte anywhere in /var/log/messages — from any program on the host — makes grep treat the whole file as binary and print nothing, with exit status 1 and no explanation.
That looks exactly like "the daemon logged nothing", which is the worst possible way to be misled.
A healthy first start, on this machine, verbatim:
INFO satld: starting satld version="0.1.0" git_commit="unknown"
config_file=/usr/local/etc/satl/satld.toml config_source="file"
socket_path=/var/run/satl.sock state_dir=/var/db/satl zfs_root=zroot/satl
node_name=alpha.fredalix.com socket_group=wheel pf_mode="enforce"
listen_addr=0.0.0.0:2377 ca_listen_addr=0.0.0.0:2378
advertise_addr="(from the default route)"
INFO satl_storage::preflight: storage preflight complete root_dataset="zroot/satl" root_mountpoint=/var/db/satl
INFO satld: host information gathered hostname=alpha.fredalix.com ncpu=12 physmem_bytes=68258983936 os_release=15.1-RELEASE-p2
INFO satld::node: SatL devfs ruleset ready ruleset=5000 outcome=AlreadyCurrent
INFO satld::node: linuxulator available; linux/* images may be selected osrelease=5.15.0
INFO satld::node: kern.racct.enable=1; rctl(8) resource limits are enforced
INFO satld::node: egress interface taken from the default route (set egress_if to override) egress_if=ice0
INFO satl_net::pf: loaded pf anchor anchor=satl/nat rules=nat on ice0 inet from 10.88.0.0/24 to any -> (ice0)
INFO satld::node: node-local network ready network=satl bridge=satl0 subnet=10.88.0.0/24 gateway=10.88.0.1 pf_mode="enforce"
INFO satld::cluster: node identity loaded from disk node_id=1oihjf6ers1k3v6ow4lxiy5bd role="satl-manager" cluster_id=2ojl5schqxehkvo5femr07j2v
INFO satl_cluster::server: internal gRPC server listening addr=0.0.0.0:2377
INFO satld::cluster: cluster state ready node_id=1oihjf6ers1k3v6ow4lxiy5bd advertise_addr="51.38.30.173:2377" joined=false is_leader=true term=1
INFO satld::cluster: NodeCA bootstrap endpoint listening addr=0.0.0.0:2378
INFO satl_api::server: docker api listening on unix socket socket=/var/run/satl.sock
That block is the whole preflight. Read it as a checklist — every degradation you can still fix appears here, once, and nowhere else:
| Line | Meaning |
|---|---|
pf_mode="enforce" on the banner |
step 6 took effect. "check" means published ports will not work. |
linuxulator available |
linux/* images can be selected. The other arm names kldload linux as the fix. |
kern.racct.enable=1 … enforced |
limits are real. The other arm is the ACCEPTED BUT NOT ENFORCED warning. |
egress interface taken from the default route |
NAT will exist. A warning here means containers get no outbound connectivity. |
loaded pf anchor anchor=satl/nat |
rules are actually being loaded, not just checked. |
cluster state ready … is_leader=true |
this node is a working cluster of one. |
docker api listening on unix socket |
the socket is up; satl will answer. |
cannot measure this node's underlay — only matters for overlays
On a host whose egress interface carries a /32 — common on cloud VMs — you
will see this at ERROR level:
ERROR satld::overlay: cannot measure this node's underlay; no overlay network can be
programmed until this is fixed ... 51.38.30.173/32 (on ice0) is too small to
derive a blackhole default remote from ... Set overlay_blackhole in satld.toml
to an address on this underlay that nothing answers on
if_vxlan demands a default remote for unknown traffic, and SatL insists it be an address that is not a real peer — a real one silently masks a missing forwarding-table entry, which is exactly how an overlay bug survives a two-node test.
Nothing else is affected: containers, published ports and bridge networks all work.
Set overlay_blackhole when you start using overlay networks.
9. Verify¶
$ satl version
Client:
Version: 0.1.0
API version: 1.43
Server:
Engine:
Version: 0.1.0
API version: 1.43 (minimum version 1.24)
OS/Arch: freebsd/amd64
Kernel Version: 15.1-RELEASE-p2
$ satl node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
1oihjf6ers1k3v6ow4lxiy5bd * alpha.fredalix.com Ready Active Leader 0.1.0
satl node ls answering at all is the interesting part: you never ran swarm init.
A fresh satld initialises a one-member cluster on first boot, so the node is Ready, Active and Leader from the first start.
If you have the Docker CLI, it works too:
Next: Your first container.