Docker¶
A generated project ships a Dockerfile, docker-compose.yml, and
.dockerignore -- a working container from zeython new onward, not
something you have to bolt on before a first deployment.
Building and running¶
This builds the image, runs pending migrations, and starts the server on
http://localhost:8000. docker compose reads .env for configuration
(copy .env.example to .env first, same as running locally) --
.dockerignore deliberately excludes .env from the build context itself,
so a secret never ends up baked into an image layer.
Without compose:
What the image does¶
- Multi-stage build. A
builderstage installs dependencies into a venv; the final image copies just that venv plus the app source -- no pip build cache, no dev tooling, in the shipped image. - Runs as a non-root user.
appuser, not root. - Migrates on startup. The container's
CMDrunsalembic upgrade headbefore startinguvicorn. A single-container deployment doesn't need anything fancier; running more than one replica usually means moving the migration into its own one-shot job so N containers starting together don't race to run it. - A
HEALTHCHECKhits/up-- Docker (and anything readingdocker inspect, like a Composedepends_on: condition: service_healthy) can tell when the app is actually ready, not just when the process started.
Postgres, Redis¶
The default DATABASE_URL is SQLite, and caching/rate-limiting default to
in-memory -- fine for a single container, not for anything you'd run more
than one replica of (see Redis). docker-compose.yml includes
commented-out db (Postgres) and redis services with the config you'd
uncomment to switch -- uncommenting them alone isn't enough: also add
the matching database driver (asyncpg for Postgres) to pyproject.toml's
dependencies and set DATABASE_URL/point RedisCache/RedisRateLimiter
at the redis service in main.py.
Switching QUEUE_DRIVER=redis (see Background Jobs) also
needs a queue-worker service -- also commented out in
docker-compose.yml -- running zeython queue work as its own container
alongside redis and app. Jobs a durable queue is meant to protect
(payment capture, anything a crash shouldn't drop) don't get that
protection if nothing is actually running the worker.
Scheduled tasks¶
A container has no host crontab to point zeython schedule run at (see
Scheduling). docker-compose.yml includes a commented-out
scheduler service -- a sidecar loop container that runs zeython
schedule run once a minute, forever, the container equivalent of a single
cron entry. Uncomment it once schedule.py actually defines something.
Persisting data¶
docker-compose.yml mounts ./storage (uploaded files -- see
File Storage) onto the host by default. The default SQLite
database file lives inside the container unless you also uncomment the
database.db volume mount -- otherwise docker compose down takes your
data with it.