Skip to content

Commit 390a8f8

Browse files
committed
Rework docker configuration
1 parent be2bfd3 commit 390a8f8

5 files changed

Lines changed: 144 additions & 72 deletions

File tree

Dockerfile

Lines changed: 115 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,142 @@
11
# syntax=docker/dockerfile:1
22

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
2529
COPY pyproject.toml poetry.lock ./
2630

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
3070

31-
# Install production dependencies only
32-
RUN poetry install --only=main --no-interaction --no-cache
71+
WORKDIR /app
3372

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
3875

39-
RUN poetry install --no-interaction --no-cache
76+
# Copy application code
77+
COPY --chown=appuser:appuser ./src ./src
4078

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
4581

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
5184

52-
ENV PYTHONUNBUFFERED=1 \
53-
PATH="/venv/bin:$PATH"
85+
# Expose port for Django dev server
86+
EXPOSE 8000
5487

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"]
5690

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
6195

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"
6699

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
68106

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
71110

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
73116

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
78121

79-
COPY --from=builder /venv /venv
80-
COPY src ./src
122+
# Copy application code
123+
COPY --chown=appuser:appuser ./src ./src
81124

82-
# Pre-compile Python bytecode for faster cold starts
125+
# Pre-compile Python bytecode for faster startup
83126
RUN python -m compileall -q ./src/
84127

128+
# Set working directory to src for running the application
85129
WORKDIR /app/src
86130

87-
ENV DJANGO_ENV=production \
88-
DB_ENGINE=django.db.backends.postgresql
131+
# Switch to non-root user
132+
USER appuser
89133

134+
# Expose port for Gunicorn
90135
EXPOSE 8000
91136

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+
92141
# 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"]

docker-compose.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ services:
1818
backend:
1919
build:
2020
context: .
21-
target: test
21+
target: development
22+
env_file:
23+
- .env
2224
environment:
23-
- SECRET_KEY=dev-secret-key-not-for-production
24-
- DJANGO_ENV=development
25-
- DEBUG=True
25+
# Override .env database settings to use PostgreSQL container
2626
- DB_ENGINE=django.db.backends.postgresql
2727
- DB_NAME=operationcode
2828
- DB_USER=operationcode
@@ -33,10 +33,10 @@ services:
3333
- "8000:8000"
3434
volumes:
3535
- ./src:/app/src
36-
- ./.dev:/app/src/.dev
3736
depends_on:
3837
db:
3938
condition: service_healthy
39+
working_dir: /app/src
4040
command: >
4141
sh -c "python manage.py migrate &&
4242
python manage.py runserver 0.0.0.0:8000"

example.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ SENTRY_SEND_DEFAULT_PII=[True]
5050

5151

5252
# Creds needed to use AWS S3 for serving static assets
53-
AWS_STORAGE_BUCKET_NAME=[BUCKET_NAMAE]
53+
AWS_STORAGE_BUCKET_NAME=[BUCKET_NAME]
5454
BUCKET_REGION_NAME=[REGION_NAME]
5555
AWS_ACCESS_KEY_ID=[ACCESS_KEY_ID]
5656
AWS_SECRET_ACCESS_KEY=[SECRET_ACCESS_KEY]

poetry.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Poetry configuration for consistent development environment across team
2+
# https://python-poetry.org/docs/configuration/
3+
4+
[virtualenvs]
5+
# Create .venv in project directory for easier IDE integration and visibility
6+
in-project = true
7+
8+
# Use Python version from pyproject.toml, not active shell Python
9+
# Ensures consistency across team members
10+
prefer-active-python = false
11+
12+
[installer]
13+
# Enable parallel installation for faster dependency resolution (default in 2.x)
14+
parallel = true
15+
16+
# Limit concurrent workers to prevent resource exhaustion
17+
max-workers = 10
18+
19+
[experimental]
20+
# Use system git client instead of dulwich for better performance
21+
# Helpful if you have git dependencies
22+
system-git-client = true

src/gunicorn_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
worker_connections = 1000
7070
timeout = 30
7171
keepalive = 2
72+
worker_tmp_dir = "/dev/shm"
7273

7374
# preload_app - Load application code before forking worker processes.
7475
# This conserves memory and speeds up server boot times by loading

0 commit comments

Comments
 (0)