Proof of concept mechanical port of ircnet/ircd to Rust as part of a bit about C being insecure for network services
1# Build the `leveva` IRC daemon binary.
2#
3# leveva is a workspace member, so the whole workspace is copied in: cargo needs
4# every member manifest to resolve the lockfile, even though `-p leveva` only
5# compiles leveva and its path dependency (leveva-iauth). The build is pure Rust
6# (rustls/ring for TLS, no system OpenSSL), and we cross-compile to a fully
7# static musl target with cargo-zigbuild, so the runtime image needs nothing.
8#
9# The build stage pins itself to the native BUILDPLATFORM (no QEMU emulation);
10# cargo-zigbuild then cross-compiles to whichever musl triple matches the
11# requested TARGETARCH (amd64 -> x86_64, arm64 -> aarch64). Both ARGs are
12# populated automatically by `docker buildx`.
13
14FROM --platform=$BUILDPLATFORM rust:1-bookworm AS build
15WORKDIR /src
16
17# Install zig from ziglang.org rather than apt: Debian's package is far behind,
18# and leveva's cross-build path (cargo-zigbuild) wants a current toolchain.
19# The tarball is a self-contained static binary; just extract and symlink.
20ARG ZIG_VERSION=0.16.0
21RUN apt-get update \
22 && apt-get install -y --no-install-recommends curl xz-utils ca-certificates \
23 && rm -rf /var/lib/apt/lists/* \
24 && case "$(dpkg --print-architecture)" in \
25 amd64) ZIG_ARCH=x86_64 ;; \
26 arm64) ZIG_ARCH=aarch64 ;; \
27 riscv64) ZIG_ARCH=riscv64 ;; \
28 ppc64le) ZIG_ARCH=powerpc64le ;; \
29 *) echo "unsupported arch: $(dpkg --print-architecture)" >&2; exit 1 ;; \
30 esac \
31 && curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}.tar.xz" \
32 | tar -xJ -C /opt \
33 && ln -s "/opt/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}/zig" /usr/local/bin/zig \
34 && cargo install cargo-zigbuild
35
36# Map the Docker TARGETARCH to a Rust musl triple, add the std component for it,
37# then cross-build. The artifact is copied to a stable path (/leveva) so the
38# runtime stage's COPY doesn't need to know the triple.
39ARG TARGETARCH
40
41# Copy the full workspace (path deps + lockfile) and build only leveva.
42COPY . .
43RUN set -eux; \
44 case "$TARGETARCH" in \
45 amd64) RUST_TARGET=x86_64-unknown-linux-musl ;; \
46 arm64) RUST_TARGET=aarch64-unknown-linux-musl ;; \
47 riscv64) RUST_TARGET=riscv64gc-unknown-linux-musl ;; \
48 ppc64le) RUST_TARGET=powerpc64le-unknown-linux-musl ;; \
49 ppc64) RUST_TARGET=powerpc64-unknown-linux-musl ;; \
50 *) echo "unsupported TARGETARCH: $TARGETARCH" >&2; exit 1 ;; \
51 esac; \
52 rustup target add "$RUST_TARGET"; \
53 cargo zigbuild --release --locked --target "$RUST_TARGET" -p leveva --bin leveva; \
54 cp "target/$RUST_TARGET/release/leveva" /leveva
55
56# ---
57
58# A throwaway stage that supplies the two pieces of userland the scratch runtime
59# needs: the CA bundle and the unprivileged service account. Both are pure data
60# (PEM files / passwd entries), so we pin this to the native BUILDPLATFORM — it
61# never has to match the target arch, and the alpine image only needs to exist
62# for the builder. This is what lets the runtime work on ppc64 (big endian),
63# which has no official alpine/debian image of its own.
64FROM --platform=$BUILDPLATFORM alpine:3.24 AS rootfs
65RUN apk add --no-cache ca-certificates \
66 && addgroup -g 10001 leveva \
67 && adduser -S -u 10001 -G leveva -h /var/lib/leveva leveva
68
69# ---
70
71# The binary is a fully static musl build, so the runtime image needs nothing but
72# the binary itself plus the CA bundle and account skeleton from the rootfs stage.
73# scratch has no per-arch base image, so every target platform is satisfied.
74FROM scratch AS runtime
75
76COPY --from=rootfs /etc/ssl/certs /etc/ssl/certs
77COPY --from=rootfs /etc/passwd /etc/passwd
78COPY --from=rootfs /etc/group /etc/group
79COPY --from=rootfs --chown=10001:10001 /var/lib/leveva /var/lib/leveva
80COPY --from=build /leveva /usr/local/bin/leveva
81
82# Config lives in /etc/leveva; runtime state (none today) under /var/lib/leveva.
83WORKDIR /var/lib/leveva
84USER leveva
85
86# 6667 = clients, 6697 = TLS clients, 6067 = server-only (S2S) links.
87EXPOSE 6667 6697 6067
88
89CMD ["/usr/local/bin/leveva"]