Skip to content

Docker Multi-Node Cluster

The repository includes a three-node Docker Compose harness for exercising Datum cluster membership, placement, live demo jobs, and the operator console without a broker or other external service. Membership uses UDP gossip. Every DCP connection that leaves a process—including peer, CLI, and TUI traffic—uses QUIC with rustls + ring mutual TLS; plaintext DCP TCP remains disabled.

Prerequisites

Install Docker Engine with Compose v2 and OpenSSL 1.1.1 or newer. Run commands from the repository root. The host also needs the datum and datum-tui binaries; build the deliberately lean package set if necessary:

sh
cargo build --release --locked -p datum-agent -p datum-cli -p datum-tui

Do not substitute cargo build --workspace: that pulls unrelated satellite crates and dev tooling into an otherwise standalone harness. The cluster harness does not use Kafka, and Datum's Kafka client is native-only.

Generate development identities

sh
./docker/gen-certs.sh

The script creates two self-signed identities, matching the trust topology proven by the three-node cluster_sessions.rs test:

  • a server certificate whose DNS SAN is exactly localhost;
  • a client certificate trusted by every listener.

The six DER files used by datum-agent and the PEM files used by host tools are written under docker/certs/. They are ignored by git. The CLI, TUI, and agent session client hardcode localhost as the QUIC server name, so the SAN is not interchangeable with a container hostname or numeric address.

OpenSSL is used only by this development certificate script. The DCP TLS stack itself is C-free: peer sessions, CLI, and TUI all use rustls with ring, not aws-lc/CMake.

Start three nodes

sh
docker compose -f docker/docker-compose.yml up --build -d
docker compose -f docker/docker-compose.yml logs -f

The compose network assigns 172.30.0.11 through 172.30.0.13. Static numeric addresses are load-bearing: cluster seed and advertised settings parse directly as SocketAddr, so service names do not work. Each DCP QUIC listener also binds its own static IP because Datum advertises the bound listener address to peers; an unspecified 0.0.0.0 peer endpoint would not be routable between containers.

The first node's 9556/udp is published to the host. Docker forwards that port even though the listener binds the container's static IP.

Verify, submit, and open the TUI

Use the PEM client material for all operator connections. The address must be numeric for CLI parsing; certificate verification still uses localhost and therefore matches the server SAN.

sh
datum \
  --addr 127.0.0.1:9556 \
  --tls-ca docker/certs/ca.pem \
  --tls-cert docker/certs/client.pem \
  --tls-key docker/certs/client-key.pem \
  nodes

datum \
  --addr 127.0.0.1:9556 \
  --tls-ca docker/certs/ca.pem \
  --tls-cert docker/certs/client.pem \
  --tls-key docker/certs/client-key.pem \
  submit --cluster --factory ticker --name demo-1

datum-tui \
  --addr 127.0.0.1:9556 \
  --tls-ca docker/certs/ca.pem \
  --tls-cert docker/certs/client.pem \
  --tls-key docker/certs/client-key.pem

If the tools are not installed, use target/release/datum and target/release/datum-tui. Submit demo-2, demo-3, and further names to populate the cluster view. The node contacted by the host serves its local membership view and routes cluster job requests over its QUIC peer sessions, so one published port provides cluster-wide visibility.

DCP peer sessions use QUIC keepalive, and a slow peer request timeout no longer tears down the session. First-attempt nodes, ps --cluster, submit, drain, and stop operations should therefore remain reliable while the cluster is healthy; when a peer or coordinator does fail, the bounded reconnect loop normally restores the operator view within a few seconds in this harness.

Stop the harness with:

sh
docker compose -f docker/docker-compose.yml down

Ports

AddressProtocolScopeUse
172.30.0.11:25520172.30.0.13:25520UDPCompose networkMembership gossip
172.30.0.11:9556172.30.0.13:9556QUIC/UDPCompose networkmTLS DCP peer sessions
127.0.0.1:9556QUIC/UDPHost to node 1mTLS CLI/TUI entrypoint
9555TCPDisabledPlaintext DCP remains loopback-only by design and is not published

Agent environment

The compose file sets only keys understood by the datum-agent bootstrap parser.

