|
1 | 1 | # syntax=docker/dockerfile:1 |
2 | 2 |
|
3 | | -# ============================================================================= |
4 | | -# Builder stage: compile dependencies |
5 | | -# ============================================================================= |
6 | | -FROM python:3.12-alpine AS builder |
7 | | - |
8 | | -# Install build dependencies for compiling Python packages |
9 | | -RUN apk add --no-cache \ |
10 | | - gcc \ |
11 | | - musl-dev \ |
12 | | - libffi-dev \ |
13 | | - postgresql-dev \ |
14 | | - python3-dev \ |
15 | | - zlib-dev \ |
16 | | - jpeg-dev |
17 | | - |
18 | | -# Install poetry system-wide (not in venv, so it won't be copied to production) |
19 | | -RUN pip install --no-cache-dir poetry |
20 | | - |
21 | | -# Create clean venv with upgraded pip (--upgrade-deps handles CVE-2025-8869) |
22 | | -RUN python -m venv /venv --upgrade-deps |
23 | | - |
24 | | -WORKDIR /build |
| 3 | +# ============================================================================ |
| 4 | +# Build stage: Install dependencies using Poetry |
| 5 | +# ============================================================================ |
| 6 | +FROM python:3.12-slim AS builder |
| 7 | + |
| 8 | +# Install build dependencies required for compiling Python packages |
| 9 | +RUN apt-get update && apt-get install -y --no-install-recommends \ |
| 10 | + build-essential \ |
| 11 | + libpq-dev \ |
| 12 | + curl \ |
| 13 | + && rm -rf /var/lib/apt/lists/* |
| 14 | + |
| 15 | +# Install Poetry |
| 16 | +ENV POETRY_VERSION=2.3.0 \ |
| 17 | + POETRY_HOME="/opt/poetry" \ |
| 18 | + POETRY_NO_INTERACTION=1 \ |
| 19 | + POETRY_VIRTUALENVS_IN_PROJECT=1 \ |
| 20 | + POETRY_VIRTUALENVS_CREATE=1 \ |
| 21 | + POETRY_CACHE_DIR=/tmp/poetry_cache |
| 22 | + |
| 23 | +RUN curl -sSL https://install.python-poetry.org | python3 - && \ |
| 24 | + ln -s /opt/poetry/bin/poetry /usr/local/bin/poetry |
| 25 | + |
| 26 | +WORKDIR /app |
| 27 | + |
| 28 | +# Copy dependency files first for better layer caching |
25 | 29 | COPY pyproject.toml poetry.lock ./ |
26 | 30 |
|
27 | | -# Tell poetry to use our venv instead of creating its own |
28 | | -ENV VIRTUAL_ENV=/venv \ |
29 | | - PATH="/venv/bin:$PATH" |
| 31 | +# Install dependencies into a virtual environment |
| 32 | +# Using --mount=type=cache speeds up rebuilds significantly |
| 33 | +RUN --mount=type=cache,target=$POETRY_CACHE_DIR \ |
| 34 | + poetry install --only=main --no-interaction --no-ansi --no-root |
| 35 | + |
| 36 | +# ============================================================================ |
| 37 | +# Development builder: Install all dependencies including dev tools |
| 38 | +# ============================================================================ |
| 39 | +FROM builder AS builder-dev |
| 40 | + |
| 41 | +# Install all dependencies including dev (debug_toolbar, pytest, etc.) |
| 42 | +RUN --mount=type=cache,target=$POETRY_CACHE_DIR \ |
| 43 | + poetry install --no-interaction --no-ansi --no-root |
| 44 | + |
| 45 | +# ============================================================================ |
| 46 | +# Development stage: Full development environment with dev tools |
| 47 | +# ============================================================================ |
| 48 | +FROM python:3.12-slim AS development |
| 49 | + |
| 50 | +LABEL org.opencontainers.image.source="https://github.com/operationcode/back-end" |
| 51 | +LABEL org.opencontainers.image.description="Operation Code Backend - Development" |
| 52 | +LABEL org.opencontainers.image.licenses="MIT" |
| 53 | + |
| 54 | +# Install runtime dependencies |
| 55 | +RUN apt-get update && apt-get install -y --no-install-recommends \ |
| 56 | + libpq5 \ |
| 57 | + curl \ |
| 58 | + && rm -rf /var/lib/apt/lists/* \ |
| 59 | + && apt-get clean |
| 60 | + |
| 61 | +# Create non-root user for security |
| 62 | +RUN groupadd -r appuser && \ |
| 63 | + useradd -r -g appuser -u 1000 -m -d /app appuser |
| 64 | + |
| 65 | +# Set environment variables for Python optimization |
| 66 | +ENV PYTHONUNBUFFERED=1 \ |
| 67 | + PYTHONPATH=/app/src \ |
| 68 | + PATH="/app/.venv/bin:$PATH" \ |
| 69 | + VIRTUAL_ENV=/app/.venv |
30 | 70 |
|
31 | | -# Install production dependencies only |
32 | | -RUN poetry install --only=main --no-interaction --no-cache |
| 71 | +WORKDIR /app |
33 | 72 |
|
34 | | -# ============================================================================= |
35 | | -# Test builder: add dev dependencies |
36 | | -# ============================================================================= |
37 | | -FROM builder AS test-builder |
| 73 | +# Copy virtual environment with dev dependencies from builder-dev stage |
| 74 | +COPY --from=builder-dev --chown=appuser:appuser /app/.venv /app/.venv |
38 | 75 |
|
39 | | -RUN poetry install --no-interaction --no-cache |
| 76 | +# Copy application code |
| 77 | +COPY --chown=appuser:appuser ./src ./src |
40 | 78 |
|
41 | | -# ============================================================================= |
42 | | -# Runtime base: minimal image shared by test and production |
43 | | -# ============================================================================= |
44 | | -FROM python:3.12-alpine AS runtime-base |
| 79 | +# Set working directory to src for running the application |
| 80 | +WORKDIR /app/src |
45 | 81 |
|
46 | | -# Install only runtime dependencies (no build tools, no poetry) |
47 | | -# Upgrade system pip to fix CVE-2025-8869 (even though app uses venv pip) |
48 | | -RUN apk upgrade --no-cache && \ |
49 | | - apk add --no-cache libpq libjpeg-turbo && \ |
50 | | - pip install --no-cache-dir --upgrade pip |
| 82 | +# Switch to non-root user |
| 83 | +USER appuser |
51 | 84 |
|
52 | | -ENV PYTHONUNBUFFERED=1 \ |
53 | | - PATH="/venv/bin:$PATH" |
| 85 | +# Expose port for Django dev server |
| 86 | +EXPOSE 8000 |
54 | 87 |
|
55 | | -WORKDIR /app |
| 88 | +# Run Django development server (will be overridden by docker-compose) |
| 89 | +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] |
56 | 90 |
|
57 | | -# ============================================================================= |
58 | | -# Test stage |
59 | | -# ============================================================================= |
60 | | -FROM runtime-base AS test |
| 91 | +# ============================================================================ |
| 92 | +# Production/Runtime stage: Minimal production image (DEFAULT) |
| 93 | +# ============================================================================ |
| 94 | +FROM python:3.12-slim AS runtime |
61 | 95 |
|
62 | | -COPY --from=test-builder /venv /venv |
63 | | -COPY src ./src |
64 | | -COPY .dev ./src/.dev |
65 | | -COPY pytest.ini ./ |
| 96 | +LABEL org.opencontainers.image.source="https://github.com/operationcode/back-end" |
| 97 | +LABEL org.opencontainers.image.description="Operation Code Backend - Django API" |
| 98 | +LABEL org.opencontainers.image.licenses="MIT" |
66 | 99 |
|
67 | | -WORKDIR /app/src |
| 100 | +# Install only runtime dependencies (no build tools) |
| 101 | +RUN apt-get update && apt-get install -y --no-install-recommends \ |
| 102 | + libpq5 \ |
| 103 | + curl \ |
| 104 | + && rm -rf /var/lib/apt/lists/* \ |
| 105 | + && apt-get clean |
68 | 106 |
|
69 | | -ENV DJANGO_ENV=testing \ |
70 | | - ENVIRONMENT=TEST |
| 107 | +# Create non-root user for security |
| 108 | +RUN groupadd -r appuser && \ |
| 109 | + useradd -r -g appuser -u 1000 -m -d /app appuser |
71 | 110 |
|
72 | | -CMD ["pytest", "-v"] |
| 111 | +# Set environment variables for Python optimization |
| 112 | +ENV PYTHONUNBUFFERED=1 \ |
| 113 | + PYTHONPATH=/app/src \ |
| 114 | + PATH="/app/.venv/bin:$PATH" \ |
| 115 | + VIRTUAL_ENV=/app/.venv |
73 | 116 |
|
74 | | -# ============================================================================= |
75 | | -# Production stage |
76 | | -# ============================================================================= |
77 | | -FROM runtime-base AS production |
| 117 | +WORKDIR /app |
| 118 | + |
| 119 | +# Copy virtual environment from builder stage (production deps only) |
| 120 | +COPY --from=builder --chown=appuser:appuser /app/.venv /app/.venv |
78 | 121 |
|
79 | | -COPY --from=builder /venv /venv |
80 | | -COPY src ./src |
| 122 | +# Copy application code |
| 123 | +COPY --chown=appuser:appuser ./src ./src |
81 | 124 |
|
82 | | -# Pre-compile Python bytecode for faster cold starts |
| 125 | +# Pre-compile Python bytecode for faster startup |
83 | 126 | RUN python -m compileall -q ./src/ |
84 | 127 |
|
| 128 | +# Set working directory to src for running the application |
85 | 129 | WORKDIR /app/src |
86 | 130 |
|
87 | | -ENV DJANGO_ENV=production \ |
88 | | - DB_ENGINE=django.db.backends.postgresql |
| 131 | +# Switch to non-root user |
| 132 | +USER appuser |
89 | 133 |
|
| 134 | +# Expose port for Gunicorn |
90 | 135 | EXPOSE 8000 |
91 | 136 |
|
| 137 | +# Health check endpoint |
| 138 | +HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ |
| 139 | + CMD curl -f http://localhost:8000/healthz || exit 1 |
| 140 | + |
92 | 141 | # Run background task processor and gunicorn |
93 | | -CMD ["sh", "-c", "python manage.py qcluster & gunicorn operationcode_backend.wsgi -c /app/src/gunicorn_config.py"] |
| 142 | +CMD ["sh", "-c", "python manage.py qcluster & gunicorn operationcode_backend.wsgi -c gunicorn_config.py"] |
0 commit comments