Skip to content

Plan: Dockerfile Hygiene — Audit Custom Builds

Status

State: Active Started: 2026-05-14

Context

Sepia has two custom Dockerfiles that are built locally: caddy and collectd. Additionally, the docs container uses a Dockerfile in /opt/docs/Dockerfile. These need a security and best-practices audit.

The collectd Dockerfile is particularly concerning — it runs as root, installs sudo with passwordless access inside the container, uses un-pinned apt with no --no-install-recommends, keeps build dependencies in the final image, and has no healthcheck.

Shuttle has the same plan as active/dockerfile-hygiene.md.

Inventory

Dockerfile Path Base Image Purpose Risk
caddy /opt/caddy/Dockerfile caddy:2.9-builder + caddy:2.9-alpine Multi-stage build with xcaddy plugins 🟢 Low
collectd /opt/collectd/docker/Dockerfile debian:bookworm Custom collectd with PMT/hddtemp 🔴 High
docs /opt/docs/Dockerfile python:3.12-slim MkDocs documentation server 🟢 Low

Goals

  • [ ] Fix collectd Dockerfile — non-root user, combined RUNs, remove build deps, add healthcheck
  • [ ] Verify caddy Dockerfile is up to standard (looks good — multi-stage, Alpine)
  • [ ] Verify docs Dockerfile is up to standard
  • [ ] Ensure all custom Dockerfiles have HEALTHCHECK instructions

Steps

Step 1: Fix collectd Dockerfile

The current file has multiple issues:

Problems: 1. Runs as root (CMD starts collectd as root, no USER directive) 2. Installs sudo with passwordless access for collectd user (antipattern) 3. Multiple RUN apt install lines not combined (creates unnecessary layers) 4. No --no-install-recommends (pulls unnecessary packages) 5. Build deps (make, g++, cmake, python3-dev, pybind11) are removed but apt cache remains 6. No HEALTHCHECK 7. HDDTEMP_VERSION is not pinned to a commit/checksum 8. PMT source is copied from build context (COPY PMT /pmt) but directory may not exist in repo 9. apt dist-upgrade in a Dockerfile is unusual and non-reproducible (pulls whatever latest) 10. ENV DEBIAN_FRONTEND=noninteractive should not persist in runtime image

Target (rewrite):

FROM debian:bookworm-slim AS builder

RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates \
    wget \
    make \
    g++ \
    python3-dev \
    python3-pybind11 \
    cmake \
    && rm -rf /var/lib/apt/lists/*

# Build hddtemp
ENV HDDTEMP_VERSION=0.3.1
RUN wget -q https://github.com/slowpeek/hddtemp/archive/refs/tags/${HDDTEMP_VERSION}.tar.gz \
    && tar xzf ${HDDTEMP_VERSION}.tar.gz \
    && cd hddtemp-${HDDTEMP_VERSION} \
    && make hddtemp-lt \
    && install hddtemp-lt /usr/sbin/hddtemp \
    && cd / && rm -rf hddtemp-${HDDTEMP_VERSION}*

# Build PMT
COPY PMT /pmt
RUN cmake -S/pmt -B/pmt/build -DPMT_BUILD_RAPL=1 -DPMT_BUILD_BINARY=1 -DPMT_BUILD_PYTHON=1 -DCMAKE_INSTALL_PREFIX=/opt/pmt \
    && make -C/pmt/build -j install \
    && rm -rf /pmt

FROM debian:bookworm-slim
ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y --no-install-recommends \
    collectd \
    libsensors5 \
    liblzo2-2 \
    btrfs-progs \
    libatasmart4 \
    speedtest-cli \
    smartmontools \
    wget \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /usr/sbin/hddtemp /usr/sbin/hddtemp
COPY --from=builder /opt/pmt /opt/pmt

ENV LD_LIBRARY_PATH="/opt/pmt/lib"
ENV PYTHONPATH="/opt/pmt/lib/python3.11/site-packages"
ENV PATH="$PATH:/opt/pmt/bin"

USER nobody
HEALTHCHECK NONE

CMD ["/usr/sbin/collectd", "-f"]

Note: PMT source directory (/opt/collectd/docker/PMT/) must exist in the repo for the build to work.

  • Verification: docker build -t collectd:bookworm /opt/collectd/docker/ && docker run --rm collectd:bookworm collectd -h succeeds, runs as non-root

Step 2: Verify Caddy Dockerfile

Current file is already good: - ✅ Multi-stage build (builder → runtime) - ✅ Minimal base (caddy:2.9-alpine) - ✅ Clean separation of build and runtime

Check if plugins need updating:

github.com/caddy-dns/route53
github.com/mholt/caddy-dynamicdns
github.com/zhangjiayin/caddy-geoip2
github.com/mholt/caddy-l4
github.com/greenpau/caddy-security

Consider adding: - HEALTHCHECK --interval=30s --timeout=10s CMD ["wget", "--spider", "http://localhost:2019/config/"] - Pin caddy builder/runtime base image digests for reproducibility

  • Verification: docker build -t caddy /opt/caddy/ && docker run --rm caddy --version works

Step 3: Verify Docs Dockerfile

Current file: - ✅ Good base (python:3.12-slim) - ✅ Installs only needed packages - ✅ Cleans apt cache in same layer

Consider adding: - HEALTHCHECK --interval=30s --timeout=10s CMD ["curl", "-f", "http://localhost:8000/"] - Pin pip package versions instead of --no-cache-dir latest - Run as non-root user

  • Verification: docker build -t opt-docs /opt/docs/ succeeds

Step 4: Add HEALTHCHECK to All Custom Dockerfiles

Dockerfile Proposed HEALTHCHECK
caddy wget --spider http://localhost:2019/config/
collectd None (no HTTP endpoint) — or pidof collectd
docs curl -f http://localhost:8000/ (already has similar in compose)

Rollback

  • Git revert for any Dockerfile changes
  • docker compose build <service> to rebuild with previous Dockerfile
  • PLANS/active/infrastructure-hardening.md (healthchecks for all services)
  • REFERENCE/services.md

Created: 2026-05-14