Recommended libraries & conventions — Python (deploymill)
You are an agent about to write or extend application code in a deploymill-managed Python app. This guide is deploymill's paved road: the libraries and patterns that are known to work with the platform's constraints, plus where deploymill has no opinion and you should defer to the user or the repo.
Read it together with deploymill://guides/stack/python, which documents the platform contract — the things that make the deploy succeed or fail. This guide is about choices; the stack guide is about requirements.
How to read these recommendations
Recommendations come in three strengths. Treat them differently:
- Contract (must). Lives in
deploymill://guides/stack/python, not here. Violate it and the deploy breaks (bind to0.0.0.0, listen on port 8000, ephemeral filesystem, slim runtime stage, no runtimepip install). - Paved road (should). The defaults below. They're not arbitrary — every one is either already in the template deploymill scaffolds for you or already used by the database playbook (
deploymill://guides/database/python). Following them keeps the app consistent with what the rest of the tooling assumes. You can deviate; each row says when that's reasonable and what you have to preserve if you do. - Taste (deploymill has no opinion). Formatting, test runner, folder layout beyond the template. deploymill is an orchestration layer, not a framework — it won't dictate these. Defer to the user, or to the repo's
.deploymill/conventions.mdif one exists (see the bottom of this guide).
The rule of thumb: the closer a choice sits to a platform constraint, the more weight the recommendation carries. A pooled DB client is a strong recommendation because Neon caps connections; a formatter is no recommendation at all.
Paved road
These are the choices the python template and the database guide already commit to. Stay on them unless you have a concrete reason.
| Concern | Default | Why (tie to the platform) | When to deviate |
|---|---|---|---|
| Runtime | Python 3.12 (python:3.12-slim) | The scaffolded Dockerfile's base image. | Only with a matching base-image bump in the Dockerfile. |
| Dependency metadata | pyproject.toml (PEP 621) | What the template ships; the build reads deps from it. | — |
| Installer | uv (uv pip install --system) | The Dockerfile uses it for fast installs onto PATH in the runtime image. No venv (the stack guide explains why a venv just adds layers). | — |
| Web framework | FastAPI + uvicorn[standard] | What the template scaffolds; the CMD passes --host 0.0.0.0 --port 8000 to satisfy the bind/port contract. | Django/Flask — keep the host/port contract and /healthz, update CMD. |
| Postgres client | psycopg[binary,pool] (>=3.2), one ConnectionPool at module scope | [binary] avoids build tooling in the image; the single pool respects Neon's connection limit (a per-request pool exhausts it). | Async app → asyncpg / async SQLAlchemy; swap the pool import, the rest of the playbook holds. |
| Migrations | alembic (>=1.13), alembic upgrade head on container start | Idempotent and runs before traffic, so a half-applied schema never serves. | — |
| Health routes | /healthz (liveness), /db (DB reachability) | deploymill's liveness convention and the post-deploy DB check. | Keep a stable replacement path if you move them. |
What the paved road steers you away from (from the database guide's "What NOT to do"): don't reach for the SQLAlchemy ORM unless the user explicitly asks. Plain psycopg + Alembic keeps the surface area small and is enough for most scaffolded apps. (Alembic itself uses SQLAlchemy Core for migration ops — that's fine and separate from using the ORM in app code.)
When the user needs a capability the template doesn't ship
These aren't in the scaffold, so there's no forced default. The picks below are recommended-but-optional — reach for them when the user asks for the capability, and feel free to substitute if they have a preference. Each one is tied to something deploymill already gives you, or something FastAPI already pulls in.
- Request/response models & validation → Pydantic. FastAPI already depends on it, so it costs nothing and is the idiomatic choice. Optional only in the sense that you might not need models at all.
- Auth → a SQL-backed library over the managed Postgres you already provision via
database: { provider: "neon" }— no extra infra. If the user wants a hosted IdP (Clerk, Auth0) instead, that's fine; host-pinned callback URLs must be set per-environment viaset_env_vars(and per-preview viacreate_previewenvOverrides— seedeploymill://guides/previews). Readdeploymill://guides/authbefore wiring auth — it covers the platform facts that trip up login on deploy/preview (sessions in Postgres, host-pinned base URL, secrets via the vault, HTTPS-vs-secure-cookies). - Concurrency → uvicorn
--workers N(rule of thumb 2N+1 vs cores) or gunicorn-w N, per the stack guide. Tune inCMD.
Things deploymill has no opinion on
Don't impose a default here. If the user hasn't said and there's no .deploymill/conventions.md, pick the least surprising option and move on — these have no platform consequence:
- Code formatting / linting (Ruff, Black, isort).
- Test framework (pytest, unittest).
- Folder structure beyond what the template scaffolds.
- Logging library, naming conventions.
Project-level overrides: .deploymill/conventions.md
A repo (or the user) may pin its own house style. Before applying the defaults above, check for a .deploymill/conventions.md file in the repo (read it with get_file). If present, it overrides this guide wherever the two disagree — it's the team's explicit choice and wins over deploymill's generic paved road. The platform contract in the stack guide still holds regardless; a project can't opt out of binding to 0.0.0.0:8000.
This is the same file-as-truth pattern as .deploymill/project.json: conventions live in the repo, version with the code, and travel with the project.