Feature 1 — Post-Quantum Hybrid KEM (X25519 + ML-KEM-768): - Create hybrid_kem.rs with keygen, encrypt, decrypt + 11 unit tests - Wire format: version(1) | x25519_eph_pk(32) | mlkem_ct(1088) | nonce(12) | ct - Add uploadHybridKey/fetchHybridKey RPCs to node.capnp schema - Server: hybrid key storage in FileBackedStore + RPC handlers - Client: hybrid keypair in StoredState, auto-wrap/unwrap in send/recv/invite/join - demo-group runs full hybrid PQ envelope round-trip Feature 2 — SQLCipher Persistence: - Extract Store trait from FileBackedStore API - Create SqlStore (rusqlite + bundled-sqlcipher) with encrypted-at-rest SQLite - Schema: key_packages, deliveries, hybrid_keys tables with indexes - Server CLI: --store-backend=sql, --db-path, --db-key flags - 5 unit tests for SqlStore (FIFO, round-trip, upsert, channel isolation) Also includes: client lib.rs refactor, auth config, TOML config file support, mdBook documentation, and various cleanups by user. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6.2 KiB
Docker Deployment
quicnprotochat includes a multi-stage Dockerfile and a Docker Compose configuration for building and running the server in containers.
Quick start
docker compose up
This builds the server image (if not already built) and starts a single server service listening on port 7000. The server will generate a self-signed TLS certificate on first launch and begin accepting QUIC connections.
To rebuild after code changes:
docker compose up --build
To run in the background:
docker compose up -d
Docker Compose configuration
The docker-compose.yml at the repository root defines a single service:
services:
server:
build:
context: .
dockerfile: docker/Dockerfile
ports:
- "7000:7000"
environment:
RUST_LOG: "info"
QUICNPROTOCHAT_LISTEN: "0.0.0.0:7000"
healthcheck:
test: ["CMD", "bash", "-c", "echo '' > /dev/tcp/localhost/7000"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
restart: unless-stopped
Port mapping
The container exposes port 7000 (QUIC/UDP). The ports directive maps host port 7000 to the container's 7000. Note that QUIC uses UDP, so ensure your firewall allows UDP traffic on this port.
Health check
The health check uses a TCP connection probe (/dev/tcp/localhost/7000). While QUIC is a UDP protocol, the TCP probe verifies that the process is running and the port is bound. A QUIC-aware health check (e.g., using the client's ping command) would be more precise but requires the client binary in the runtime image.
Restart policy
restart: unless-stopped ensures the server restarts automatically after crashes but stays stopped if you explicitly docker compose stop or docker compose down.
Multi-stage Docker build
The Dockerfile at docker/Dockerfile uses a two-stage build to produce a minimal runtime image.
Stage 1: Builder (rust:bookworm)
FROM rust:bookworm AS builder
RUN apt-get update \
&& apt-get install -y --no-install-recommends capnproto \
&& rm -rf /var/lib/apt/lists/*
Key steps:
- Base image:
rust:bookworm(Debian Bookworm with the Rust toolchain pre-installed). - Install
capnproto: Required byquicnprotochat-proto/build.rsto compile.capnpschemas at build time. - Copy manifests first:
Cargo.tomlandCargo.lockare copied before source code. Dummymain.rs/lib.rsstubs are created so thatcargo buildcan resolve and cache the dependency graph. This ensures that dependency compilation is cached in a separate Docker layer -- subsequent builds that only change source code skip the dependency compilation step entirely. - Copy schemas: The
schemas/directory is copied before the dependency build becausequicnprotochat-proto/build.rsrequires the.capnpfiles during compilation. - Copy real source and build: After the dependency cache layer, real source files are copied in and
cargo build --releaseis run.
Stage 2: Runtime (debian:bookworm-slim)
FROM debian:bookworm-slim AS runtime
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /build/target/release/quicnprotochat-server /usr/local/bin/quicnprotochat-server
EXPOSE 7000
ENV RUST_LOG=info \
QUICNPROTOCHAT_LISTEN=0.0.0.0:7000
USER nobody
CMD ["quicnprotochat-server"]
Key characteristics:
- Minimal image: No Rust toolchain, no
capnpcompiler, no build artifacts. ca-certificates: Included for future HTTPS calls (e.g., ACME certificate provisioning or key sync endpoints).- Non-root execution: The container runs as
nobodyfor defense in depth. - Default port: The Dockerfile defaults to port
7000viaQUICNPROTOCHAT_LISTEN, but thedocker-compose.ymloverrides this to7000for consistency with the development workflow.
Note
: The
EXPOSE 7000directive in the Dockerfile and theQUICNPROTOCHAT_LISTEN=0.0.0.0:7000override indocker-compose.ymlmean the effective listen port is7000when using Compose. If you run the Docker image directly without Compose, the server will listen on7000by default.
Volume persistence
The server stores its state (TLS certificates, KeyPackages, delivery queues, hybrid keys) in the data directory (default data/). To persist this data across container restarts, mount a volume:
services:
server:
# ... existing config ...
volumes:
- server-data:/data
environment:
QUICNPROTOCHAT_DATA_DIR: "/data"
volumes:
server-data:
Or use a bind mount for easier inspection:
docker compose run \
-v $(pwd)/server-data:/data \
-e QUICNPROTOCHAT_DATA_DIR=/data \
server
Without a volume, all server state (including TLS certificates and message queues) is lost when the container is removed. The server will generate a new self-signed certificate on each fresh start, which means clients will need the new certificate to connect.
Building just the image
To build the Docker image without starting a container:
docker build -t quicnprotochat-server -f docker/Dockerfile .
To run it manually:
docker run -d \
--name quicnprotochat \
-p 7000:7000/udp \
-e QUICNPROTOCHAT_LISTEN=0.0.0.0:7000 \
-e RUST_LOG=info \
quicnprotochat-server
Note the /udp suffix on the port mapping -- QUIC runs over UDP.
Connecting the client to a containerised server
When the server runs in Docker with docker compose up, the client can connect from the host:
# Extract the server's TLS cert from the container
docker compose cp server:/data/server-cert.der ./data/server-cert.der
# Connect
cargo run -p quicnprotochat-client -- ping \
--ca-cert ./data/server-cert.der \
--server-name localhost
If you mounted a volume (e.g., ./server-data:/data), the certificate is directly accessible at ./server-data/server-cert.der.
Next steps
- Running the Server -- server configuration without Docker
- Running the Client -- CLI subcommands
- Demo Walkthrough -- step-by-step messaging scenario