Operating
systemd and Docker
applies to v0.5.1The Debian package installs LuraDB as the systemd service luradb. It runs as the luradb user and logs to journald. The same package runs in Docker with the compose file in the repository.
The package installs one service
Section titled “The package installs one service”After apt install, these files are on the host:
| Path | What it is | Owner and mode |
|---|---|---|
/usr/bin/luradb |
The server binary. | root, 755 |
/usr/lib/systemd/system/luradb.service |
The unit. | root, 644 |
/etc/luradb/luradb.toml |
The configuration, a dpkg conffile that also holds the admin key. | root:luradb, 640 |
/etc/luradb/tls/ |
Empty; the place for the certificate and key of the HTTPS listener. | root:luradb, 750 |
/usr/share/doc/luradb/ |
README.md, INSTALL.md and the copyright file. |
root, 644 |
/var/lib/luradb/ |
The data, created by systemd when the service starts. | luradb, 750 |
/run/luradb/ |
The runtime directory with the Unix socket, created by systemd. | luradb, 750 |
The package creates the system user and group luradb without a login and without a home directory, enables the unit and does not start it. Install walks through the first start; this page covers running the service afterwards. The unit is this file:
[Unit]Description=LuraDB multi-model databaseAfter=network.targetStartLimitIntervalSec=300StartLimitBurst=3
[Service]Type=simpleUser=luradbGroup=luradbExecStart=/usr/bin/luradb --config /etc/luradb/luradb.tomlWorkingDirectory=/var/lib/luradbStateDirectory=luradbStateDirectoryMode=0750RuntimeDirectory=luradbRuntimeDirectoryMode=0750UMask=0027LimitNOFILE=65536LimitMEMLOCK=infinityRestart=on-failureRestartSec=2RestartPreventExitStatus=4
NoNewPrivileges=trueProtectSystem=strictProtectHome=truePrivateTmp=trueProtectKernelTunables=trueProtectControlGroups=trueRestrictSUIDSGID=true
[Install]WantedBy=multi-user.target| Setting | Effect |
|---|---|
User=luradb, Group=luradb |
The server runs as the user the package created. |
WorkingDirectory=/var/lib/luradb |
A relative path in luradb.toml resolves against this directory. |
StateDirectory=luradb, StateDirectoryMode=0750 |
systemd creates /var/lib/luradb for the service. |
RuntimeDirectory=luradb, RuntimeDirectoryMode=0750 |
systemd creates /run/luradb, where the Unix socket lives. Group members need the execute bit on the directory to reach the socket. |
UMask=0027 |
Files the server creates are readable for the owner and the group, and not for others. |
LimitNOFILE=65536 |
The most open files and connections. |
LimitMEMLOCK=infinity |
The io_uring buffers the server registers need locked memory. |
Restart=on-failure, RestartSec=2 |
systemd starts the service again two seconds after it ended with an error. |
RestartPreventExitStatus=4 |
After exit code 4 it does not restart, see below. |
StartLimitIntervalSec=300, StartLimitBurst=3 |
At most three starts in five minutes. |
NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp, ProtectKernelTunables, ProtectControlGroups, RestrictSUIDSGID |
Hardening. The unit sets no SystemCallFilter on purpose, because the io_uring calls must stay allowed; /dev/shm stays writable for the shared-memory path. |
Start, stop and read the log
Section titled “Start, stop and read the log”The service is enabled, so it also starts on the next boot. The commands are those of the package’s INSTALL.md:
sudo systemctl start luradbsystemctl status luradbjournalctl -u luradbjournalctl -u luradb -fThe server reads luradb.toml once, at start. After a change:
sudoedit /etc/luradb/luradb.tomlsudo systemctl restart luradbsystemctl stop sends SIGTERM, as does Ctrl+C for a server started by hand. The server stops accepting connections, finishes the requests in flight, drains the data shards and exits with code 0:
INFO luradb: Shutdown signal received. Starting graceful shutdown...INFO luradb::server: [http] listener stoppedINFO luradb::server::control: Draining the data shards...INFO luradb::server::control: Shutting down system store...INFO luradb::server::control: Shutdown complete.A write that was confirmed is in the write-ahead log, so even a stop with SIGKILL loses no confirmed write: in a test, a key written before kill -9 was there after the next start. An update restarts the service only if it is running, see Upgrading. Removing the package keeps the data and the user:
sudo apt remove luradb # binary and unit gone, the configuration stayssudo apt purge luradb # also removes /etc/luradb/luradb.toml/var/lib/luradb and the user luradb are never deleted by the package. Removing them is up to you (sudo rm -rf /var/lib/luradb, sudo deluser luradb).
Directories and permissions
Section titled “Directories and permissions”Every data path in the packaged luradb.toml lies below /var/lib/luradb, the one directory the service may write to besides /run/luradb. Relative paths resolve there too, because of WorkingDirectory=:
| Setting | Packaged value | Where it lands |
|---|---|---|
storage.wal_path |
/var/lib/luradb/luradb.wal |
A directory of 64 MiB segment files. |
storage.vlog_path |
/var/lib/luradb/luradb.vlog |
The value log; its files live in shard_<k>/ beside this path. |
storage.sstable_dir |
/var/lib/luradb/sstables |
SSTables, in shard_<k>/ as well. |
storage.system_dir |
/var/lib/luradb/luradb_sys |
Users, key hashes and permissions, and the lock file. |
json.wal_path, json.vlog_path, json.sstable_dir |
luradb_json.wal and so on |
Below /var/lib/luradb, relative to the working directory. |
rel.wal_path, rel.vlog_path, rel.sstable_dir |
luradb_rel.wal and so on |
The same. |
backup.dir |
luradb_backups |
/var/lib/luradb/luradb_backups. |
log.path |
not set | No files: the log goes to the journal. |
server.unix_socket_path |
/run/luradb/luradb.sock |
Mode 0660, group luradb. |
server.tls_cert_path, server.tls_key_path |
/etc/luradb/tls/server.crt and server.key |
Read at start. |
With ProtectSystem=strict, systemd mounts the file system read-only for the service, apart from the state and runtime directories. A data, log or backup path outside /var/lib/luradb, for example a disk mounted at /mnt/backup, is therefore not writable until you allow it in a drop-in, which systemctl edit creates and keeps across updates of the package:
sudo systemctl edit luradb[Service]ReadWritePaths=/mnt/backupRestart the service after it. Nothing below /home is reachable (ProtectHome=true), and the service has a private /tmp. The same drop-in is where MemoryHigh= goes, which caps the memory of the service from the system side in addition to resources.memory_budget.
The socket directory has the mode 0750 and the socket 0660, so membership of the group luradb is what lets a local user connect. Files the server writes are group-readable (UMask=0027). Add users to the group only if they may use the socket, see Unix socket and trusted users.
Restarts, exit codes and the start limit
Section titled “Restarts, exit codes and the start limit”The server ends with an exit code that tells systemd what to do:
| Exit code | Meaning | systemd |
|---|---|---|
0 |
A stop through SIGTERM or SIGINT. |
No restart, because the exit is not a failure. |
1 |
The start was refused, for example for an invalid configuration. | Restarts after two seconds, until the start limit is reached. |
3 |
The disk reported an error while making data durable, or writes to a data file failed for good. | Restarts; the start recovers every confirmed write from the write-ahead log. |
4 |
At the start, a volume has less free space than the recovery needs. | No restart (RestartPreventExitStatus=4). Free space, then start the service. |
The unit allows three starts within five minutes, manual ones included. After that, systemd refuses to start the service until you reset the counter:
sudo systemctl reset-failed luradbA refused start, such as a mistake in the configuration, therefore ends in a unit that systemd no longer starts after the third attempt. Read the message with journalctl -u luradb -b, fix it, run reset-failed and start again. Troubleshooting lists the messages, and Resource guard explains the codes 3 and 4.
Run in Docker
Section titled “Run in Docker”The Docker setup is a way to try LuraDB on any machine with Docker; it is not a distribution image. It builds an image from a .deb you provide, so nothing is compiled, and nothing beyond the ubuntu:24.04 base image is pulled. Install has the steps. The container shares the kernel of its host (with Docker Desktop, of its virtual machine), which must be Linux 6.1 or newer.
compose.yaml is the whole setup:
name: luradb
services: luradb: build: context: . additional_contexts: debdir: ../../target/debian image: luradb:local init: true # Docker's default seccomp profile blocks io_uring — LuraDB requires it. security_opt: - seccomp=unconfined # io_uring registered buffers need memlock (see systemd unit). ulimits: memlock: -1 ports: - "127.0.0.1:${LURADB_PORT:-3000}:3000" volumes: - data:/var/lib/luradb - etc:/etc/luradb healthcheck: test: ["CMD", "curl", "-fs", "http://127.0.0.1:3000/health"] interval: 5s timeout: 3s retries: 5 start_period: 15s
volumes: data: etc:| Setting | Effect |
|---|---|
security_opt: seccomp=unconfined |
Docker’s default seccomp profile blocks the io_uring calls LuraDB needs. Without this, a plain docker run fails at start. |
ulimits: memlock: -1 |
The counterpart of LimitMEMLOCK=infinity in the unit. |
init: true |
Docker runs an init process as process 1 of the container, with the server as its child. |
ports |
The container’s port 3000 is published on 127.0.0.1 of the host only. LURADB_PORT picks the host port. |
volumes |
data holds /var/lib/luradb, etc holds /etc/luradb, so the data, the configuration and the admin key outlive the container. |
healthcheck |
curl -fs on /health every five seconds. It fails on 503 and passes on a degraded server that answers 200. |
The image installs the package like a target system and then changes three lines of the packaged configuration: the bind address becomes 0.0.0.0, because Docker’s port mapping reaches the container through its network interface and the host-side bind stays local, the Swagger UI is switched on, and the admin key goes back to the text @GENERATED_ON_INSTALL@, so that no key is baked into an image layer. Authentication stays on. The entrypoint stands in for what systemd does on the host: it creates /var/lib/luradb and /run/luradb, generates the admin key on the first start, and starts the server as luradb.
The admin key is printed once, on the first start, and kept in the etc volume. Set LURADB_ADMIN_KEY in the container’s environment before that first start to use a key of your own; the stock compose.yaml forwards no host variables, so this takes an environment: entry of your own or docker compose run -e.
docker compose up -ddocker compose logs luradb | grep "admin api key"docker compose exec luradb grep api_key /etc/luradb/luradb.tomlcurl http://localhost:3000/healthThe stock compose.yaml sets no restart: policy, so a stopped container stays stopped. Docker cannot leave out an exit code from a restart policy, which matters because of the codes above: restart: on-failure:3 restarts after exit code 3 and after any failure at most three times, while restart: unless-stopped or restart: always would restart exit code 4 in a loop.
The configuration is the packaged one, in the etc volume, with the Unix socket at /run/luradb/luradb.sock inside the container. Removing the volumes removes the data, the configuration and the key together.