Building

A chat server in fifteen lines of config

cmp is an open-source encrypted messaging experiment in Rust. Its relay runs on Clusterbase, and the whole deployment is one small file.

cmp is an experiment in end-to-end encrypted messaging: a Rust terminal client and a store-and-forward relay. Clients establish sessions with X3DH and encrypt every message with a Double Ratchet. The relay never receives plaintext — it stores and routes opaque ciphertext, and it never even depends on the crypto crate.

Fair warning: the crypto is homegrown and unaudited, so don't trust cmp with anything sensitive. The threat model covers what the relay can still see. This post is about the other half — getting that relay onto the internet.

What the relay needs

The server side of cmp is ordinary in the best way. It compiles to a single Linux binary. It keeps its state in one SQLite file. It reads four environment variables. It listens on one port and answers GET /health.

Most Rust and Go services reduce to exactly this shape. Which means deploying one should be about this hard:

name = "cmp"
mode = "binary"

[binary]
path = "./target/x86_64-unknown-linux-gnu/release/server"
runtime = "debian"

[service]
internal_port = 3000
always_on = true

[resources]
vcpu = 1
memory_mb = 256

That is the entire deployment manifest. It lives at the root of the repo, committed next to the code, with no credentials in it.

Shipping it

Two commands take it from source to a public endpoint:

cross build --release --target x86_64-unknown-linux-gnu -p server
ccp compute deploy

ccp reads cluster.toml, uploads the binary, and brings the service up. There is no Dockerfile in the repository, and there never was one.

That absence is worth dwelling on, because it is where most of the time goes in the usual Rust deployment loop. Packaging a Rust service with Docker means a multi-stage build, layer-cache tricks to keep cargo build from starting cold, an image that carries a whole userland to ship one binary, and a push and pull through a registry before anything runs. Every iteration pays that tax again. Here the compiler's incremental cache stays warm in your local target/ directory, and the only thing that travels is the binary itself. Redeploying cmp after a code change is one compile and one upload — the loop is fast enough that you stop thinking about it, which is the whole point.

Clusterbase terminates TLS, keeps the service running, and gives it an address. The relay now lives at wss://cmp.clusterbase.dev/ws — which is the client's default, so this works out of the box:

cmp --user alice

The first connection registers the username and its identity key. After that you get a keyboard-driven client: conversations on the left, Ctrl+N to start one, F2 to compare safety numbers, F1 for everything else.

Why this is the point

A side project dies at the deployment step more often than at the hard part. cmp's hard part was the ratchet. Its deployment was fifteen lines of TOML and two commands, and that gap is the argument: if your service compiles to a binary, putting it on the internet should not be a second project.

The code, the protocol, and the self-hosting guide are all in the repository. If you would rather run your own relay, cmp --user alice --server ws://... points the client anywhere you like.