# syntax=docker/dockerfile:1.7 # ============================================================================= # Django API image. # # Two stages: wheels are built with a compiler present, the runtime image gets # only the compiled artefacts. The result is small, has no build toolchain to # exploit, and runs as an unprivileged user on a read-only-friendly layout. # ============================================================================= # ----------------------------------------------------------------------------- # Stage 1 - build wheels # ----------------------------------------------------------------------------- FROM python:3.12-slim-bookworm AS builder ENV PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 RUN apt-get update \ && apt-get install --no-install-recommends -y build-essential libpq-dev \ && rm -rf /var/lib/apt/lists/* # The build context is the repository root so the image can also carry the # dataset; paths below are therefore prefixed with the directory name. WORKDIR /build COPY backend/requirements/ requirements/ RUN pip wheel --wheel-dir /wheels -r requirements/base.txt # ----------------------------------------------------------------------------- # Stage 2 - runtime # ----------------------------------------------------------------------------- FROM python:3.12-slim-bookworm AS runtime ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 \ DJANGO_SETTINGS_MODULE=config.settings.prod \ PATH="/home/app/.local/bin:${PATH}" # libpq5 is the only runtime library psycopg needs; curl backs the healthcheck. RUN apt-get update \ && apt-get install --no-install-recommends -y libpq5 curl \ && rm -rf /var/lib/apt/lists/* \ && groupadd --gid 10001 app \ && useradd --uid 10001 --gid app --create-home --shell /usr/sbin/nologin app COPY --from=builder /wheels /wheels COPY backend/requirements/ /tmp/requirements/ RUN pip install --no-index --find-links=/wheels -r /tmp/requirements/base.txt \ && rm -rf /wheels /tmp/requirements WORKDIR /app COPY --chown=app:app backend/ /app/ COPY --chown=app:app data/ /data/ # Static assets are only needed for the admin and the API docs, but baking them # in keeps the container stateless. RUN DJANGO_SECRET_KEY=build-only-not-a-real-secret-000000000000000000000000000000000000 \ DATABASE_URL=postgres://build:build@localhost:5432/build \ DJANGO_SETTINGS_MODULE=config.settings.dev \ python manage.py collectstatic --noinput --clear \ && chmod +x /app/entrypoint.sh USER app EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \ CMD curl -fsS http://localhost:8000/api/v1/health/live/ || exit 1 ENTRYPOINT ["/app/entrypoint.sh"] CMD ["gunicorn", "config.wsgi:application", "--config", "/app/gunicorn.conf.py"]