# Multi-stage Dockerfile for SousChef UI - Production Ready
# Optimised for security, robustness, and Docker registry publishing
ARG PYTHON_VERSION=3.13.1
ARG POETRY_VERSION=1.8.3
# ============================================================================
# Base Stage - Common configuration for all stages
# ============================================================================
FROM python:${PYTHON_VERSION}-slim AS base
ARG PYTHON_VERSION
# Metadata for Docker registry and CI/CD
LABEL org.opencontainers.image.title="SousChef - MCP AI Chef to Ansible Converter" \
org.opencontainers.image.description="AI-powered Model Context Protocol server and web UI for converting Chef cookbooks to Ansible playbooks" \
org.opencontainers.image.authors="SousChef Contributors" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.vendor="SousChef Project" \
org.opencontainers.image.url="https://github.com/kpeacocke/souschef" \
org.opencontainers.image.documentation="https://kpeacocke.github.io/souschef/" \
org.opencontainers.image.source="https://github.com/kpeacocke/souschef" \
org.opencontainers.image.base.name="python:${PYTHON_VERSION}-slim"
# Set environment variables for Python and Streamlit
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
PYTHONPATH=/app \
STREAMLIT_SERVER_HEADLESS=true \
STREAMLIT_BROWSER_GATHER_USAGE_STATS=false \
STREAMLIT_SERVER_ENABLE_CORS=true \
STREAMLIT_SERVER_ENABLE_XSRF_PROTECTION=true \
STREAMLIT_SERVER_ENABLE_STATIC_SERVING=false \
STREAMLIT_LOGGER_LEVEL=INFO \
STREAMLIT_SERVER_LOGGER_LEVEL=INFO \
STREAMLIT_SERVER_MAX_UPLOAD_SIZE=200
# Install security updates and minimal system dependencies
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
git \
&& apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* \
&& groupadd -r app --gid=1001 && \
useradd -r -g app --uid=1001 --create-home --shell /sbin/nologin app && \
mkdir -p /app && \
chown -R app:app /app
# Set work directory
WORKDIR /app
# ============================================================================
# Builder Stage - Install dependencies in isolated layer
# ============================================================================
FROM base AS builder
ARG POETRY_VERSION
# Install build dependencies needed for compilation
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
python3-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Copy dependency files first (for better layer caching)
COPY pyproject.toml poetry.lock ./
# Install Poetry with pinned version for reproducibility
RUN pip install --no-cache-dir --require-hashes \
poetry=="$POETRY_VERSION" || \
pip install --no-cache-dir poetry=="$POETRY_VERSION"
# Configure poetry to not create virtual environment (install globally)
RUN poetry config virtualenvs.create false
# Install production dependencies with UI extras
# Use --no-interaction for automated environments
RUN poetry install \
--only=main \
--extras=ui \
--extras=ai \
--no-dev \
--no-interaction \
--no-root && \
poetry cache clear pypi --all || true
# ============================================================================
# Runtime Stage - Minimal production image
# ============================================================================
FROM base AS production
# Copy installed Python packages from builder (more efficient than copying site-packages)
COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
# Copy application code (keep root-owned for security)
COPY souschef/ ./souschef/
# Copy Streamlit configuration
COPY .streamlit/ ./.streamlit/
# Create application directories with restricted permissions
RUN mkdir -p /app/.streamlit && \
mkdir -p /app/.cache && \
mkdir -p /app/.tmp && \
chmod 755 /app && \
chmod 755 /app/souschef && \
chmod 755 /app/.streamlit && \
chmod 755 /app/.cache && \
chmod 755 /app/.tmp && \
find /app -type f -name '*.py' -exec chmod 644 {} \; && \
chown -R app:app /app/.streamlit /app/.cache /app/.tmp
# Switch to non-root user for security
USER app
# Expose Streamlit port
EXPOSE 9999
# Health check - robust implementation with timeout
HEALTHCHECK \
--interval=30s \
--timeout=10s \
--start-period=5s \
--retries=3 \
CMD python -m souschef.ui.health_check || exit 1
# Use ENTRYPOINT for proper signal handling
# This ensures Ctrl+C and container stop signals work correctly
ENTRYPOINT ["python", "-m", "streamlit", "run"]
# CMD provides the default arguments
CMD ["souschef/ui/app.py", \
"--server.address", "0.0.0.0", \
"--server.port", "9999", \
"--client.showErrorDetails", "true", \
"--logger.level", "info", \
"--server.headless", "true", \
"--server.runOnSave", "false", \
"--client.toolbarMode", "auto"]