Files
AHDX/Dockerfile
T
steffen 14f8081525 Harden runtime image: distroless/cc-debian12 instead of python:3.12-slim
Three-stage build. A python:3.12-slim-bookworm stage installs Flask and
stages the interpreter's link libraries; the runtime is
gcr.io/distroless/cc-debian12 with /usr/local copied over, so we ship a
current Python without the shell, apt, perl and tar that make up most of a
slim image's post-exploitation toolkit.

Bookworm on the build side so glibc matches the debian12 runtime. We
deliberately do not use gcr.io/distroless/python3-debian12 — its pinned
interpreter carries a larger CVE count than the slim image it would replace.

The container now runs as nonroot (65532), so /data is created and chowned
in the build stage: registry.db, databases/, uploads/ and inbox/ are written
there at startup and there is no shell left to fix ownership at runtime.
CMD becomes an exec-form ENTRYPOINT for the same reason.

Note for deploys: docker-compose.yml bind-mounts ./data:/data, and a
host-owned directory will need to be writable by uid 65532.
2026-08-26 16:12:19 +00:00

49 lines
2.0 KiB
Docker

# ============================================
# STAGE 1: Build the Python deps on bookworm-slim
# ============================================
# Bookworm so glibc matches the distroless/debian12 runtime below. Flask is pure
# Python, so no apt build tools are needed here.
FROM python:3.12-slim-bookworm AS pybuild
WORKDIR /app
# Install deps first so this layer caches when only the code changes.
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Stage the shared libs the Python interpreter links against into an arch-neutral
# dir (the glob matches whatever multiarch triplet — x86_64 in CI, aarch64 on an
# arm dev box), plus the writable data dir owned by the distroless nonroot uid.
RUN set -eux; \
mkdir -p /rootfs/pylib /rootfs/data; \
cp -aL /usr/lib/*-linux-gnu/*.so* /rootfs/pylib/ 2>/dev/null || true; \
chown -R 65532:65532 /rootfs/data
# ============================================
# STAGE 2: Distroless runtime (no shell / apt / perl / tar; runs as nonroot)
# ============================================
# distroless/cc carries glibc + libgcc + libstdc++; we bring our own Python 3.12
# and its link libs. Note we do NOT use distroless/python3-debian12 — it ships a
# far larger CVE surface than the slim image it would replace; copying
# /usr/local out of the pybuild stage gives us a current interpreter instead.
FROM gcr.io/distroless/cc-debian12
WORKDIR /app
COPY --from=pybuild /usr/local /usr/local
COPY --from=pybuild /rootfs/pylib /opt/pylib
# registry.db, databases/, uploads/ and inbox/ are all created under /data at
# startup. Distroless has no shell, so ownership has to be set at COPY time.
COPY --from=pybuild --chown=65532:65532 /rootfs/data /data
ENV LD_LIBRARY_PATH=/usr/local/lib:/opt/pylib \
PATH=/usr/local/bin:/usr/bin:/bin \
AHDX_DATA=/data
COPY . .
# All state lives here, mounted as a volume from the host.
VOLUME ["/data"]
EXPOSE 8080
USER nonroot
# Exec form — distroless has no shell to interpret a string CMD.
ENTRYPOINT ["python3", "app.py"]