# Docker Deployment quicproquo includes a multi-stage Dockerfile and a Docker Compose configuration for building and running the server in containers. --- ## Quick start ```bash 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: ```bash docker compose up --build ``` To run in the background: ```bash docker compose up -d ``` --- ## Docker Compose configuration The `docker-compose.yml` at the repository root defines a single service: ```yaml services: server: build: context: . dockerfile: docker/Dockerfile ports: - "7000:7000" environment: RUST_LOG: "info" QPQ_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`) ```dockerfile 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 `quicproquo-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 `quicproquo-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`) ```dockerfile 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/qpq-server /usr/local/bin/qpq-server EXPOSE 7000 ENV RUST_LOG=info \ QPQ_LISTEN=0.0.0.0:7000 USER nobody CMD ["qpq-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 `QPQ_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 `QPQ_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: ```yaml services: server: # ... existing config ... volumes: - server-data:/data environment: QPQ_DATA_DIR: "/data" volumes: server-data: ``` Or use a bind mount for easier inspection: ```bash docker compose run \ -v $(pwd)/server-data:/data \ -e QPQ_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: ```bash docker build -t qpq-server -f docker/Dockerfile . ``` To run it manually: ```bash docker run -d \ --name quicproquo \ -p 7000:7000/udp \ -e QPQ_LISTEN=0.0.0.0:7000 \ -e RUST_LOG=info \ qpq-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: ```bash # 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 quicproquo-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](running-the-server.md) -- server configuration without Docker - [Running the Client](running-the-client.md) -- CLI subcommands - [Demo Walkthrough](demo-walkthrough.md) -- step-by-step messaging scenario