In a container
The published image, the two ports it opens, every variable it reads, and the one setting that decides whether your store is open to the network.
docker run -d --name tessaridb \
-p 9080:9080 -p 8000:8000 \
-v tessaridb-data:/var/lib/tessaridb \
-e TESSARIDB_INITIAL_USER=owner \
-e TESSARIDB_INITIAL_PASSWORD='choose-a-real-one' \
tessaridb/tessaridb
That is a node. It needs no repository access, no toolchain and no C++ compiler, which is what makes it the shortest path to a running store while the engine's repository is still private — see Install for the build from source and what it requires.
The image is tessaridb/tessaridb
on Docker Hub.
The one thing to get right
A store with no users is open. It runs anything for anybody who reaches the
port, because the first user is what closes it — and nothing types a
DEFINE USER into a node that came up unattended. Users
explains why the rule is that way round.
So set TESSARIDB_INITIAL_USER and TESSARIDB_INITIAL_PASSWORD on the first
start. The node declares that user as a store-wide owner, once, when the
store has no users at all, and says so in its log:
INFO tessaridb::bootstrap declared owner as the store's first user, from TESSARIDB_INITIAL_USER
Every start after that leaves an existing store alone and says that instead — which is the line that distinguishes this did nothing because it was already done from this did nothing and nobody noticed:
INFO tessaridb::bootstrap this store already has users, so TESSARIDB_INITIAL_USER was not applied
So the pair can stay in a compose file forever. They are not a way to reset a password: once the store is closed the variables do nothing, and a lost owner password is a restore from backup rather than a recovery.
Set only one of the two and the node refuses to start. That is deliberate. The alternative failure is a node that came up open because a variable was misspelled, which looks exactly like a node that came up correctly.
The image ships no default credential on purpose. One would close every store that pulls it with a password the whole internet knows, which is worse than the open store it appears to fix.
The two ports
| Port | Surface | What speaks it |
|---|---|---|
9080 | the wire protocol | the tessaridb prompt, the Rust client |
8000 | HTTP | anything that speaks JSON, and the web console at / |
One process serves both over one store. Either can be turned off by setting its address to the empty string, and both empty is refused rather than started — a node asked to serve nothing would read from standard input, reach end of file and exit, which is indistinguishable from a crash.
Give the node a certificate and both surfaces speak TLS 1.3. Mount the
certificate chain and its key — a secret mount, not the store's volume — and point
TESSARIDB_TLS_CERT and TESSARIDB_TLS_KEY at them. The node runs as uid
10001 and refuses a key anybody else may read: own the key file by 10001
with mode 600, or leave it root's with mode 440 and the node's group able to
read it, which is how a Kubernetes secret with fsGroup arrives. Without them
the node —
single or in a cluster — serves in the clear and says so when it starts;
TESSARIDB_REQUIRE_CLIENT_TLS=1 makes it refuse to start instead. The nodes of a
cluster speak mutual TLS to each other either way.
Encryption in transit says the rest.
The console is already in there
Open http://localhost:8000 and the web console is
there. It is served by the binary — the page, its stylesheet, its two scripts and
its icon are all inside the executable, so there is no second image to run beside
this one and nothing to keep at the same version as the node. Sign in with the
user you declared above; the store is closed, so an anonymous session is refused.
Every variable it reads
| Variable | Default | What it does |
|---|---|---|
TESSARIDB_STORE | /var/lib/tessaridb/store | where the store lives. Empty is a store held in memory and lost when the container stops |
TESSARIDB_ADDRESS | 0.0.0.0:9080 | the wire protocol's address. Empty turns it off |
TESSARIDB_HTTP_ADDRESS | 0.0.0.0:8000 | the HTTP surface's address. Empty turns it off |
TESSARIDB_LOG | info | error, warn, info, debug or trace, or directives such as info,tessari_wire=debug |
TESSARIDB_LOG_FORMAT | unset — text | json writes one object per event for a log collector |
TESSARIDB_BACKUP_DIR | /var/lib/tessaridb/backups | where BACKUP … TO '<name>' and the console's Backup tab write, inside the volume so a backup outlives the container. Empty refuses every TO |
TESSARIDB_RETAIN_RECORDS | unset — the node keeps 100 000 | how many records of each log a serving node keeps before it prunes the rest; none keeps the whole log. A DEFINE NODE RETAIN stored on the node wins over it (Bounding the log) |
TESSARIDB_UNSEAL_FOR | 10m | how long an unseal lasts before the store seals itself (90s, 1h). Read by the node, so it works outside a container too |
TESSARIDB_TLS_CERT | unset | the certificate chain clients are served under, PEM; with the key, every client surface speaks TLS 1.3 (Encryption in transit) |
TESSARIDB_TLS_KEY | unset | its private key, PEM |
TESSARIDB_REQUIRE_CLIENT_TLS | unset | 1 refuses to start without TESSARIDB_TLS_CERT and TESSARIDB_TLS_KEY, for a deployment that forbids the clear; off by default |
TESSARIDB_CLIENT_PLAINTEXT | unset | retired: the clear is already the default without a certificate; 1 is refused from 0.24.0-beta — drop it |
TESSARIDB_ENCRYPTION_KEY_FILE | unset | 32 bytes, mode 600, on a secret mount: the store and every backup it writes are encrypted under it (Encryption at rest) |
TESSARIDB_JOIN_TOKEN | unset | a token from CREATE JOIN TOKEN, offered to the seeds by a node joining a cluster |
TESSARIDB_INITIAL_USER | unset | the first user, declared once on a store that has none |
TESSARIDB_INITIAL_PASSWORD | unset | that user's password. Both or neither |
TESSARIDB_PASSWORD | unset | the password for --user, when the image is run as a client |
TESSARIDB_TLS_AUTHORITY | unset | as a client: speak TLS and trust the node by these certificates, PEM |
0.0.0.0 rather than a loopback address, because a container's loopback is
reachable from nothing outside it — a node bound there answers no published port,
which again looks like a node that failed to start.
The same image is the client
There is one binary, so the image that serves is the image that connects. A prompt against a running node needs nothing installed:
docker run --rm -it --network host \
-e TESSARIDB_PASSWORD='choose-a-real-one' \
tessaridb/tessaridb --at 127.0.0.1:9080 --user owner
Anything that is not the word serve is passed straight to the binary, so
--version, --help, -e '…', --backup and --verify all work the way
the command line describes them.
The store's directory
/var/lib/tessaridb is declared as a volume. The container runs as uid 10001,
so a bind mount has to be owned by it:
mkdir -p ./data && sudo chown 10001:10001 ./data
docker run -d -v "$PWD/data:/var/lib/tessaridb" … tessaridb/tessaridb
A named volume needs none of that. A bind mount presents the host's ownership and shadows whatever the image set, so the wrong uid there is a store the node cannot open — reported as a database fault rather than as a permissions one.
Health, and stopping
The image's health check asks the node's own GET /health, which needs no
credential precisely so that a supervisor does not have to hold one. When the
HTTP surface is turned off it falls back to a connect on the wire port and says
so, rather than reporting healthy because nothing was checked. The three probes
are described in Serving.
There is no init process in the image. The node installs its own SIGTERM
and SIGINT handlers before any surface serves and forks nothing, so it is
correct as PID 1 and docker stop reaches it directly: it reports not ready
for five seconds, stops accepting, gives in-flight work twenty seconds, and
closes the store. A second signal exits at once.
With compose
services:
tessaridb:
image: tessaridb/tessaridb:0.34.2-beta
restart: unless-stopped
ports:
- "9080:9080"
- "8000:8000"
volumes:
- tessaridb-data:/var/lib/tessaridb
environment:
TESSARIDB_INITIAL_USER: owner
TESSARIDB_INITIAL_PASSWORD: ${TESSARIDB_OWNER_PASSWORD:?set it}
TESSARIDB_LOG: info
volumes:
tessaridb-data:
An environment variable is readable by anything that can inspect the container or the process, which is a smaller circle than a command line but is not a secret store. That is worth knowing rather than discovering.
What is in the image, and what is not
The build is two stages and only the binary crosses between them, so the published image carries no source, no toolchain and no repository:
/usr/local/bin/tessaridb | the node, the client and the shell — about 11 MB |
/usr/local/bin/tessaridb-entrypoint | turns the variables above into the command line |
/usr/local/bin/tessaridb-healthcheck | the probe |
/usr/share/doc/tessaridb/LICENSE | BUSL-1.1, the same text as the repository's |
| everything else | a debian:bookworm-slim base — about 108 MB in total |
No cargo, no rustc, no .rs file and no Cargo.toml reach the finished
image.
Tags
| Tag | What it is |
|---|---|
latest | the most recent build |
0.34.2-beta | that version, pinned |
latest moves. Pin the version tag for anything you would mind losing, and read
Backup and restore before you rely on a store
surviving a version change — the on-disk format is still moving, and a store
written by one pre-release build is not promised to open under the next.
Next
- Your first store — a table, a record, and a read.
- Connecting — reaching a node from somewhere else.
- Serving — the probes, draining, and what a node does on the way down.
- Users — why the first user is the one that closes the store.