# syntax=docker/dockerfile:1 # Both stages use the same python:3.12-slim image, pinned by digest so a build # is reproducible. Refresh the digest deliberately (docker pull, then # docker inspect --format '{{index .RepoDigests 0}}' python:3.12-slim). ARG PYTHON_IMAGE=python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 ARG UV_IMAGE=ghcr.io/astral-sh/uv:0.12.5@sha256:e85be844203885286c60ffad8a858d48afb6c5a5c237ca0e67f12e74b8f174b1 FROM ${UV_IMAGE} AS uv # --------------------------------------------------------------------------- # Builder: resolve the locked, non-dev dependencies and install the project # (non-editable) into /app/.venv. Nothing from this stage except the venv is # copied into the runtime image. # --------------------------------------------------------------------------- FROM ${PYTHON_IMAGE} AS builder COPY --from=uv /uv /usr/local/bin/uv ENV UV_COMPILE_BYTECODE=1 \ UV_LINK_MODE=copy \ UV_PYTHON_DOWNLOADS=never \ UV_PROJECT_ENVIRONMENT=/app/.venv WORKDIR /app COPY pyproject.toml uv.lock README.md LICENSE ./ COPY src ./src RUN uv sync --locked --no-dev --no-editable # --------------------------------------------------------------------------- # Runtime: slim image, only the venv, non-root user. # --------------------------------------------------------------------------- FROM ${PYTHON_IMAGE} ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PATH=/app/.venv/bin:$PATH # Non-root runtime user (uid 1000). DATA_DIR holds the token store and the # audit log; mount a persistent volume there. RUN useradd --create-home --uid 1000 --shell /usr/sbin/nologin aegis \ && mkdir -p /var/lib/aegis-mcp \ && chown aegis:aegis /var/lib/aegis-mcp COPY --from=builder --chown=root:root /app/.venv /app/.venv # Binding 0.0.0.0 is required for the published port to work inside a # container. It is safe by default: the server refuses to start (exit code 2) # on a non-loopback bind unless authentication is configured (MCP_BEARER_TOKEN # or OAUTH_ENABLED). Still publish the port only on 127.0.0.1 or behind a # reverse proxy (see docker-compose.yml). ENV DATA_DIR=/var/lib/aegis-mcp \ MCP_TRANSPORT=http \ MCP_HOST=0.0.0.0 \ MCP_PORT=8080 USER aegis WORKDIR /home/aegis EXPOSE 8080 # Python stdlib only (the slim image has no curl). Probes loopback on MCP_PORT. HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \ CMD ["python", "-c", "import os, urllib.request; urllib.request.urlopen('http://127.0.0.1:%s/health' % os.environ.get('MCP_PORT', '8080'), timeout=5)"] ENTRYPOINT ["aegis-gitea-mcp"] CMD ["serve"]