# iperf3-enabled image variant. Same prebuilt, CAP_NET_RAW-stamped pingularity # binary as the default Dockerfile, but the final stage is debian:13-slim + iperf3 # so the opt-in iperf3 speedtest engine has a binary to exec. The default image # is distroless/static-debian13 with NO package manager, so iperf3 can never be # present there; this variant exists only for users who select the iperf3 engine. # See Dockerfile for the default (lean, iperf-free) image. # # The base must ship iperf3 >= 3.17: IperfVersion in internal/speedtest warns on # every older build (the 3.17 authentication-padding change), so a base that apt- # installs less than that makes the image flag its own binary. trixie carries 3.18; # bookworm's 3.12 is why the base can never fall back a Debian generation. # # Multi-arch works exactly like the default image: goreleaser (dockers_v2) stages # the per-platform binaries - linux/amd64/pingularity, linux/arm64/pingularity - # in the build context and buildx selects the target-arch one via $TARGETPLATFORM. # The difference here is the final stage runs apt for the TARGET arch (under QEMU # when cross-building; the release workflow already sets up buildx + QEMU). setcap # stays on $BUILDPLATFORM so it still runs natively without emulation. # # Both FROM lines are pinned by digest (alongside the tag) so Dependabot's docker # ecosystem can bump them; the debian:13-slim digest is kept identical to the one # in the default Dockerfile so the two bases stay in lock-step. # --- stamp CAP_NET_RAW onto the binary (runs natively on the build host) --- FROM --platform=$BUILDPLATFORM debian:13-slim@sha256:3a39a0592364683e6bab97937b72cad5a8fa6dcbbee90edb3bb48c7f8e94f258 AS setcap RUN apt-get update \ && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* # TARGETPLATFORM (set by buildx per target arch) selects the matching prebuilt # binary; this stage stays pinned to $BUILDPLATFORM above so apt + setcap run # natively without QEMU. ARG TARGETPLATFORM COPY $TARGETPLATFORM/pingularity /pingularity # +ep: effective+permitted, so the cap is raised automatically on exec even for a # non-root user. `mkdir /data` seeds the data dir the final stage chowns. BuildKit # preserves the security.capability xattr across the COPY into the final image # (same guarantee the default Dockerfile relies on), so the final stage needs no # libcap2-bin of its own. # The .pingularity-image-dir marker is a volume-lineage HEURISTIC (not a proof # of volume type): Docker's copy-up carries it into a fresh named volume, so the # daemon's container carve-out (store.go) can tell content that came from OUR # image from an empty PVC or a plain bind-mounted host directory, which never # carry it. A bind mount restored FROM a marked volume would carry it too - an # accepted edge, since the content is genuinely ours. RUN setcap cap_net_raw+ep /pingularity && mkdir -p /data && touch /data/.pingularity-image-dir # --- final image: debian-slim carrying iperf3 + the capped binary --- FROM debian:13-slim@sha256:3a39a0592364683e6bab97937b72cad5a8fa6dcbbee90edb3bb48c7f8e94f258 # Static attribution; version/revision are stamped per build by goreleaser # (dockers_v2 labels/annotations in .goreleaser.yaml), not hardcoded here. LABEL org.opencontainers.image.title="pingularity" \ org.opencontainers.image.description="Single-binary internet-connectivity monitor (iperf3-enabled image variant)." \ org.opencontainers.image.source="https://github.com/pingular/pingularity" \ org.opencontainers.image.licenses="MIT" # iperf3 gives the opt-in engine a binary to exec (pingularity looks it up on PATH # via exec.LookPath; the debian package installs /usr/bin/iperf3, pulling libiperf0 # as a dependency). ca-certificates + tzdata are bundled by the distroless/static # base of the default image, so install them here too: without ca-certificates the # static Go binary's TLS calls (Ookla, the update feed) fail x509 verification, and # tzdata keeps time-zone handling identical to the default image. RUN apt-get update \ && apt-get install -y --no-install-recommends iperf3 ca-certificates tzdata \ && rm -rf /var/lib/apt/lists/* # debian has no distroless `nonroot` account, so recreate the SAME uid/gid (65532) # the default image runs as. This keeps the volume-ownership contract identical: a # freshly created named volume inherits 65532 and the unprivileged process can # write its database + pingularity.key. Creating the account (not just USER 65532) # also restores the /etc/passwd entry distroless `nonroot` provides, so uid->name # lookups resolve. RUN groupadd --gid 65532 pingularity \ && useradd --uid 65532 --gid 65532 --no-create-home --home-dir /var/lib/pingularity --shell /usr/sbin/nologin pingularity COPY --from=setcap /pingularity /pingularity # The licences the binary is distributed under. Every other channel carries these # - the archives ship them beside the binary (.goreleaser.yaml archives.files) and # the deb/rpm install them under /usr/share/doc/pingularity - but an image is a # binary distribution too, and the statically-linked binary carries BSD-3 code # (golang.org/x, speedtest-go, the modernc/musl-derived libc) plus an embedded # OFL font, all of which ask for their notice to travel with it. goreleaser stages # only the built binaries into the docker context, so both files are named in that # builder's extra_files; without them this COPY fails the build rather than # silently shipping an image with no notices. COPY LICENSE THIRD-PARTY-NOTICES.md /usr/share/doc/pingularity/ # The data dir must exist owned by 65532 so a freshly created named volume inherits # that ownership and the unprivileged process can write to it. # --chmod=0700 to match the mode pingularity gives a data directory it creates # itself; see the same COPY in Dockerfile. Without it the destination directory is # 0755 (COPY does not carry the source mode across) and the daemon spends every # start warning that its own data directory is readable by others. COPY --from=setcap --chown=65532:65532 --chmod=0700 /data /var/lib/pingularity # Belt and braces: some BuildKit versions apply COPY --chmod to the files but # not the directory itself (observed: buildx honors it, ubuntu-latest's plain # docker build does not), and this image HAS a shell to correct it. The # distroless default image cannot RUN anything, so it seeds the directory one # level down and copies it as an entry instead - see the COPY in Dockerfile. # The daemon also tightens its own container data dir at boot either way # (store.go's container carve-out). RUN chmod 0700 /var/lib/pingularity EXPOSE 9000 VOLUME /var/lib/pingularity # Start in the writable data dir rather than / (unwritable for uid 65532), the # same guarantee the distroless base gives the default image via /home/nonroot: # anything that resolves a relative path lands somewhere the process can write. WORKDIR /var/lib/pingularity USER pingularity # Same healthcheck as the default image, exec form for parity (this image has a # shell, but the contract is one probe in both variants). The subcommand probes # http://127.0.0.1:9000/healthz; operators who change -listen must override this # healthcheck (or disable it), or the container reports unhealthy while the # daemon is fine. HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \ CMD ["/pingularity", "healthz"] # Pin the database onto the volume, exactly as the default image does: without this # the path is chosen from the effective uid and a --user override would fall back to # a temp dir inside the writable layer, leaving the mounted volume empty so updates # silently discard all history plus pingularity.key. The key lives beside the # database, so pinning -db pins both. ENTRYPOINT ["/pingularity", "run", "-db", "/var/lib/pingularity/pingularity.db"]