Features that run a server
Table of contents
Introduction
Some features need more than Python. Search needs a search node, and a session backend needs Redis. Such a feature ships a docker/ directory, and every product that installs it gets its own instance of that stack, so two products of a line run side by side on one machine.
splent feature:create splent-io/splent_feature_indexer --type service --docker
The scaffold follows the four rules below and marks every value to fill in with CHANGEME.
splent_feature_indexer, and each name resolves only inside its own product's network.What a feature ships
splent_feature_indexer/
└── docker/
├── docker-compose.dev.yml the stack in development
├── docker-compose.prod.yml the stack in production
└── .env.example its defaults
Name the service after the package
services:
splent_feature_indexer: # not "indexer", not "search"
The name is the hostname other containers use, as in ELASTICSEARCH_URL=http://splent_feature_indexer:9200.
Leave out container_name
Compose prefixes the names it derives with the project name, which is per product. It does not prefix a container_name you write, so a second product would collide with the first.
Publish ports from a *_HOST_PORT variable
ports:
- "${INDEXER_HOST_PORT}:9200"
product:env --merge gives each product its own value through the port offset. Give the variable a default in docker/.env.example, since Compose publishes an unset one on a random port without an error.
Keep data in a named volume
volumes:
- indexer_data:/usr/share/elasticsearch/data
volumes:
indexer_data:
Compose prefixes a named volume with the project name, so each product keeps its own data. A writable host path would be shared. A read-only host path is fine for configuration files.
What the CLI does for you
A project per product
product:up runs each feature’s stack as a Compose project named after the product and the feature, without the version.
my_shop_splent_io_splent_feature_indexer_dev
A new release of the feature reuses the volume of the previous one. Every command that touches the stack (product:down, product:restart, product:clean, product:containers, feature:install) derives the same name.
In production, product:build merges every feature’s compose file into deploy/docker-compose.yml, the project <product>_deploy. Build contexts and bind-mounted files are copied under deploy/features/<service>/, so the bundle needs nothing outside deploy/.
A network per product
Compose gives every container a DNS alias equal to its service name. On a shared network, two products running the same feature would both answer to it. So each product gets its own network.
networks:
splent_network:
external: true
name: ${SPLENT_NETWORK:-splent_network}
product:env --merge writes SPLENT_NETWORK=<product>_network, and product:up creates it. The CLI’s container joins the selected product’s network while it builds that product’s app. The deploy bundle drops external networks and uses the project’s own, <product>_deploy_default.
The env file it reads
A feature’s compose file would read the feature’s own .env. Every command passes --env-file with the merged file instead, docker/.env in development and deploy/.env in production.
Restart and health in production
product:build gives every deploy service without a restart policy restart: unless-stopped, so servers come back after a reboot. A job that runs once must say so, with the "no" quoted, since YAML reads a bare no as false.
splent_feature_indexer_init:
image: CHANGEME:latest
restart: "no"
The build adds healthchecks only to the web and database services. Declare your own in docker-compose.prod.yml, where the scaffold leaves one marked CHANGEME.
Checking a feature
splent check:infra
Isolation
[OK] 1 feature stack(s) isolate per product
check:infra reads the compose files as written. None of these mistakes breaks a single product. They show up the first time two products run together, which is why they need a check.
To fix a feature it flags,
- remove any
container_name, - replace literal host ports with
${<FEATURE>_HOST_PORT}and add the default todocker/.env.example, - add
name: ${SPLENT_NETWORK:-splent_network}to the external network, - move writable host paths to named volumes,
- run
splent check:infrauntil Isolation is clean.
A named volume starts empty. Rebuild a cache or an index with the command the feature ships for it, rather than copying the old host path.