Running DB Agent Rootless
DB Agent talks to its container engine through the Docker API, and rootless Podman exposes a compatible socket under your user account. This guide sets DB Agent up to run entirely as a normal user: the binary, its config, its data and the database containers all belong to that user, and nothing runs as root.
Root is still needed once, to install Podman and to let the user's services run without an open login session. After that, DB Agent and every database container run unprivileged.
Podman only
Rootless mode relies on Podman's keep-id user namespace mapping. Rootless Docker has no equivalent, so this guide covers Podman only.
Before You Start
Install Podman through your distribution's package manager (apt install podman, dnf install podman), then enable the Podman socket as the user DB Agent will run as:
systemctl --user enable --now podman.socketUser services normally stop when the user logs out. Enable lingering so the socket, DB Agent and the databases keep running and come back up after a reboot. This is the one step that needs root:
sudo loginctl enable-linger $USERCheck that the user's UID has a subordinate ID range, which Podman needs to map container users:
grep "^$USER:" /etc/subuid /etc/subgidMost distributions add one when the user is created. If either file has no line for the user, add one as root, for example sudo usermod --add-subuids 100000-1099999 --add-subgids 100000-1099999 $USER.
Install the Binary
Install DB Agent into your home directory instead of /usr/local/bin:
mkdir -p ~/.local/bin
curl -L "https://github.com/calagopus/db-agent/releases/latest/download/db-agent-$(uname -m)-linux" -o ~/.local/bin/calagopus-db-agent
chmod +x ~/.local/bin/calagopus-db-agent
~/.local/bin/calagopus-db-agent versionCreate the Config
DB Agent looks for its config in /etc/calagopus-db-agent/config.yml by default, which a normal user can't write. Keep it in your home directory and pass its path with --config every time:
mkdir -p ~/.config/calagopus-db-agent
~/.local/bin/calagopus-db-agent --config ~/.config/calagopus-db-agent/config.yml configure --token <TOKEN>This writes a config with all defaults and your API token. The defaults assume root, so change the following.
Directories
The default socket, data and log directories are system paths. Move them somewhere the user owns, and move the SQLite database with the data directory:
-socket_dir: /run/calagopus-db-agent
+socket_dir: /run/user/1000/calagopus-db-agent
-data_dir: /var/lib/calagopus-db-agent/data
+data_dir: /home/robert/.local/share/calagopus-db-agent/data
-log_dir: /var/log/calagopus-db-agent
+log_dir: /home/robert/.local/state/calagopus-db-agent
database:
- url: sqlite:///var/lib/calagopus-db-agent/data/database.db
+ url: sqlite:///home/robert/.local/share/calagopus-db-agent/data/database.dbReplace 1000 with your user ID (id -u) and /home/robert with your home directory. The socket directory lives under /run/user/1000, which is a tmpfs: it's emptied on reboot, and DB Agent recreates each database's socket directory when it starts that database.
Socket path
Point DB Agent at the rootless Podman socket instead of Docker's:
docker:
- socket: /var/run/docker.sock
+ socket: /run/user/1000/podman/podman.sockRootless mode
Set docker.rootless.enabled to true:
docker:
rootless:
- enabled: false
+ enabled: trueEach template sets the UID and GID its image runs the database as, for example 70 for Alpine PostgreSQL and 999 for MariaDB. As root, DB Agent chowns the database's data and socket directories to that UID. A normal user can't do that, so without rootless mode every database fails to start.
With rootless mode on, DB Agent starts each container with keep-id:uid=<image_uid>,gid=<image_gid>, which maps the image's database user onto your host user. The database runs as its usual UID inside the container, and its files are owned by you on the host. The chown is still attempted, and when Podman refuses it DB Agent logs chown refused under a rootless engine once at debug level and carries on. docker.userns_mode is ignored in this mode.
The stock templates work unchanged.
Log driver
Podman doesn't support the local log driver DB Agent uses by default, and every database fails to start with invalid log driver. Switch to json-file:
log_config:
- type: local
+ type: json-fileTCP congestion control
DB Agent tries to load bbr with modprobe, which a normal user can't run. A user also can't set a congestion control algorithm on a socket unless root has added it to net.ipv4.tcp_allowed_congestion_control. Leave the setting empty to keep the system default and skip the startup warnings:
-tcp_congestion_control: bbr
+tcp_congestion_control: ''If you want BBR anyway, load it as root and allow it:
sudo modprobe tcp_bbr
sudo sysctl -w net.ipv4.tcp_allowed_congestion_control="reno cubic bbr"Ports
The default ports (5432, 3306, 27017, 6379, and 8090 for the API) are all above 1024, so an unprivileged user can bind them. If you move any of them below 1024, lower net.ipv4.ip_unprivileged_port_start as root, or the listener will fail to bind.
Test It
Run DB Agent in the foreground once:
~/.local/bin/calagopus-db-agent --config ~/.config/calagopus-db-agent/config.ymlIt should end with http listening on 0.0.0.0:8090. Stop it with Ctrl-C.
Install as a User Service
calagopus-db-agent service-install writes a system unit that runs as root, so don't use it here. Create a user unit at ~/.config/systemd/user/db-agent.service instead:
[Unit]
Description=Calagopus DB Agent
After=podman.socket
Requires=podman.socket
[Service]
KillMode=process
LimitNOFILE=4096
RuntimeDirectory=calagopus-db-agent
RuntimeDirectoryMode=0700
RuntimeDirectoryPreserve=yes
ExecStart=%h/.local/bin/calagopus-db-agent --config %h/.config/calagopus-db-agent/config.yml
Restart=on-failure
StartLimitInterval=180
StartLimitBurst=30
RestartSec=5s
[Install]
WantedBy=default.targetIn a user unit, RuntimeDirectory= creates the directory under /run/user/<UID>, which is the socket_dir set above, and %h expands to your home directory.
Enable and start it:
systemctl --user daemon-reload
systemctl --user enable --now db-agent
systemctl --user status db-agentLogs go to the user journal:
journalctl --user -u db-agent -fDatabases that were running when DB Agent stopped are started again when it comes back.
Connect It to the Panel
Nothing changes on the Panel side. Add the DB Agent under the Panel's admin area with the host's address, port 8090 and the token you configured, exactly as for a root install.
IO Weights
A template's io_weight needs the io cgroup controller. By default systemd only delegates cpu, memory and pids to user sessions, so a rootless database with an IO weight fails to start with:
preparing container ... for attach: container create failed (no logs from conmon)Check what your user gets:
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.subtree_controlIf io isn't listed, either leave io_weight empty in your templates (the stock templates already do), or delegate the controller as root:
sudo mkdir -p /etc/systemd/system/user@.service.d
printf '[Service]\nDelegate=cpu cpuset io memory pids\n' | sudo tee /etc/systemd/system/user@.service.d/delegate.conf
sudo systemctl daemon-reloadLog the user out and back in, or reboot, for it to take effect. Even with the controller delegated, IO weights only do something on hosts whose IO scheduler enforces them (BFQ or an iocost model). DB Agent warns about this at start when it isn't the case.
SELinux
On SELinux hosts (RHEL, AlmaLinux, Rocky, Fedora), DB Agent detects SELinux and asks Podman to relabel the bind mounts it creates with the shared z option, so each database container can reach its own data and socket directories. Nothing needs configuring for this.
Full Diff
Everything changed from a default config:
-socket_dir: /run/calagopus-db-agent
+socket_dir: /run/user/1000/calagopus-db-agent
-data_dir: /var/lib/calagopus-db-agent/data
+data_dir: /home/robert/.local/share/calagopus-db-agent/data
-log_dir: /var/log/calagopus-db-agent
+log_dir: /home/robert/.local/state/calagopus-db-agent
-tcp_congestion_control: bbr
+tcp_congestion_control: ''
database:
- url: sqlite:///var/lib/calagopus-db-agent/data/database.db
+ url: sqlite:///home/robert/.local/share/calagopus-db-agent/data/database.db
docker:
- socket: /var/run/docker.sock
+ socket: /run/user/1000/podman/podman.sock
rootless:
- enabled: false
+ enabled: true
log_config:
- type: local
+ type: json-file