SettingValue or patternWhy it is set
DATUM_AGENT_CLUSTER1Enables the cluster entrypoint
DATUM_AGENT_CLUSTER_NODE_IDdatum-node-1datum-node-3Stable node identity
DATUM_AGENT_CLUSTER_ROLESagent,workerSession discovery and job placement roles
DATUM_AGENT_CLUSTER_BIND_ADDRStatic IP plus 25520Gossip socket bind
DATUM_AGENT_CLUSTER_ADVERTISE_ADDRStatic IP plus 25520Numeric peer-visible gossip endpoint
DATUM_AGENT_CLUSTER_SEEDS172.30.0.11:25520Node 1 seed; the local identity is filtered on node 1
DATUM_AGENT_CLUSTER_GOSSIP_INTERVAL_MS60Proven three-node test cadence
DATUM_AGENT_CLUSTER_PROBE_TIMEOUT_MS15Proven probe timeout
DATUM_AGENT_CLUSTER_DOWNING_TIMEOUT_MS150Proven downing timeout
DATUM_AGENT_CLUSTER_AGENT_ROLEagentSelects members with DCP endpoints
DATUM_AGENT_CLUSTER_REQUEST_TIMEOUT_MS300Bounds peer fan-out
DATUM_AGENT_CLUSTER_RECONNECT_MIN_BACKOFF_MS20Initial session reconnect delay
DATUM_AGENT_CLUSTER_RECONNECT_MAX_BACKOFF_MS100Maximum session reconnect delay
DATUM_AGENT_CLUSTER_DCP_TRANSPORTquicSelects authenticated cross-container sessions
DATUM_AGENT_CLUSTER_DCP_QUIC_SERVER_NAMElocalhostMatches the server certificate SAN
DATUM_AGENT_DCP_TCP0Disables plaintext TCP
DATUM_AGENT_DCP_METRICS_INTERVAL_MS20Keeps live demo metrics responsive
DATUM_AGENT_DCP_QUIC_ADDRStatic IP plus 9556Listener and advertised DCP endpoint
DATUM_AGENT_DCP_QUIC_KEEP_ALIVE_INTERVAL_MS2000 (default)Idle-session liveness probe interval for both QUIC endpoints
DATUM_AGENT_DCP_QUIC_MAX_IDLE_TIMEOUT_MS10000 (default)Bounds dead-peer detection on an inactive QUIC connection
DATUM_AGENT_DCP_QUIC_CERT_DERserver-cert.derListener identity certificate
DATUM_AGENT_DCP_QUIC_KEY_DERserver-key.derListener PKCS#8 key
DATUM_AGENT_DCP_QUIC_CLIENT_CA_DERclient-ca.derClient identity trust anchor
DATUM_AGENT_CLUSTER_DCP_QUIC_SERVER_CA_DERserver-ca.derServer identity trust anchor
DATUM_AGENT_CLUSTER_DCP_QUIC_CLIENT_CERT_DERclient-cert.derPeer session client identity
DATUM_AGENT_CLUSTER_DCP_QUIC_CLIENT_KEY_DERclient-key.derPeer session PKCS#8 key

The timing values mirror every corresponding environment-configurable field in the proven cluster_sessions.rs three-node setup. Fields without bootstrap environment keys retain the daemon defaults.

Why QUIC+mTLS is required

Datum deliberately refuses non-loopback plaintext TCP listeners and rejects plaintext node sessions to non-loopback peers. A Docker bridge therefore cannot use TcpLoopback. QUIC provides the non-loopback transport, while mutual TLS authenticates both the server and the connecting peer or operator. The same identities can be reused across these development containers; use managed, node-specific credentials for a production deployment.

Troubleshooting

Membership never reaches three nodes

Inspect all logs and check that 172.30.0.0/24 does not overlap an existing host route. Gossip is UDP on 25520; DCP peer sessions are QUIC/UDP on 9556. Keep seed, bind, advertise, and DCP listener values numeric. A service name such as datum-node-1:25520 will fail SocketAddr parsing.

QUIC handshake fails

Recreate credentials and containers, then inspect the SAN:

sh
./docker/gen-certs.sh
openssl x509 -in docker/certs/ca.pem -noout -ext subjectAltName
docker compose -f docker/docker-compose.yml up --build -d --force-recreate

The SAN output must contain DNS:localhost. Confirm the mounted DER paths are readable and supply all three --tls-* flags together to operator tools.

Host tools cannot connect

Use 127.0.0.1:9556, confirm node 1 publishes 9556/udp (not TCP), and check the host firewall's UDP rules. docker compose ... ps should show the UDP mapping. Do not try the disabled TCP port.

Dev-server E2E

The harness is authored and locally validated without assuming access to a Docker daemon. The maintainer owns the live three-node E2E on the dev server: confirm all members become Up, all remote sessions show connected, ticker jobs remain running and accumulate throughput on placed nodes, and datum-tui renders the populated cluster. Environment-specific findings from that run should feed back into the harness.

See the repository-local docker/README.md for the compact command reference.