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.

Two products running the same feature, each with its own stack my_shop offset 702 network my_shop_network my_shop_web my_shop_db splent_feature_indexer Its own indexer_data volume Host port 9201 + 702 = 9903 my_blog offset 543 network my_blog_network my_blog_web my_blog_db splent_feature_indexer Its own indexer_data volume Host port 9201 + 543 = 9744
Both products reach their search node by the same name, 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,

  1. remove any container_name,
  2. replace literal host ports with ${<FEATURE>_HOST_PORT} and add the default to docker/.env.example,
  3. add name: ${SPLENT_NETWORK:-splent_network} to the external network,
  4. move writable host paths to named volumes,
  5. run splent check:infra until 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.


Back to top

splent. Distributed by an LGPL license v3. Contact us: drorganvidez@us.es