Running in production¶
leBLOB runs as one command, leblob serve, on one machine. This page covers what to set when the store holds data that matters: addresses, TLS, workers, a service manager, containers and upgrades.
Early software
leBLOB is alpha software. Keep a copy of anything you store in it somewhere else, and read Known limits before relying on it.
Address and port¶
By default the server listens on 127.0.0.1:9000, so nothing outside the machine reaches it. Set --host 0.0.0.0 to listen on every interface, and --port to change the port.
/-/health and /-/metrics answer without a signature. If the store is reachable from the internet, block both paths at the proxy in front of it, as Monitoring explains.
TLS¶
Either let leBLOB serve HTTPS itself:
leblob serve --data-dir /srv/leblob --host 0.0.0.0 --port 443 \
--ssl-certfile /etc/leblob/cert.pem --ssl-keyfile /etc/leblob/key.pem
or put it behind a reverse proxy that terminates TLS, such as nginx or Caddy, and keep leBLOB on 127.0.0.1. The proxy must pass the request path and the Host header through unchanged, because both are part of the signature a client sends. It must not normalise paths: the keys foo/ and a//b are distinct objects.
Clients behave differently over HTTP and HTTPS. boto3, for instance, sends a different body framing over HTTPS. leBLOB handles both. The tests run every client over both.
Encryption keys over plain HTTP¶
Requests with an SSE-C encryption key are refused over plain HTTP, as AWS does, since anyone on the way could read the key. Behind a proxy that terminates TLS, leBLOB only sees plain HTTP, so start it with --allow-sse-c-over-http. Use that option only when the hop between proxy and server can't be observed, such as 127.0.0.1; SSE-C has the details.
Virtual-hosted addressing¶
Clients address a bucket in the path by default when they talk to a custom endpoint: https://s3.example.org/my-bucket/key. To also accept the bucket in the host name, https://my-bucket.s3.example.org/key, give the base domain:
A request whose host ends in .s3.example.org then names its bucket there. Any other host is read as path-style. DNS needs a wildcard record. With TLS on, the certificate must also cover *.s3.example.org.
Workers and memory¶
--workers sets the number of worker processes. The default is the number of CPUs, at most 4. Each worker takes about 30 MiB.
Writes gain nothing from more workers: each one waits for the disk to flush, and all workers share one metadata writer. Reads, which use CPU, do gain. Workers hold no state of their own, so any worker can answer any request, continue any listing or complete any upload.
Shutdown¶
On SIGTERM or SIGINT the server stops accepting connections and lets requests finish for up to --shutdown-grace seconds, 20 by default, then exits. The limit is there because a client that stops reading would otherwise keep the server running for ever.
A request cut short by shutdown or a crash changes nothing visible: an object appears only once the metadata commits, after the bytes are on disk.
The data directory¶
Everything lives in the directory given by --data-dir, data by default:
data/
meta.db the metadata: SQLite, with -wal and -shm beside it
meta.db.writer-lock empty; writers queue on it
volume.json names this directory as a volume
tmp/ uploads in progress
blobs/ the stored bytes, one file per blob, in 4096 shard directories
metrics/ counters each worker writes for /-/metrics
meta.db holds the access keys' secrets, because SigV4 needs the secret itself. leBLOB sets the directory to mode 0700 at every start. Blob files have random names, so without meta.db the bytes can't be put back together into objects: back it up as Backup and restore describes.
To use more than one disk, add volumes: see Disks and copies.
Running under systemd¶
Here is a unit for a store in /srv/leblob, run by a dedicated user:
# /etc/systemd/system/leblob.service
[Unit]
Description=leBLOB S3 object store
After=network-online.target local-fs.target
Wants=network-online.target
[Service]
User=leblob
Group=leblob
ExecStart=/opt/leblob/bin/leblob serve --data-dir /srv/leblob --host 0.0.0.0 --port 9000
Restart=on-failure
KillSignal=SIGTERM
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target
Here leBLOB is installed in a virtual environment at /opt/leblob, for example with uv venv /opt/leblob and then uv pip install --python /opt/leblob with the package or a wheel built by uv build. Keep TimeoutStopSec above --shutdown-grace, so requests can finish before systemd kills the process.
The first start creates an access key and prints it once: find it with journalctl -u leblob. Otherwise, create one with leblob key create --data-dir /srv/leblob, as the same user.
If volumes are on other disks, add RequiresMountsFor= with their mount points. An unmounted disk looks like an empty directory, so leBLOB checks each volume's identity. With one copy of each blob, it refuses to start without all its volumes. With two or more, it starts without the missing one and logs a warning, as Disks and copies explains.
In a container¶
The repository has a Dockerfile:
docker build -t leblob .
docker run -d --name leblob -p 9000:9000 -v leblob-data:/data leblob
docker logs leblob # the first start prints an access key once
The image runs as the user leblob, keeps the store in the volume /data, listens on port 9000, and checks /-/health for Docker's health status. Arguments after the image name are added to leblob serve, so docker run ... leblob --workers 2 sets the number of workers. Run the other commands inside the container:
docker exec leblob leblob key create --data-dir /data
docker exec leblob leblob backup /data/backups/meta-$(date +%F).db --data-dir /data
Put backups on a volume other than the store's, so losing one disk doesn't take both.
Upgrades¶
A newer version upgrades the store's format when it starts. An older version can't open it afterwards. Back up the metadata before upgrading:
Opening a store from an earlier release is tested: tests/upgrade/stores/ keeps a sample store written by each release, and the tests open each one with the current code and read its objects back. Read the changelog for anything else that changed between releases.
Durability and the disk¶
An acknowledged write is on disk: the blob file is flushed and renamed into place, the directory flushed, and then the metadata committed with SQLite's synchronous=FULL. That has been tested by cutting the power at the block layer on ext4, XFS and btrfs, as Testing describes.
That promise can only be as good as the drive's own. A drive that acknowledges a flush it hasn't done can lose acknowledged writes on power loss, whatever the software does. Drives with power-loss protection don't have that problem.
macOS
On macOS, fsync doesn't flush the drive's cache, and leBLOB doesn't use F_FULLFSYNC. A power cut on a Mac can lose acknowledged writes. Run production stores on Linux, which is the platform leBLOB is tested and measured on. For the same reason, small writes look 10 to 30 times faster on a Mac, so don't judge write speed there.