Deployment¶
systemd¶
The repository ships trench.service. It binds the privileged ports with
CAP_NET_BIND_SERVICE rather than running as root, and applies a restrictive
sandbox (ProtectSystem=strict, PrivateDevices, RestrictAddressFamilies,
NoNewPrivileges).
sudo useradd --system --no-create-home --shell /usr/sbin/nologin trench
sudo mkdir -p /etc/trench
sudo cp trench.example.yaml /etc/trench/trench.yaml
# The console's Settings page saves by atomically replacing this file, so the
# service account needs the directory, not just the file. 0750: the config can
# hold TSIG secrets.
sudo chown -R trench:trench /etc/trench
sudo chmod 0750 /etc/trench
sudo cp trench.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now trench
journalctl -u trench -f
State lives in /var/lib/trench (StateDirectory=), which is one of only
two paths the unit can write to. The unit's working directory is that same
path, so the example config's relative data_dir: ./data lands in
/var/lib/trench/data; an absolute data_dir must be under
/var/lib/trench or the unit cannot write it.
Check the sandbox after editing the unit: systemd-analyze security trench.
Freeing port 53¶
Most distributions have something on port 53 already:
sudo ss -ulpn 'sport = :53' # find out what
sudo systemctl disable --now systemd-resolved
If you disable systemd-resolved, replace /etc/resolv.conf with a real one
(nameserver 127.0.0.1) or the machine loses name resolution for itself.
Docker Compose¶
cp trench.example.yaml trench.yaml
docker compose up -d
docker compose logs -f
The image carries its own healthcheck, which sends a real query over loopback
and requires a well-formed reply. It queries a name under .invalid, so an
upstream outage cannot turn into a restart loop — it reports on whether
Trench is answering, not on whether the internet is up.
Raspberry Pi¶
Use deploy/docker-compose.raspi.yml, which adds what a small board needs:
docker compose -f deploy/docker-compose.raspi.yml up -d
The important differences are memory bounds:
mem_limit: 700m
memswap_limit: 700m # equal to mem_limit: no swap for this container
Without a limit, the memory spike during a blocklist rebuild lets the kernel's
OOM killer pick a victim anywhere on the host, which is how a 1 GB Pi locks up
instead of restarting one container. With the limit, the container is the only
thing that can die, and restart: unless-stopped brings it back. Swap is
disabled for the container deliberately: swapping a resolver onto an SD card
is worse than restarting it.
deploy/README-raspi.md has the full walkthrough, including blocklist choices
sized for the board.
Blocklist size and memory¶
The list set is the main thing that decides the memory ceiling. HaGeZi's
ultimate.txt plus tif.medium.txt is roughly 750k rules and comfortable on
a 2 GB Pi; the full tif.txt alone is around 1.7M and is not. If Trench is
being OOM-killed, cut lists before anything else.
Multiple workers¶
server:
workers: 0 # 0 = one per CPU
Do53 runs in every worker, with the kernel distributing datagrams across pre-bound inherited sockets. The encrypted transports, the API, DHCP and the scheduler run in the primary worker only. Counters and the block table are shared across processes, so the console still reports whole-server numbers.
Backups¶
trench backup /path/to/trench-$(date +%F).tar.gz
trench restore /path/to/trench-2026-08-19.tar.gz
The archive covers the data directory: the query-log database, users and API
tokens, custom rules, and zone data. backup can run from cron while the
server is up — databases are snapshotted through SQLite's backup API, not
copied mid-write. Stop the server before a restore, which replaces the data
directory's contents wholesale (--force when it is not empty).
Monitoring¶
Prometheus metrics are at /metrics and a liveness endpoint at /healthz;
neither requires authentication, so keep the console's port on a network you
trust. See API.