# ============================================================================= # Mnemosyne — production image # ============================================================================= # Multi-stage: # builder installs Python deps and runs `collectstatic` once. # runtime copies only the artifacts the running process needs. # # The same image runs three different processes (Django web, MCP server, # Celery worker) — the compose file picks the command per service. # ============================================================================= # ── Stage 1: builder ──────────────────────────────────────────────────────── FROM python:3.12-slim AS builder # Build deps for psycopg, PyMuPDF, Pillow, cryptography, etc. RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ libpq-dev \ libffi-dev \ libssl-dev \ libjpeg-dev \ zlib1g-dev \ && rm -rf /var/lib/apt/lists/* ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 WORKDIR /build # Install dependencies first (better layer caching). COPY pyproject.toml README.md ./ COPY mnemosyne/ ./mnemosyne/ RUN pip install --upgrade pip \ && pip install . # Bake static files into the image. The env vars below are build-time-only # stubs needed for settings.py to import without real infrastructure — they # never reach the runtime image because this is the builder stage. # Inlined into the RUN command (rather than ENV/ARG) so static analysis # tools (Trivy) don't flag them as baked-in secrets. ENV DJANGO_SETTINGS_MODULE=mnemosyne.settings \ DEBUG=False \ USE_LOCAL_STORAGE=True \ APP_DB_NAME=collectstatic \ APP_DB_USER=collectstatic WORKDIR /build/mnemosyne RUN SECRET_KEY=collectstatic-stub \ APP_DB_PASSWORD=collectstatic-stub \ python manage.py collectstatic --noinput --clear # ── Stage 2: runtime ──────────────────────────────────────────────────────── FROM python:3.12-slim AS runtime # Runtime libs for psycopg + PyMuPDF + Pillow + cryptography. RUN apt-get update && apt-get install -y --no-install-recommends \ libpq5 \ libjpeg62-turbo \ zlib1g \ libssl3 \ ca-certificates \ && rm -rf /var/lib/apt/lists/* ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ DJANGO_SETTINGS_MODULE=mnemosyne.settings \ PATH=/usr/local/bin:$PATH # Copy installed packages from the builder. COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages COPY --from=builder /usr/local/bin /usr/local/bin # Application code + collected statics. WORKDIR /app COPY --from=builder /build/mnemosyne /app COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh # Non-root user for everything that runs in this image. uid:gid 1000:1000 # matches the convention for a single-application container. RUN groupadd --gid 1000 mnemosyne \ && useradd --uid 1000 --gid mnemosyne --home /app --no-create-home --shell /sbin/nologin mnemosyne \ && mkdir -p /app/media /app/logs \ && chown -R mnemosyne:mnemosyne /app USER mnemosyne # The compose file overrides this per service. Default = Django web. EXPOSE 8000 22091 ENTRYPOINT ["entrypoint.sh"] CMD ["web"]