PrkDB Deployment Guide
This guide reflects the current prkdb-server binary and the current prkdb-cli verification workflow.
Architecture
- Inter-node Raft traffic uses the addresses in
CLUSTER_NODES. - Client gRPC traffic is multiplexed on the same address and port as the local node entry in
CLUSTER_NODES. GRPC_PORTis optional. When set, it must match the local node'sCLUSTER_NODESport.- Use
PRKDB_ADVERTISED_GRPC_ADDRwhen the bind address differs from the dialable client address. - Use
PRKDB_ADVERTISED_NODE_ADDRSwhen peer nodes also need explicit dialable client addresses in metadata, for example2=http://db-2.example.com:8081,3=http://db-3.example.com:8082. - Metrics bind to
127.0.0.1:(9090 + NODE_ID)by default. SetPRKDB_METRICS_ADDRto override orPRKDB_DISABLE_METRICS=1to disable them. - Schema registry data is persisted under
${STORAGE_PATH}/schemas.
Build
cargo build --release --bin prkdb-server --bin prkdb-cli
cp target/release/prkdb-server /usr/local/bin/
cp target/release/prkdb-cli /usr/local/bin/prkdbExample 3-Node Cluster
Node addresses
- Node 1 address:
10.0.0.1:8080 - Node 2 address:
10.0.0.2:8081 - Node 3 address:
10.0.0.3:8082
prkdb-server refuses to start a multi-node CLUSTER_NODES unless one of PRKDB_CLUSTER_SECRET, PRKDB_TLS_CLIENT_CA, or PRKDB_ALLOW_UNAUTHENTICATED_PEERS=1 is set. Today, only PRKDB_ALLOW_UNAUTHENTICATED_PEERS=1 actually forms a working cluster: nothing in the codebase sends the cluster-secret header to peers, and prkdb-server does not configure peer TLS even when PRKDB_TLS_CLIENT_CA is set — setting either of those two variables passes the startup check but peers still reject each other and no leader is elected (known issue: RFT-08). Mutual TLS for peers currently only works with prkdb-cli serve.
| Variable | Purpose |
|---|---|
PRKDB_ALLOW_UNAUTHENTICATED_PEERS=1 | The only mode that currently forms a cluster. Development on a trusted network only — any caller that can reach the port can issue Raft RPCs. |
PRKDB_CLUSTER_SECRET / PRKDB_TLS_CLIENT_CA | Satisfy the startup check but do not currently authenticate peers (RFT-08) |
PRKDB_BOOTSTRAP_TOKEN | Creates the first admin principal on an empty data directory; ignored once any principal exists; use the same value on every node |
PRKDB_METRICS_ADDR | Metrics bind address; use 0.0.0.0:<port> inside containers |
/metrics requires an Admin bearer token: curl -H "Authorization: Bearer $PRKDB_BOOTSTRAP_TOKEN" http://localhost:9091/metrics.
./scripts/start_cluster.sh starts a dev-only, unauthenticated 3-node cluster using PRKDB_ALLOW_UNAUTHENTICATED_PEERS=1.
Systemd unit
Create /etc/systemd/system/prkdb.service on each node.
[Unit]
Description=PrkDB Server
After=network.target
[Service]
Type=simple
User=prkdb
WorkingDirectory=/var/lib/prkdb
Environment=NODE_ID=1
Environment=CLUSTER_NODES=1@10.0.0.1:8080,2@10.0.0.2:8081,3@10.0.0.3:8082
Environment=STORAGE_PATH=/var/lib/prkdb/node1
# Required: the server refuses to start with no principals configured.
# Ignored once any principal exists, so a restart cannot mint a second way in.
# Use the same value on every node.
Environment=PRKDB_BOOTSTRAP_TOKEN=change-me
# Required for a multi-node CLUSTER_NODES: one of PRKDB_CLUSTER_SECRET,
# PRKDB_TLS_CLIENT_CA or PRKDB_ALLOW_UNAUTHENTICATED_PEERS. Only the last forms a
# cluster today (RFT-08); run it only on a trusted network.
# See https://prk-jr.github.io/prkdb/status/remediation
Environment=PRKDB_ALLOW_UNAUTHENTICATED_PEERS=1
Environment=PRKDB_ADVERTISED_GRPC_ADDR=http://db-1.example.com:8080
Environment=PRKDB_ADVERTISED_NODE_ADDRS=2=http://db-2.example.com:8081,3=http://db-3.example.com:8082
ExecStart=/usr/local/bin/prkdb-server
Restart=always
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.targetFor node 2 and node 3, change:
NODE_IDSTORAGE_PATH- the local address inside
CLUSTER_NODES
Start the Cluster
sudo systemctl daemon-reload
sudo systemctl enable prkdb
sudo systemctl start prkdbVerify the Deployment
Check metrics
On node 1:
curl -H "Authorization: Bearer $PRKDB_BOOTSTRAP_TOKEN" http://127.0.0.1:9091/metrics | grep prkdb_upOn node 2:
curl -H "Authorization: Bearer $PRKDB_BOOTSTRAP_TOKEN" http://127.0.0.1:9092/metrics | grep prkdb_upCheck the gRPC API
export PRKDB_CREDENTIAL=change-me # what clients send
prkdb --server http://127.0.0.1:8080 collection listCheck schema registry persistence
export PRKDB_CREDENTIAL=change-me # what clients send
prkdb schema list --server http://127.0.0.1:8080Backup and Restore
prkdb-cli backup operates offline, directly on a data directory — it does not talk to a running server. Point it at a stopped node, or at a copy of the directory.
prkdb-cli --database /var/lib/prkdb backup --output /backups/prkdb-$(date +%F).binThis writes two files: the archive, and a .manifest sidecar recording the archive's length, SHA-256, entry count, and format version.
prkdb-cli restore --input /backups/prkdb-2026-08-09.bin --data-dir /var/lib/prkdb-restoredrestore verifies the archive against its manifest before writing anything, so a corrupt archive fails without leaving a half-populated target. Keep the two files together; a missing manifest downgrades to a warning so older archives still restore, but then nothing is checked.
--force— restore into a non-empty directory. Without it, restore refuses rather than merging two databases.--skip-verify— restore despite a manifest mismatch. This is for salvaging what is readable from a damaged archive, not for silencing the check. A mismatch means the archive is not the one that was backed up.
Verify the backup, not just the exit code
A backup nobody has restored is not a backup. Restore into a scratch directory on a schedule and check a known key:
prkdb-cli restore --input "$ARCHIVE" --data-dir /tmp/verify-$$ && rm -rf /tmp/verify-$$Scheduling
Scheduling belongs to the operator, not to the database — PrkDB deliberately ships no internal scheduler, so backups follow the same operational controls as everything else you run (alerting on failure, retention, offsite copies).
A systemd timer, for a node whose data directory is /var/lib/prkdb:
# /etc/systemd/system/prkdb-backup.service
[Unit]
Description=PrkDB backup
# Back up a stopped node or a snapshot of its directory. Backing up a directory that is
# being written to captures an inconsistent point in time.
[Service]
Type=oneshot
ExecStart=/usr/local/bin/prkdb-cli --database /var/lib/prkdb \
backup --output /backups/prkdb-%%i.bin# /etc/systemd/system/prkdb-backup.timer
[Unit]
Description=Nightly PrkDB backup
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.targetThe cron equivalent, with retention:
# Nightly at 02:30; keep 14 days.
30 2 * * * prkdb-cli --database /var/lib/prkdb backup \
--output /backups/prkdb-$(date +\%%F).bin >> /var/log/prkdb-backup.log 2>&1
15 3 * * * find /backups -name 'prkdb-*.bin*' -mtime +14 -deleteNote the * in the retention glob: it removes each archive's .manifest alongside it. Deleting archives while leaving manifests behind accumulates files that describe nothing.
Operational Notes
CLUSTER_NODESshould contain every node in the cluster, including the local node.- Smart clients consume the addresses returned by metadata. Do not advertise
0.0.0.0; setPRKDB_ADVERTISED_GRPC_ADDRif clients connect through DNS or a load balancer. - If peer nodes have different bind and public addresses, configure
PRKDB_ADVERTISED_NODE_ADDRSso metadata never falls back to an internal-only socket. PRKDB_BOOTSTRAP_TOKENcreates the first admin principal; the server refuses to start without it unlessPRKDB_ALLOW_ANONYMOUS=1is set. See Security & Operations.PRKDB_ADMIN_TOKENis the deprecated single shared secret, still honoured for one release.- If you expose the HTTP server from
prkdb-cli serve, restrict CORS origins explicitly withPRKDB_CORS_ORIGINS. - WebSocket auth is header-based. Set
PRKDB_WS_TOKENwhen you want bearer-token enforcement for/ws/collections/:name.
Security Checklist
- Run the cluster behind TLS termination or a private network boundary.
- Keep credentials out of shell history and process listings where possible. Only the SHA-256 of a credential is stored, and audit records never contain either.
- Persist
STORAGE_PATHon durable local disks. - Scrape metrics from the node-local metrics bind address instead of exposing it publicly.