# syntax=docker/dockerfile:1 # check=skip=InvalidDefaultArgInFrom ARG PYTHON_VERSION ARG PYTHON_VERSION_SHORT ARG RUNNER # =========================================== # Stage 1: Builder # =========================================== FROM python:${PYTHON_VERSION}-slim-bookworm AS builder # Re-declare ARGs after FROM to make them available in this stage ARG RUNNER # STFU - No need - Write logs asap ENV DEBIAN_FRONTEND=noninteractive \ PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 # Install build dependencies (needs root) RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ && rm -rf /var/lib/apt/lists/* # Install runner globally (needs root) - Save cache for future RUN --mount=type=cache,target=/root/.cache/pip \ pip install $RUNNER # Set working directory for dependency installation WORKDIR /tmp # Copy dependency files COPY brain/pyproject.toml brain/poetry.lock* brain/uv.lock* Makefile ./ # Install dependencies as root (to avoid permission issues with system packages) RUN --mount=type=cache,target=/root/.cache/pip \ --mount=type=cache,target=/root/.cache/pypoetry \ --mount=type=cache,target=/root/.cache/uv \ if [ "$RUNNER" = "poetry" ]; then \ poetry config virtualenvs.create false && \ poetry install --only main --no-root; \ elif [ "$RUNNER" = "uv" ]; then \ uv pip install --system -r pyproject.toml; \ fi # =========================================== # Stage 2: Testing # =========================================== FROM builder AS test ARG RUNNER RUN --mount=type=cache,target=/root/.cache/pip \ --mount=type=cache,target=/root/.cache/pypoetry \ --mount=type=cache,target=/root/.cache/uv \ if [ "$RUNNER" = "poetry" ]; then \ poetry install --no-root; \ elif [ "$RUNNER" = "uv" ]; then \ uv pip install --system -e .[dev]; \ fi COPY brain/agent/ ./agent/ COPY brain/application/ ./application/ COPY brain/domain/ ./domain/ COPY brain/infrastructure/ ./infrastructure/ COPY brain/tests/ ./tests/ COPY brain/app.py . # =========================================== # Stage 3: Runtime # =========================================== FROM python:${PYTHON_VERSION}-slim-bookworm AS runtime ARG PYTHON_VERSION_SHORT ENV LLM_PROVIDER=deepseek \ MEMORY_STORAGE_DIR=/data/memory \ PYTHONDONTWRITEBYTECODE=1 \ PYTHONPATH=/home/appuser/app \ PYTHONUNBUFFERED=1 # Install runtime dependencies (needs root) RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ && rm -rf /var/lib/apt/lists/* \ && apt-get clean # Create non-root user RUN useradd -m -u 1000 -s /bin/bash appuser # Create data directories (needs root for /data) RUN mkdir -p /data/memory /data/logs \ && chown -R appuser:appuser /data # Switch to non-root user USER appuser # Set working directory (owned by appuser) WORKDIR /home/appuser/app # Copy Python packages from builder stage COPY --from=builder /usr/local/lib/python${PYTHON_VERSION_SHORT}/site-packages /usr/local/lib/python${PYTHON_VERSION_SHORT}/site-packages COPY --from=builder /usr/local/bin /usr/local/bin # Copy application code (already owned by appuser) COPY --chown=appuser:appuser brain/agent/ ./agent/ COPY --chown=appuser:appuser brain/application/ ./application/ COPY --chown=appuser:appuser brain/domain/ ./domain/ COPY --chown=appuser:appuser brain/infrastructure/ ./infrastructure/ COPY --chown=appuser:appuser brain/app.py . # Create volumes for persistent data VOLUME ["/data/memory", "/data/logs"] # Expose port EXPOSE 8000 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD python -c "import requests; requests.get('http://localhost:8000/health', timeout=5).raise_for_status()" || exit 1 # Run the application CMD ["python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]