diff --git a/Dockerfile b/Dockerfile index 1123177..09c6443 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,11 +6,17 @@ ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \ PYTHONDONTWRITEBYTECODE=1 WORKDIR /build -COPY pyproject.toml ./ +COPY pyproject.toml requirements.lock ./ COPY app ./app +# requirements.lock pins every transitive dependency to the known-good +# versions captured by `pip freeze` against a clean install. Install +# from it first, then add the project itself with --no-deps so the +# lockfile is the single source of truth and pyproject's range pins +# (>=) can't drift on rebuild. RUN python -m venv /opt/venv \ && /opt/venv/bin/pip install --upgrade pip \ - && /opt/venv/bin/pip install . + && /opt/venv/bin/pip install -r requirements.lock \ + && /opt/venv/bin/pip install --no-deps . FROM python:3.13-slim AS runtime @@ -49,7 +55,7 @@ ENV PYTHONUNBUFFERED=1 \ COPY --from=builder /opt/venv /opt/venv WORKDIR /app -COPY pyproject.toml ./ +COPY pyproject.toml requirements.lock ./ COPY app ./app COPY alembic ./alembic COPY alembic.ini ./ @@ -57,6 +63,10 @@ COPY alembic.ini ./ # a shipped image). docker-compose.test.yml bind-mounts ./tests:/app/tests # at run time, so the suite is always available without baking it in. -RUN /opt/venv/bin/pip install ".[dev]" +# The lockfile already contains the dev extras (pytest, ruff, aiosqlite, +# ...) because it was generated against a test-stage install. Same +# install pattern as the builder stage: lockfile first, project --no-deps. +RUN /opt/venv/bin/pip install -r requirements.lock \ + && /opt/venv/bin/pip install --no-deps . CMD ["pytest", "tests/", "-v"] diff --git a/requirements.lock b/requirements.lock new file mode 100644 index 0000000..035b270 --- /dev/null +++ b/requirements.lock @@ -0,0 +1,60 @@ +aiomysql==0.3.2 +aiosmtplib==5.1.0 +aiosqlite==0.22.1 +alembic==1.18.4 +annotated-doc==0.0.4 +annotated-types==0.7.0 +anyio==4.13.0 +APScheduler==3.11.2 +argon2-cffi==25.1.0 +argon2-cffi-bindings==25.1.0 +certifi==2026.5.20 +cffi==2.0.0 +charset-normalizer==3.4.7 +click==8.4.1 +cryptography==48.0.0 +dnspython==2.8.0 +email-validator==2.3.0 +fastapi==0.136.3 +greenlet==3.5.1 +h11==0.16.0 +hiredis==3.3.1 +httpcore==1.0.9 +httptools==0.8.0 +httpx==0.28.1 +idna==3.16 +iniconfig==2.3.0 +itsdangerous==2.2.0 +Jinja2==3.1.6 +Mako==1.3.12 +MarkupSafe==3.0.3 +packaging==26.2 +pluggy==1.6.0 +pycparser==3.0 +pydantic==2.13.4 +pydantic-settings==2.14.1 +pydantic_core==2.46.4 +Pygments==2.20.0 +PyMySQL==1.2.0 +pytest==9.0.3 +pytest-asyncio==1.4.0 +pytest-httpx==0.36.2 +python-dotenv==1.2.2 +python-multipart==0.0.29 +PyYAML==6.0.3 +redis==7.4.0 +requests==2.34.2 +ruff==0.15.14 +SQLAlchemy==2.0.50 +starlette==1.1.0 +stripe==15.1.0 +structlog==25.5.0 +tenacity==9.1.4 +typing-inspection==0.4.2 +typing_extensions==4.15.0 +tzlocal==5.3.1 +urllib3==2.7.0 +uvicorn==0.48.0 +uvloop==0.22.1 +watchfiles==1.2.0 +websockets==16.0