My Dockerfile to build bearblog.

FROM python:3.13-slim AS base

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1 \
    POETRY_VIRTUALENVS_CREATE=false \
    PATH="/usr/local/bin:$PATH"

WORKDIR /app

# System deps for psycopg2, Pillow, etc.
RUN apt-get update \
 && apt-get install -y --no-install-recommends \
    build-essential \
    libpq-dev \
    libjpeg62-turbo-dev \
    zlib1g-dev \
    curl \
 && rm -rf /var/lib/apt/lists/*

# Copy dependency manifests if present; fallback to pip freeze later
COPY conf/settings.py conf/settings.py
COPY architecture.md architecture.md

# Install Python dependencies
# If a requirements.txt is present we'll use it; otherwise install most-likely deps
COPY requirements.txt requirements.txt
RUN --mount=type=cache,target=/root/.cache/pip \
    if [ -f requirements.txt ]; then \
      pip install -r requirements.txt; \
    else \
      pip install \
        Django==5.2.* \
        gunicorn \
        dj-database-url \
        python-dotenv \
        whitenoise \
        boto3 \
        pillow \
        requests \
        django-allauth \
        tldextract \
        pygmentify \
        sentry-sdk \
        django-debug-toolbar \
        geoip2 \
        ipaddr \
        psycopg2-binary \
      ; fi

# Copy project
COPY . .

# Create non-root user (optional)
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser

ENV PORT=8000 \
    DJANGO_SETTINGS_MODULE=conf.settings \
    PYTHONPATH=/app

# Expose port
EXPOSE 8000

# Entrypoint handles migrations/collectstatic, then starts gunicorn
ENTRYPOINT ["/app/entrypoint.sh"]