Files
quicproquo/docs/src/getting-started/docker.md
Christian Nennemann f334ed3d43 feat: add post-quantum hybrid KEM + SQLCipher persistence
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>
2026-02-22 08:07:48 +01:00

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:

  1. Base image: rust:bookworm (Debian Bookworm with the Rust toolchain pre-installed).
  2. Install capnproto: Required by quicnprotochat-proto/build.rs to compile .capnp schemas at build time.
  3. Copy manifests first: Cargo.toml and Cargo.lock are copied before source code. Dummy main.rs / lib.rs stubs are created so that cargo build can 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.
  4. Copy schemas: The schemas/ directory is copied before the dependency build because quicnprotochat-proto/build.rs requires the .capnp files during compilation.
  5. Copy real source and build: After the dependency cache layer, real source files are copied in and cargo build --release is 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 capnp compiler, 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 nobody for defense in depth.
  • Default port: The Dockerfile defaults to port 7000 via QUICNPROTOCHAT_LISTEN, but the docker-compose.yml overrides this to 7000 for consistency with the development workflow.

Note

: The EXPOSE 7000 directive in the Dockerfile and the QUICNPROTOCHAT_LISTEN=0.0.0.0:7000 override in docker-compose.yml mean the effective listen port is 7000 when using Compose. If you run the Docker image directly without Compose, the server will listen on 7000 by 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