# Production backend image: API, both worker roles, and the migration/admin CLI. # # One image, several commands (see docker/entrypoint.sh) — matches SPEC §6.3's # WORKER_ROLE split. Build context is the REPO ROOT, because the image needs # backend/ *and* plugins/ (the plugins are separate distributions that the # loader discovers through the `webmail.plugins` entry-point group, SPEC §11). # # docker build -f docker/Dockerfile.backend -t webmail/backend . # ---------- builder: resolve wheels into a self-contained venv ---------------- FROM python:3.11-slim AS builder ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \ PIP_NO_CACHE_DIR=1 \ PYTHONDONTWRITEBYTECODE=1 # Only needed if a pinned dep has no manylinux wheel for this platform; the # whole stage is discarded, so it costs nothing in the runtime image. RUN apt-get update \ && apt-get install -y --no-install-recommends build-essential \ && rm -rf /var/lib/apt/lists/* RUN python -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" # Backend first: its dependency set is the slowest layer and changes least often. COPY backend/pyproject.toml /src/backend/pyproject.toml COPY backend/app /src/backend/app COPY backend/workers /src/backend/workers RUN pip install /src/backend # Plugins are ordinary distributions installed on top (each owns its own deps). COPY plugins /src/plugins RUN pip install \ /src/plugins/webmail_plugin_calendar \ /src/plugins/webmail_plugin_contacts \ /src/plugins/webmail_plugin_labels \ /src/plugins/webmail_plugin_filters \ /src/plugins/webmail_plugin_antispam # ---------- runtime ----------------------------------------------------------- FROM python:3.11-slim ARG APP_VERSION=dev ARG GIT_SHA=unknown LABEL org.opencontainers.image.title="webmail-backend" \ org.opencontainers.image.version="${APP_VERSION}" \ org.opencontainers.image.revision="${GIT_SHA}" ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PATH="/opt/venv/bin:$PATH" \ APP_VERSION="${APP_VERSION}" \ GIT_SHA="${GIT_SHA}" # tini reaps zombies and forwards SIGTERM, which the workers need for the <10 s # graceful lease release in workers/run.py. RUN apt-get update \ && apt-get install -y --no-install-recommends tini \ && rm -rf /var/lib/apt/lists/* \ && useradd --system --uid 10001 --create-home --home-dir /home/webmail webmail COPY --from=builder /opt/venv /opt/venv WORKDIR /app # Alembic's core tree lives outside the installed package, so it is copied in. COPY backend/alembic.ini ./alembic.ini COPY backend/migrations ./migrations COPY backend/scripts ./scripts COPY docker/entrypoint.sh /usr/local/bin/entrypoint RUN chmod +x /usr/local/bin/entrypoint # Blob store mount point (BLOB_ROOT). Ownership is set by the compose `init` # service, which runs before anything else touches the bind mount. RUN mkdir -p /data/blobs && chown webmail:webmail /data/blobs VOLUME ["/data/blobs"] USER webmail ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint"] CMD ["api"]