This page explains how the Cluster ERP server process starts: the two entry points server/main.py and server/asgi.py, the factory create_app in server/app.py, and the lifespan context that runs at start-up and shutdown. It is for developers and operators. The middleware order is on request pipeline and middleware, and service units and gunicorn settings are in the operations section.
In short¶
python -m cluster_erp.server.maincallsuvicorn.run("cluster_erp.server.app:create_app", factory=True,...)(src/cluster_erp/server/main.py:39-58).cluster_erp.server.asgi:appis a module-level application made bycreate_app()for a process manager (src/cluster_erp/server/asgi.py:1-19).create_appreadsSettings, builds theFastAPIobject, installs error handlers and middleware, mounts the shared routes and the web app, and returns the app (src/cluster_erp/server/app.py:1287-1646).- The
lifespancontext creates the database handle, theEventBus, theModuleRegistry, and the storage backend, then loads every enabled module (src/cluster_erp/server/app.py:58-140). - At shutdown the lifespan closes the PDF renderer, cancels background tasks, shuts down every module, and closes the database (
src/cluster_erp/server/app.py:196-224).
How it works¶
Entry points¶
There are two entry points.
| Entry point | Used for | Behaviour | Source |
|---|---|---|---|
python -m cluster_erp.server.main (console script cluster-erp-server) |
Development and single-process runs | Runs uvicorn with the factory; enables reload when debug is on; uses server_workers processes when greater than 1 and reload is off |
src/cluster_erp/server/main.py:12-58, pyproject.toml:87 |
cluster_erp.server.asgi:app |
gunicorn with uvicorn workers | Creates the app at import time, once per worker | src/cluster_erp/server/asgi.py:1-19 |
main.py ignores server_workers above 1 on Windows and logs a warning (src/cluster_erp/server/main.py:27-35). It passes the proxy-header settings, the access-log switch, and the keep-alive timeout to uvicorn (src/cluster_erp/server/main.py:50-55). See the settings reference for these settings.
The factory¶
create_app(settings=None) runs these steps in order:
- Creates
Settings()when none is passed (src/cluster_erp/server/app.py:1288-1289). - Configures logging at DEBUG or INFO (
src/cluster_erp/server/app.py:1291-1294). - Builds
FastAPIwith title "Cluster ERP", version0.1.0, and thelifespancontext. The/api/docsand/api/redocpages exist only whendebugis on (src/cluster_erp/server/app.py:1296-1303). - Stores the settings on
app.state.settings(src/cluster_erp/server/app.py:1304). - Applies three process-wide settings: change-history redaction columns, the authorisation cache lifetime, and the list count cap (
src/cluster_erp/server/app.py:1307-1313). - Installs the error handlers (
src/cluster_erp/server/app.py:1314). See errors and exceptions. - Adds five middlewares (
src/cluster_erp/server/app.py:1317-1430). See request pipeline and middleware. - Includes eight shared routers, and in multi-tenant mode also the signup and vendor routers (
src/cluster_erp/server/app.py:1432-1450). See shared server routes. - Defines seven inline routes:
GET /api/v1/signup/(single-tenant only),/.well-known/security.txt,/health,/ready,/api/v1/modules,/api/v1/_meta, and/api/v1/_switches(src/cluster_erp/server/app.py:1451-1572). - Mounts the web app at
/app/vand/appwhenwebapp/exists (src/cluster_erp/server/app.py:1633-1644). See cache and asset stamp.
Module routes are not added by create_app. Each module adds its own routes when the lifespan loads it.
The lifespan at start-up¶
The sequence diagram shows the start-up path of the lifespan context.
sequenceDiagram participant U as uvicorn or gunicorn participant L as lifespan participant R as ModuleRegistry participant M as Module participant D as Database U->>L: start application L->>D: Database(settings) L->>L: EventBus(), ModuleRegistry(), create_storage_backend() L->>R: load_all(app, bus, database, enabled_modules) R->>M: initialize, register_routes, register_events L->>D: seed sequences, permissions, deactivation columns L->>L: start background tasks L-->>U: yield (serve requests)
In order, the lifespan does the following:
- Creates
Database(settings),EventBus(),ModuleRegistry(), and the storage backend, and stores them onapp.state(src/cluster_erp/server/app.py:60-92). - Subscribes a handler to
AnonymiseSubjectthat drops cached user names of an anonymised account (src/cluster_erp/server/app.py:80-90). - In multi-tenant mode, creates and migrates the default database, opens the
TenantRegistry, and ensures the bootstrap operator. In single-tenant mode, checks the connection budget (src/cluster_erp/server/app.py:100-129). Tenancy is documented in multi-tenancy. - Loads the modules listed in
enabled_modules(src/cluster_erp/server/app.py:140). See module lifecycle. - Validates that every sub-table tab points at an entity with columns, and fails start-up otherwise (
src/cluster_erp/server/app.py:146,src/cluster_erp/server/app.py:547-589). - Seeds the code sequences (
src/cluster_erp/server/app.py:148,src/cluster_erp/server/app.py:1058-1066). See base model and ids. - Builds the tenant-field specs from every module's metadata (
src/cluster_erp/server/app.py:151). See tenant fields. - Seeds permissions from metadata, adds missing deactivation columns, and builds the deactivation registry (
src/cluster_erp/server/app.py:152-154). - Runs three data reconciliation passes: CRM customers to parties, party role flags, and orphaned MRP runs (
src/cluster_erp/server/app.py:155-157). - Runs a log retention pass, starts the audit-log redaction backfill as a task, and replays the erasure ledger (
src/cluster_erp/server/app.py:160-168). - Starts background tasks: retention purge, subscription sweep, analytics summary refresh, and in multi-tenant mode the membership expiry loop (
src/cluster_erp/server/app.py:170-194). - Yields; the server now serves requests (
src/cluster_erp/server/app.py:196).
The daily background loops sleep for 24 hours before each pass (src/cluster_erp/server/app.py:384, src/cluster_erp/server/app.py:424, src/cluster_erp/server/app.py:521). The analytics loop sleeps for a configured interval (src/cluster_erp/server/app.py:483). A periodic pass runs only in the worker that wins a PostgreSQL advisory lock (src/cluster_erp/server/app.py:357-369).
The lifespan at shutdown¶
After the yield, the lifespan closes the shared PDF renderer, cancels the background tasks and awaits them, calls registry.shutdown_all(), closes the tenant registry, and closes the database (src/cluster_erp/server/app.py:198-224).
Why it is built this way¶
- The project's written engineering rules state that the server uses the uvicorn factory pattern.
- The
asgi.pydocstring states why production uses gunicorn with uvicorn workers: onSIGHUPgunicorn starts new workers that import the current code and stops the old ones, which gives zero-downtime upgrades. The app is created per worker so that a reload loads new code (src/cluster_erp/server/asgi.py:3-12). - A comment states that
main.pykeeps one process on Windows because a shared socket was measured slower there (src/cluster_erp/server/main.py:27-34). - Rationale for the factory itself, beyond the above, is not determined from code.
Rules and boundaries¶
Settings()validates at construction and refuses unsafe combinations, for example multi-tenant mode withdebugon (src/cluster_erp/config/settings.py:766-770). The server then does not start. See the settings reference.- A sub-table tab without columns stops start-up with an error (
src/cluster_erp/server/app.py:547-589).
Interaction with other parts¶
- Modules are loaded through the module lifecycle.
- The
EventBuscreated here is shared by all modules (src/cluster_erp/server/app.py:62,src/cluster_erp/server/app.py:140). See event bus. - Retention and log passes depend on the
recordsandlogsmodules. See records and scheduled jobs and retention.
Limits and open points¶
- Whether a single-tenant start applies Alembic migrations is not stated in
server/app.py; the lifespan callsupgrade_database_to_headonly in multi-tenant mode (src/cluster_erp/server/app.py:100-113). Migration handling is documented in database migrations. - Most start-up passes log a warning and continue when they fail, for example
_seed_sequences(src/cluster_erp/server/app.py:1058-1066).
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| app factory | create_app |
The function that builds and returns the FastAPI application. | uygulama fabrikası | src/cluster_erp/server/app.py:1287 |
| lifespan | lifespan |
The async context that runs start-up work before serving and cleanup after. | yaşam süresi bağlamı | src/cluster_erp/server/app.py:58-59 |
Related pages¶
- Platform core
- Module lifecycle
- Request pipeline and middleware
- Settings reference
- Service and process manager
Verified against commit 92f8051 on 2026-11-13.