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.
This commit is contained in:
+36
-4
@@ -1,16 +1,48 @@
|
|||||||
FROM python:3.12-slim
|
# ============================================
|
||||||
|
# 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
|
WORKDIR /app
|
||||||
|
|
||||||
# Install deps first so this layer caches when only the code changes.
|
# Install deps first so this layer caches when only the code changes.
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
RUN pip install --no-cache-dir -r 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 . .
|
COPY . .
|
||||||
|
|
||||||
# All state lives here, mounted as a volume from the host.
|
# All state lives here, mounted as a volume from the host.
|
||||||
ENV AHDX_DATA=/data
|
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
CMD ["python", "app.py"]
|
USER nonroot
|
||||||
|
# Exec form — distroless has no shell to interpret a string CMD.
|
||||||
|
ENTRYPOINT ["python3", "app.py"]
|
||||||
|
|||||||
Reference in New Issue
Block a user