diff --git a/Dockerfile b/Dockerfile index 9f1423c..95e0386 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,29 +1,49 @@ -# syntax=docker/dockerfile:1 -FROM python:3.12-slim +# syntax=docker/dockerfile:1.7 + +# ============================================================================ +# Build stage: install Python dependencies into a dedicated prefix +# ============================================================================ +FROM python:3.11-slim-bookworm AS builder ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ - PIP_NO_CACHE_DIR=1 + PIP_NO_CACHE_DIR=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 -# Non-root user (uid 1000 to match the run script) -RUN useradd -m -u 1000 appuser +# Install runtime deps into /install. --target keeps everything in one tree +# so we can copy it cleanly to a distroless image without venv/shebang issues. +RUN pip install --target=/install \ + fastapi==0.115.* \ + "uvicorn[standard]==0.30.*" \ + redis==5.0.* -WORKDIR /home/appuser/app +# Pre-compile bytecode for faster cold start under a read-only rootfs +RUN python -m compileall -q /install -# (Optional) tini for proper signal handling -RUN apt-get update && apt-get install -y --no-install-recommends tini \ - && rm -rf /var/lib/apt/lists/* +# ============================================================================ +# Runtime stage: distroless Python 3.11, non-root, no shell +# ============================================================================ +# gcr.io/distroless/python3-debian12 ships Python 3.11 with NO shell, NO +# package manager, and NO setuid binaries. The :nonroot tag pins USER to +# uid 65532 / gid 65532. +FROM gcr.io/distroless/python3-debian12:nonroot -# Runtime deps -RUN pip install --no-cache-dir fastapi==0.115.* uvicorn[standard]==0.30.* redis==5.0.* +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONPATH=/app/lib -# Copy your app -COPY --chown=appuser:appuser app.py . -COPY --chown=appuser:appuser favicon.ico . +WORKDIR /app -USER appuser +# Site-packages first (rarely changes → maximises layer cache) +COPY --from=builder --chown=nonroot:nonroot /install /app/lib + +# Application source (changes most often → its own layer) +COPY --chown=nonroot:nonroot app.py favicon.ico /app/ + +USER nonroot EXPOSE 8000 -ENTRYPOINT ["/usr/bin/tini","--"] -CMD ["uvicorn","app:app","--host","0.0.0.0","--port","8000"] - +# Module form (-m uvicorn) sidesteps broken shebangs in /app/lib/bin/uvicorn +# (those were generated against the builder's Python path, not distroless's). +ENTRYPOINT ["/usr/bin/python3", "-m", "uvicorn"] +CMD ["app:app", "--host", "0.0.0.0", "--port", "8000"]