Reference Setup
How an entirius-service-* runs in production. This is the reference setup the maintainers run; install and update scripts are not shipped yet. The service repo ships the application
(main/wsgi.py, main/celery.py, main/settings_example.py); host layout, process units and scripts
are yours to write, and these pages say what they must do. Everything CI/CD and
Releases cover ends at “the package is published”; this section starts
where they stop.
The setup is the native deployment rail the maintainers run. It is a recommendation, with the reason next to each rule. A deviation is fine; document it in your service repository.
| Page | Covers |
|---|---|
| Reference Setup, below | host layout, the deploy rail, the post-deploy gate |
| Process Supervision | supervisor programs for uwsgi, celery and beat: stop signals, group-kill, timeouts |
| Watchdogs | detectors for faults nothing else reports, and how to arm them without crying wolf |
| Deploy Patterns | every part of a deployment and the file it is configured in |
The Host
Section titled “The Host”One service, one host (an LXC container or a VM), everything on the box:
| Component | Runs as | Listens on |
|---|---|---|
| nginx | system service | :80, the only public port |
| uwsgi (Django) | supervisor program | unix socket in run/ |
| celery worker | supervisor program | none |
| celery beat | supervisor program | none |
| PostgreSQL, Redis, RabbitMQ | system services | 127.0.0.1 only |
Why native and not containers: one host per environment has nothing to schedule, and a
container runtime on top of an LXC adds a layer to debug without removing one. Docker belongs
to local development. No WSGI server is a dependency of the service (pyproject.toml); the
reference runs uWSGI. The integration harness
(entirius-zeno) is not a deployment target.
Why supervisor and not systemd units: one config format for all three Python processes,
and supervisorctl status as the single view of the app.
The price is that supervisor’s defaults are unsafe for forking servers, see
Process Supervision. That page is mandatory reading
before touching a unit template.
What the Deploy Rail Must Do
Section titled “What the Deploy Rail Must Do”The rail has three jobs, and every step is idempotent (re-running converges the host):
- Converge applies the checkout it is given: system packages, the database, the venv from
uv.lockwithuv sync --frozen, migrations, static files, process units, and a restart. - Quick converge is the routine redeploy for code-only changes: venv, migrations, static files, units, restart.
- Fetch is a separate step (
git pull --ff-only). Converge never moves the checkout, so a host can be converged onto a reviewed commit.
- Keep every generated file as a template in your repo:
settings_local.py, supervisor programs, the uWSGI ini, nginx, cron. The converge step expands the placeholders. A hand edit on the host is erased by the next deploy, by design: the repo is the only place a change survives. - Feed every consumer from one env file. An operator-edited source file is expanded into one
generated file (
640 root:<app-user>), which supervisor, cron and uWSGI all read. Three copies of a config drift; one cannot. - Put operator flags in the source file, never the generated one. Anything set in the generated file by hand is wiped by the next converge, silently.
- Generate secrets once, on the host, never in git. The first converge writes them to a
600 rootfile; later runs read them back. - Install dependencies from
uv.lock.uv sync --frozen, PyPI only, no git dependencies, no build-time credentials.
The Post-Deploy Gate
Section titled “The Post-Deploy Gate”A deploy that exits 0 on an app that serves nothing is worse than a failed one. The converge ends with a smoke step and exits non-zero if any check fails:
make healthin the service checkout:manage.py check, the database check andmigrate --check. It is read-only and safe in production.supervisorctl status: no program inFATAL,BACKOFForEXITED.- The process tree is the expected one: one uwsgi master, exactly
processesworkers under it, zero uwsgi processes parented to init (see Process Supervision). - An HTTP request through nginx returns the expected status.
- An API error path returns the mapped 4xx envelope, not a 500. A happy path alone does not prove the exception handler is wired.
- One real call through every external integration the service depends on (a proxy, a third-party API). Configuration that is present but wrong only shows up here.
Then run the configuration health check.
Guard the Rail with Tests
Section titled “Guard the Rail with Tests”Deploy templates are code and get tests in the service’s own suite, so they run in CI, long before a host sees the change. At minimum:
- every rail script exists, is executable, and parses (
bash -n); - every placeholder in a template is one the converge step expands;
- the supervision invariants from Process Supervision.
Related
Section titled “Related”- Process Supervision: the unit templates.
- Watchdogs: runtime faults a deploy gate cannot see.
- CI/CD: the pipeline that runs the rail’s tests.