Your Own Service
Your backend starts as a fork of the service repository. You choose the modules it runs, give every environment its own settings, create the first channel and point the CMS and the storefront at the API. Product names such as Volkanos and zeno are explained in Concepts.
What You Build
Section titled “What You Build”The service is a thin Django project. Modules are packages it installs and enables; none of their code lives in the
service repository. You keep main/ as the Django project and change which modules run, the settings and the data.
The muted item is planned, not shipped. modules.yaml is where you choose what goes into LOCAL_APPS and the
URLs, and the enabled apps decide the queues of the Celery worker. The first channel and your data package are the
data the service writes.
Start From the Service
Section titled “Start From the Service”Fork entirius-service-volkanos on GitHub (the repository is not a template) and clone your fork. Then, in the clone:
uv sync --frozencp main/settings_example.py main/settings_local.pyuv run python manage.py migrateuv run python manage.py runservermain/settings_local.py is required and gitignored. It must define ENVIRONMENT, SECRET_KEY and DATABASES.
Without the file, or without one of those names, the service refuses to boot and names what is missing: an
environment nobody configured on purpose does not start.
The example settings read DATABASE_URL and fall back to SQLite when it is unset, so the commands above need no
database server. They also enable no module: LOCAL_APPS is empty, so a module route answers Not Found until its module is enabled.
Choose Modules
Section titled “Choose Modules”modules.yaml at the repository root is the registry of every module the service depends on. Each entry carries the facts you need to enable it:
| Key | Meaning |
|---|---|
core | The module runs in every service; the rest are optional. |
requires | Modules that must be enabled with it: hard package dependencies. |
also_enable | Apps it imports whenever their package is installed, so on this service they must be enabled with it. |
queues | Worker queues its tasks are routed to. |
settings | Django settings it reads. |
urls | Whether main/urls.py routes it when its app is enabled. |
Every module package stays installed, whether you enable its app or not. That is why also_enable exists: a module
that imports a sibling as soon as the sibling’s package is importable needs that sibling’s app enabled too.
Enabling a module takes three places:
- Its package in
pyproject.toml. The packages of the registry’smodulesare already there. - Its app label in
LOCAL_APPSinmain/settings_local.py, in registry order, together with the apps of itsrequiresandalso_enable. Order matters: every module comes after the modules it requires. - A URL guard in
main/urls.pywhenurls: true. The guards of the registry’s modules are already there; each one routes its module only where the app is enabled.
The commented LOCAL_APPS list in main/settings_example.py is the full set in the right order. It is generated
from the registry by make settings-example, so copy from it and delete what you do not run.
The platform entry of the registry is the access module. It is never added to LOCAL_APPS: the service enables it
itself wherever the package is installed, and with it the admin gate and the token checks. The package is one of the
service’s dependencies since Platform 4.0: a fork gets it by keeping the package installed.
What each module does: Module Catalog.
Settings Per Environment
Section titled “Settings Per Environment”Every environment has its own main/settings_local.py, and none of them is committed. Beyond the required names,
the commented blocks of main/settings_example.py show what a working installation sets:
CELERY_BROKER_URL,REDIS_URLandCELERY_RESULT_BACKENDfor the worker.QMS_TYPE, the stock strategy of the environment.CRYPT_SALT, the Fernet key of the encrypted fields, required when the crypt module is installed. The example file carries the command that generates one.JWT_SECRET, which defaults toSECRET_KEY. Set it per environment.CORS_ALLOWED_ORIGINS: the CMS and the storefront call the API from the browser, so production lists their origins.DRF_NUM_PROXIES, the number of proxies in front of the application. Left unset, it failscheck --deploy.
The read model needs one thing the example file does not show: a django-redis cache as the default cache. The harness sets it in its own settings_local.py, which is a complete working example of the file.
Module settings are listed per module in modules.yaml under settings.
Worker Queues
Section titled “Worker Queues”One worker consumes celery, the default queue, plus the queues of every enabled module:
celery -A main worker -l info -Q <list><list> is comma separated. Build it from the registry each time you enable a module. What a queue missing from -Q
costs: Process Supervision.
The First Channel
Section titled “The First Channel”A migrated database has no channel, and nothing sells without one. Create a superuser, then run the first-channel
script with your copy of scripts/first-channel.example.toml:
[channel]idx = "web"name = "Web"default_language = "en"languages = ["en", "pl"]default_currency = "EUR"currencies = ["EUR"]country = "DE"uv run python manage.py createsuperusermake first-channel CONFIG=my.tomlLanguages, currencies and the country are codes of the regional tables. make first-channel runs
scripts/first_channel.py on that file, and the script prints one line per step:
first-channel: regional createdfirst-channel: pim createdfirst-channel: sync ranfirst-channel: pricemanager created…first-channel: storefront X-API-KEY ent_api_… access tokenfirst-channel: api-key createdfirst-channel: admin createdfirst-channel: munin ranfirst-channel: done web| Line | Means |
|---|---|
regional | The regional defaults fixture was loaded: the languages, currencies and countries the config refers to. |
pim, pricemanager, checkout, matrix, accounts, qms | The channel was created in that module. Each module keeps its own channel row, and they agree on the idx. |
sync | The channel sync commands of the other enabled modules ran. |
storefront X-API-KEY | The storefront’s key, shown once. |
admin | Every superuser got the customer record the CMS login reads. createsuperuser does not create it. |
munin | The module registry the CMS reads its panels from was refreshed. |
done | The idx of the channel. |
A step whose module is not enabled prints skipped, and so does admin while no superuser exists: run
createsuperuser, then the script again. A second run prints exists where the first printed created
and no key line. It is safe to repeat, with one caveat: the pim step writes the name, defaults, languages and
currencies of the config file back on every run, so keep the file in step with what you later change in the CMS.
With the access module installed, the printed X-API-KEY is an application token with the checkout.storefront
scope, pinned to the channel. The server stores only a hash and cannot show the value again, so store it now.
Why keys are tokens: Access and API Tokens. Rotation, revocation and more tokens: Application Tokens.
Point the CMS and the Storefront at It
Section titled “Point the CMS and the Storefront at It”In the CMS .env, set VUE_APP_API_URL to the URL of your service and VUE_APP_CHANNEL to the idx of the
channel, then build. Every variable: Admin CMS.
In the storefront’s _CONFIG/ directory, API_BASE_URL is the URL of your service and API_CHECKOUT_KEY of the
channel is the key the first-channel script printed. The three files and their keys:
Storefront.
In the harness both frontends are configured from its .env: PWA_CHANNEL is the channel the storefront serves and
the CMS edits, and CHECKOUT_API_KEY, a line you add, is the storefront’s access token. Set them, then build and
start:
make pwamake cmsThe harness seed imports the fixture keys of the demo dataset as access tokens. On your own data,
CHECKOUT_API_KEY is the value the first-channel script printed.
Your Own Data
Section titled “Your Own Data”A channel without products is an empty shop. Catalog, prices and stock go in through a package of CSV files and a
fixed run of manage.py commands: Data Import.
Run It in the Harness
Section titled “Run It in the Harness”In the harness’s .env, SERVICE and SERVICE_BRANCH select the service under test. The image build clones
github.com/entirius/<SERVICE> at that branch, so those two lines choose among the repositories and branches of the
entirius organisation.
A fork that lives elsewhere runs in dev mode, where the harness mounts a local clone over the image. Leave SERVICE
as it is, clone your fork into repos/services/<SERVICE> (the path make clone fills), then:
make buildmake devOn every dev start the entrypoint copies the harness’s docker/settings_local.py into the mounted clone and syncs
the environment from your fork’s lock file. Two consequences. The main/settings_local.py of your clone is overwritten, so edit the harness’s file. And the
modules that run in the harness come from the active recipe: that file reads LOCAL_APPS from
recipes/<RECIPE>/apps.txt, and the worker consumes the queues of recipes/<RECIPE>/queues.txt (RECIPE in .env,
shop when unset). Change modules and queues in those two files, then run make dev again.
make dev exits clean even when the service failed to start; the service’s container log tells the truth.
Deploy
Section titled “Deploy”The reference setup and the patterns behind it are under DevOps: host layout, process supervision, watchdogs, the checks a deploy must pass. Install and update scripts are not shipped yet; until they are, the host side is yours to write.
Turn your own catalog into a data package: Your Data.