product:build
Table of contents
- Introduction
- What it writes
- Writing the files without the image
- Fixing a failed check
- Keeping the files in step with the pins
- Advanced
- Reference
- Requirements
- See also
Introduction
product:build prepares your product for production. It checks the product, writes the deploy bundle deploy/ and the pins the image is built from, and builds the production image. Commit what it writes, and a clone of the repository deploys with docker compose alone.
splent product:build
🏗️ Building deployment artifacts for product: my_first_app
validate UVL configuration is satisfiable
contract no conflicts detected
refiners no collisions detected (0 refiners)
features all 7 feature(s) versioned and on PyPI
📦 Generating deploy/.env.example...
✅ Created: /workspace/my_first_app/deploy/.env.example
🐳 Generating deploy/docker-compose.yml...
✅ Created: /workspace/my_first_app/deploy/docker-compose.yml
✅ Pinned splent_framework 1.26.0, splent_cli 1.44.0, gunicorn >=23,<24 in pyproject.toml [tool.splent.runtime]
✅ Created: /workspace/my_first_app/splent.contracts.json
✅ Created: /workspace/my_first_app/docker/frontend.lock.json
✅ Staged the UVL model: /workspace/my_first_app/docker/spl/sample_splent_spl/sample_splent_spl.uvl
🐳 Building Docker image: my_first_app:0.0.1...
...
✅ Image built: my_first_app:0.0.1
✅ Pinned 45 third-party package(s) in docker/requirements.lock
🎯 product:build completed successfully.
The image takes several minutes the first time. Build, deploy and release tells this command apart from product:deploy and product:release.
What it writes
Everything comes from the releases your pyproject.toml pins, never from the editable copies in your workspace.
| Path | What it is |
|---|---|
deploy/docker-compose.yml |
Every service of the product and its features, in one Compose project that runs with Docker alone |
deploy/.env.example |
Every production setting. Secrets are empty and marked # required |
deploy/features/<service>/ |
The Dockerfiles and files of the features’ own services |
deploy/docker-compose.build.yml |
An override that builds the web image instead of pulling a published one |
deploy/README.md |
How to run and upgrade the bundle, for whoever runs the server |
[tool.splent.runtime] |
The framework, the CLI and gunicorn the image installs, in pyproject.toml |
docker/requirements.lock |
Every third-party package the image installs, at an exact version |
docker/frontend.lock.json |
The bundle of every feature with frontend/, which the image checks |
splent.contracts.json |
The contracts and parameter schemas of the features, which the image reads |
docker/spl/<spl>/ |
The UVL model the product pins |
The settings of a real deployment go in deploy/.env, which product:deploy writes and git never sees.
Writing the files without the image
splent product:build --no-image
Every file is written and no image is built. Use it when a server or your CI builds the image.
Fixing a failed check
Before it writes anything, the build checks the prod selection (features plus features_prod) as product:validate would, in phases 1, 2, 4, 6 and 7, and adds two checks of its own. When one fails it says why and writes nothing.
| Check | Fails when | Fix |
|---|---|---|
| Pinned releases | A pinned prod feature is not in .splent_cache or not linked in <product>/features/ |
product:resolve |
| Feature readiness | A prod feature has no version, or its version is neither on PyPI nor tagged on the forge | feature:release it, and pin the release |
| Validation | A feature listed twice, an spl with no model, or an error in the selection, the contracts, the refiners, a parameter or a bundle |
What the line says, or product:validate for details |
The build reads the releases pyproject.toml pins, and these are not resolved in this workspace.
splent_feature_reports@v0.2.1 not in .splent_cache
Run splent product:resolve and build again.
A feature only tagged on the forge passes with a warning, and the image installs it from its tag.
Keeping the files in step with the pins
The product’s CI fails when the committed files do not match the pinned releases. The commands that change the selection or the pins write them again for you, such as feature:attach, feature:upgrade, feature:pin, feature:add, feature:remove and product:resolve.
Updated deploy, splent.contracts.json for the pins, as product:build --no-image writes them. Commit them with the pins.
They do it only in a product that already has deploy/docker-compose.yml, and leave [tool.splent.runtime] alone.
Advanced
A feature service that runs once, such as an init job, must say
restart: "no", quoted, or it getsunless-stoppedand runs again after every exit. See Features that run a server.
deploy/features/is rebuilt on every build from each feature’sdocker/directory. A file you add there by hand disappears.
Reference
Options
| Option | Description |
|---|---|
--no-image |
Write every file, but do not build the image |
Errors and warnings
| Message | What to do |
|---|---|
Product has no docker/ directory. |
Run product:sync-template |
Dockerfile not found |
Run splent product:sync-template. The files are written, the image is not |
docker/Dockerfile.<product>.prod comes from an older CLI |
Run splent product:sync-template <product> --docker and build again |
PyPI does not serve splent_framework==<version> |
Release the framework or the CLI first, or the image build fails at that install |
.gitignore leaves out what a clean clone needs |
Remove the rules that match the listed files |
Docker image build failed. |
Read the Docker output above it |
deploy/docker-compose.yml
The product’s docker-compose.prod.yml (or docker-compose.yml) merged with every feature’s. A feature service replaces a product service of the same name, with a warning.
| Setting | Value |
|---|---|
| Project | <product>_deploy, on its own network |
| Env file | deploy/.env, for every service |
| Ports | ${SPLENT_WEB_PORT:-<port>} and ${SPLENT_DB_PORT:-<port>}, the database on loopback unless SPLENT_DB_BIND_HOST says otherwise |
| Web image | ${SPLENT_IMAGE:-<registry>/<org>/<product>:stable} once a release publishes an image, otherwise a build from the repository. See Running a published image |
| Required settings | Under x-required, so docker compose up names the missing one and refuses to start |
| Restart and health | unless-stopped for every service without a policy. The web asks /readyz after a 180 second start period, a MariaDB runs SELECT 1 |
deploy/.env.example
Four layers merged, a later one winning. Each feature’s env file (.env.prod.example, otherwise .env.example), each feature’s [tool.splent.config], the product’s [tool.splent.config], and the product’s docker/.env.prod.example (otherwise docker/.env.example).
SPLENT_ENV is prod, __PRODUCT__ is resolved and feature ports get the product’s offset, as in product:env --merge. The database names are filled in. Every other secret is empty under # required, and the optional settings come last, commented out.
# required: docker compose up refuses to start until this is set. A long random string will do: openssl rand -hex 32
SECRET_KEY=
[tool.splent.runtime] in pyproject.toml
[tool.splent.runtime]
splent_framework = "1.26.0"
splent_cli = "1.44.0"
gunicorn = ">=23,<24"
A bare version means exactly that version, anything else is a pip specifier. product:create writes the table, and every build sets splent_framework and splent_cli to the workspace’s framework and the running CLI.
docker/requirements.lock
Read back from the built image with pip freeze, and used by the Dockerfile as a constraint, so a clone of a commit installs exactly what that commit’s image had. Every image build resolves afresh and rewrites it.
docker/frontend.lock.json
For each feature with frontend/, its release, its entries, the sha256 of its bundle’s manifest, what it bundles with the licences, and who provides each peer, as phase 7 of product:validate reads them. The image checks the installed bundles against it, and product:frontend-audit audits it.
splent.contracts.json
Features installed with pip carry no pyproject.toml, so their refinement contracts and their [tool.splent.config_schema] come here, with every default but a secret’s filled in. The image reads it to load the features and check the parameters.
The SPL model in the image
The framework orders features and their migrations by the UVL model, so the image ships the model the product pins. When your working copy differs from the published model, the image ships the published one and a warning asks you to publish and pin yours. When the published model cannot be fetched, it ships the working copy and asks you to run splent spl:fetch <spl>. With no model at all, features load in pyproject.toml order.
Docker image
docker build -t <product>:<version> -t <product>:latest -f <product>/docker/Dockerfile.<product>.prod <product>
The context is the product’s repository alone, so a clone builds the same image. The Dockerfile installs the runtime pins and every feature from PyPI, or from its git tag, under the lock. It builds the product’s own frontend with npm ci when package.json has a build script, and runs feature:compile --verify on the bundles the features bring.
The app runs as the user splent (uid 10001), with mariadb-client for db:dump, db:restore and db:console, and without Node.js or compilers. No .env file but the *.example templates reaches the image.
Requirements
- A selected product (
SPLENT_APP) with adocker/directory. - Every prod feature pinned and resolved.
- To build the image, Docker, and PyPI serving the versions
[tool.splent.runtime]pins.
See also
product:deployruns the bundle on this machine.product:releasepublishes a version, image included.product:validateruns every check in detail.