Operations
How code reaches production upstream, and what you need to get right if you run your own instance.
One container running the FastAPI app on port 8000. Postgres is managed elsewhere, Redis is optional and external. There is no production Compose file; the deploy image is docker/deploy/Dockerfile, which is the development image without hot reload.
development. When one merges, CI runs and then calls a deploy hook for the staging service../scripts/ci/create-release.sh 0.3.0This branches from development, bumps the version in pyproject.toml, optionally updates the contributor list, and opens a pull request back into development.release/* triggers automation that merges development into main, generates a changelog, tags the version and publishes a GitHub release.main fires the production deploy hook.Name release branches release/0.3.0, without a v. The tag is created by adding one, so release/v0.3.0 would produce a tag of vv0.3.0.
The hosting configuration is not in the repository
The deploy step is a single call to a deploy hook. Which Dockerfile is built, what environment variables are set, and how migrations are applied are all configured in the hosting dashboard, not in version control. There is norender.yaml or equivalent. Cloning the repository is not enough to reproduce the production environment.The application is MIT licensed and self-hostable. Anything that can run a container will do. Work through this list before you point a real phone number at it.
MOCK_WHATSAPP must be false. Both templates ship it as true. Left on, it disables webhook signature verification and exposes an unauthenticated endpoint that accepts messages claiming to be from any user. This is the single most important line in your configuration./metrics has no authentication. If the service is public, so are your metrics. Restrict it at the proxy.TWIGA_ENV at a mounted file. Do not bake an .env into an image.ENVIRONMENT=production. Below that value, Redis never connects, rate limiting is skipped, welcome templates are not sent, and unknown numbers are turned into test accounts.fontconfig and libgraphite2-3, which LaTeX rendering needs.Nothing applies them for you. Neither the image nor application startup runs Alembic, so alembic upgrade head is a deliberate step in your deploy process. Startup only checks that the database answers.
Three jobs in scripts/crons/ approve users, mark them inactive and send re-engagement reminders. Nothing in the repository schedules them; you need an external scheduler. Without one, approvals never complete. See Monitoring.
Rate limiting fails open
If Redis is unreachable, requests are not blocked. They pass through, and any user currently limited is reset to active. Model inference costs money, so do not treat rate limiting as your only spend control.The repository assumes one process. There is no worker count, no gunicorn, no autoscaling configuration. Two things break or degrade with multiple replicas:
Rate limiting itself is Redis backed and scales fine, as does the request path.