Boilr Generator
Boilr Generator is a modular scaffolding engine for assembling complete Dockerized application stacks from declarative YAML or JSON manifests.
Instead of maintaining one template for every technology combination, Boilr composes reusable modules. The current built-in stack combines Django, PostgreSQL, and Redis through explicit integration modules.
Boilr is currently maintained by a single contributor and is under active pre-1.0 development.
Features
- Declarative project manifests
- Reusable backend, database, cache, and integration modules
- Typed capabilities, requirements, and bindings
- Provider selection by module, PEP 440 version constraint, and tags
- Deterministic dependency resolution and cycle detection
- Typed extension points and contributions
- Complete generation plans before filesystem mutation
- Immutable dry runs with human-readable and JSON output
- Deterministic project state persisted in
.boilr/state.json - Read-only filesystem drift inspection
- Explicit reconciliation of detected resource moves
- Safe state-aware project updates with dry-run support
- Module addition, update, retention, and removal planning
- Dependency-aware module lifecycle ordering
- Atomic state transactions with interrupted-operation detection
- Create, overwrite, skip, clean, and replace operations
- SHA-256 fingerprints and content sizes for planned files
- Safe path boundaries and structured diagnostics
- Docker Compose and environment-file generation
- Linux and Windows support on Python 3.11 through 3.14
- Wheel and source-distribution verification in CI
- Real Docker E2E coverage for Django, PostgreSQL, and Redis
Installation
Boilr requires Python 3.11 or newer.
Install from the repository
Clone the repository, create a virtual environment, and install the package:
git clone https://github.com/estebanGarnil/boilr-generator.git
cd boilr-generator
python -m venv .venv
Activate the environment on Linux or macOS:
source .venv/bin/activate
Activate it in Windows PowerShell:
.venv\Scripts\Activate.ps1
Install Boilr:
python -m pip install .
The distribution name is boilr-generator. The installation exposes the
boilr command, while the Python package remains boilr_generator. The
module entry point is also available as python -m boilr_generator.cli.
Development installation
Install the package with its development dependencies:
uv sync --locked --all-groups
Quick start
Create a project.yml manifest:
project:
name: my_app
type: fullstack_web
version: 1.0.0
modules:
- key: postgres
variables:
db_name: my_app
db_user: my_app
db_password: change-me
db_port: 5432
- key: redis
variables:
redis_host_port: 6379
redis_database: 0
- key: django
variables:
project_name: my_app
django_settings_module: config.settings.local
backend_port: 8000
secret_key: change-me
debug: true
allowed_hosts:
- localhost
- 127.0.0.1
options:
rest_framework: true
cors: true
- key: django-postgres
- key: django-redis
Preview every planned operation without writing anything:
boilr dry-run project.yml generated-project --info
Generate the project:
boilr generate project.yml generated-project --info
Start the generated stack:
cd generated-project
docker compose up --build
The resulting Compose stack contains three services:
backend: Djangodb: PostgreSQLredis: Redis
The integration modules also add:
psycopg[binary]and the PostgreSQL Django backenddjango-redisand the DjangoCACHESconfiguration
The credentials in this example are intended only for local development. Replace them before using a generated project in another environment.
Project manifest
A manifest contains project metadata and an ordered list of selected modules.
project:
name: my_app
type: fullstack_web
version: 1.0.0
modules:
- key: postgres
variables:
db_name: my_app
db_user: my_app
db_password: change-me
options: {}
Project metadata
| Field | Description |
|---|---|
name |
Project name |
type |
Project category used by the caller |
version |
Project version |
Module inputs
Each module selection accepts:
| Field | Description |
|---|---|
key |
Built-in or registered module key |
variables |
Values declared by the module's variable schema |
options |
Optional feature switches declared by the module |
bindings |
Optional provider-selection criteria for named requirements |
Unknown modules, variables, and options are rejected. Required values and declared types are validated before resolution.
Provider selection
When a capability has several possible providers, the consumer can select one through the requirement's binding name:
modules:
- key: django
bindings:
primary_database:
provider: postgres
version: ">=1,<2"
tags:
- database
- sql
The available criteria are:
| Criterion | Meaning |
|---|---|
provider |
Exact provider module key |
version |
PEP 440 version specifier matched against the provider module version |
tags |
Tags that must all be present on the provider |
Criteria are combined with logical AND. A direct selection must contain at least one criterion.
If no provider matches, Boilr raises a structured provider-selection error. If a unique requirement still matches multiple providers, Boilr reports the remaining candidates as ambiguous.
Built-in modules
| Key | Type | Contract |
|---|---|---|
django |
Backend | Provides backend.python, requires one database.connection |
postgres |
Database | Provides database.connection and the db service |
redis |
Cache | Provides cache.connection and the redis service |
django-postgres |
Integration | Connects the Python backend to PostgreSQL |
django-redis |
Integration | Connects Django to Redis |
Django
Required variables:
| Variable | Type | Default |
|---|---|---|
project_name |
string | none |
django_settings_module |
string | config.settings.local |
backend_port |
integer | 8000 |
secret_key |
string | none |
debug |
boolean | true |
allowed_hosts |
list | localhost, 127.0.0.1 |
Options:
| Option | Type | Default |
|---|---|---|
rest_framework |
boolean | true |
cors |
boolean | true |
PostgreSQL
| Variable | Type | Default |
|---|---|---|
db_name |
string | none |
db_user |
string | none |
db_password |
string | none |
db_port |
integer | 5432 |
Redis
| Variable | Type | Default |
|---|---|---|
redis_host_port |
integer | 6379 |
redis_database |
integer | 0 |
Command-line interface
boilr [OPTIONS] COMMAND [ARGS]...
Available commands:
| Command | Description |
|---|---|
dry-run |
Build and display a generation plan without mutating the output |
generate |
Generate a project and persist its initial state |
status |
Inspect project drift without modifying files or state |
reconcile |
Explicitly accept detected resource moves into the state |
update |
Safely apply manifest, resource, and module changes |
Preview a generation
boilr dry-run [OPTIONS] MANIFEST_PATH OUTPUT_PATH
Options:
| Option | Description |
|---|---|
--info |
Show the detailed human-readable plan |
--json |
Print the complete serialized plan |
--clean |
Preview cleanup of the output directory |
--debug |
Show the complete traceback when an error occurs |
Even with --clean, a dry run does not create, overwrite, remove, or change permissions on any path.
Example:
boilr dry-run project.yml generated-project --clean --json
Generate a project
boilr generate [OPTIONS] MANIFEST_PATH OUTPUT_PATH
Options:
| Option | Description |
|---|---|
--info |
Show the generation plan before writing |
--clean |
Plan cleanup before generation |
--debug |
Show the complete traceback when an error occurs |
Example:
boilr generate project.yml generated-project --clean --info
Inspect a generated project
Inspect tracked resources without modifying the project:
boilr status generated-project
Request the complete observation as JSON:
boilr status generated-project --json
Reconcile a detected move
Inspect possible resource moves first:
boilr status generated-project --json
Preview explicit acceptance of a move:
boilr reconcile generated-project \
--accept-move RESOURCE_ID=NEW_PATH \
--dry-run \
--json
Apply the reconciliation:
boilr reconcile generated-project \
--accept-move RESOURCE_ID=NEW_PATH
Reconciliation updates only .boilr/state.json. It does not move or rewrite
project files.
Update a generated project
After changing project.yml, preview the state-aware update:
boilr update project.yml generated-project --dry-run --info
Request the complete update contract:
boilr update project.yml generated-project --dry-run --json
Apply the update:
boilr update project.yml generated-project --info
Updates compare the committed state, the observed filesystem, and the newly desired project. Modified tracked resources, untracked destinations, unresolved moves, dependency cycles, and stale plans block execution.
Module additions and removals are planned from the complete manifest. Unchanged generated resources may be safely removed, while user-created files and non-empty containing directories are preserved.
See Generated project lifecycle for the complete state, transaction, reconciliation, and safety contract.
Dry-run JSON contract
The JSON representation contains metadata rather than complete file contents:
| Field | Description |
|---|---|
resolved_project |
Project metadata and resolved module order |
output_path |
Absolute destination path |
initial_output_state |
Filesystem snapshot used to protect plan execution |
directories |
Directories that execution must create |
files |
Complete file operations without embedded content bytes |
removals |
Exact clean or replace removals |
docker_services |
Generated Compose service names |
env_variables |
Generated environment-variable names |
clean_output |
Whether cleanup was requested |
summary |
Stable operation counters and byte totals |
Each planned file includes:
{
"source_path": "/absolute/source/path",
"destination_path": "/absolute/output/backend/manage.py",
"relative_destination_path": "backend/manage.py",
"operation": "render",
"action": "create",
"module": "django",
"mode": 438,
"content_size": 684,
"content_sha256": "..."
}
Possible file actions are create, overwrite, and skip.
The summary reports:
- selected module count
- captured initial paths
- directories to create
- files to create, overwrite, or skip
- clean and replace removal counts
- Docker service and environment-variable counts
- total prepared bytes and bytes that execution would write
Resolution model
Capabilities and bindings
Modules communicate through typed capabilities.
A provider exposes values:
provides:
- capability: database.connection
values:
engine: postgresql
host: db
port: "{{ db_port }}"
name: "{{ db_name }}"
user: "{{ db_user }}"
password: "{{ db_password }}"
service: db
A consumer declares a named requirement:
requires:
- capability: database.connection
binding: primary_database
optional: false
unique: true
contract:
engine: string
host: string
port: int
name: string
user: string
password: string
service: string
The resolver validates the provider values against the requirement contract and exposes the result to templates as a typed binding:
{{ bindings.primary_database.host }}
{{ bindings.primary_database.port }}
{{ bindings.primary_database.service }}
When unique is false, the binding contains a list of matching providers.
Dependency graph
Bindings create dependency edges. Boilr uses the resulting graph to:
- order modules deterministically
- assemble providers before consumers
- place integration modules after the modules they connect
- reject dependency cycles
Extension points and contributions
A consumer can expose typed extension points:
extension_points:
python.dependencies:
type: list
merge: append_unique
default: []
django.settings:
type: dict
merge: deep_merge
default: {}
database.backend:
type: string
merge: replace
required: true
An integration module contributes through a declared binding:
contributions:
- target: backend
extension_point: python.dependencies
value:
- django-redis>=7.0,<8.0
- target: backend
extension_point: django.settings
value:
CACHES:
default:
BACKEND: django_redis.cache.RedisCache
LOCATION: "{{ bindings.cache.url }}"
Supported merge strategies:
| Value type | Strategies |
|---|---|
| Scalar | replace |
| List | replace, append, append_unique |
| Dictionary | replace, deep_merge |
Required extension points must receive a contribution. Incompatible contributions and duplicate replacements are reported before generation.
Planning and execution
Boilr separates generation into two phases:
manifest
|
v
validation and resolution
|
v
GenerationPlan
|
v
execute(plan)
|
v
generated project
Planning
Planning performs:
- manifest and module-input validation
- capability resolution and provider selection
- contribution merging
- dependency ordering
- template rendering
- copy-strategy resolution
- Docker Compose generation
- environment generation
- collision detection
- output-state capture
It returns a complete GenerationPlan without mutating the filesystem.
Execution
Execution applies only the prepared plan. It does not resolve modules, rerender templates, or regenerate Docker and environment configuration.
Before writing, execution verifies the captured initial output state. A stale or externally modified plan is rejected.
This separation provides two guarantees:
- A successful dry run describes every mutation that execution would perform.
- Executing an unchanged plan writes the exact prepared bytes and applies the planned removals and modes.
Copy strategies
| Strategy | Behavior |
|---|---|
merge |
Merge the source tree; source files replace matching destinations while unrelated destinations remain |
skip |
Leave an existing destination tree unchanged |
replace |
Remove the destination safely, then recreate it from the source |
Replace and clean removals are restricted to paths inside the project output directory.
Diagnostics and safety
All expected failures derive from BoilrError and expose structured context such as:
- a stable error code
- a human-readable message
- the affected module
- the manifest or generation field path
- machine-readable context
- an optional remediation suggestion
Boilr validates or protects:
- manifest structure
- duplicate and unknown modules
- unknown variables and options
- required values and input types
- provider versions and tag criteria
- missing and ambiguous capabilities
- binding contracts
- dependency cycles
- extension-point targets, types, and conflicts
- template rendering
- generated file collisions
- environment-variable names and values
- source reads and output writes
- output, clean, and replacement path boundaries
- symbolic links and unsupported filesystem entries
- stale generation plans
Use --debug to include the complete traceback for unexpected failures. Without it, the CLI renders the structured Boilr diagnostic.
Canonical exceptions are exported from:
from boilr_generator.exceptions import BoilrError
Compatibility exports from older exception modules remain available temporarily, but new code should use the canonical module.
Internal architecture
project.yml
|
v
ProjectManifest
|
v
ModuleRegistry
|
v
Resolver
|-- capabilities and requirements
|-- provider selections
|-- typed bindings
|-- dependency graph
|-- extension points
└── contributions
|
v
ResolvedProject
|
v
GenerationPlan
|
v
ProjectGenerator.execute(plan)
|
v
Generated project
Technology-specific behavior belongs in modules and integrations. The resolver and generation engine remain framework-agnostic.
Creating a module
Built-in modules are stored by category:
boilr_generator/templates/
|-- backend/
| └── django/
|-- database/
| └── postgres/
|-- cache/
| └── redis/
└── integration/
|-- django-postgres/
└── django-redis/
A module contains a module.yml manifest and optional source files:
module/
|-- module.yml
└── files/
The module manifest can define:
| Section | Purpose |
|---|---|
meta |
Name, key, type, version, description, and tags |
role |
Module role group |
dependencies |
Base and optional generated dependencies |
provides |
Capabilities exposed by the module |
requires |
Named capability requirements and contracts |
extension_points |
Typed contribution targets |
contributions |
Values contributed through bindings |
variables |
Required and defaulted module variables |
options |
Optional module features |
assembly |
Priority and destination root |
sources.copy |
File trees copied with a declared strategy |
sources.render |
Jinja templates and destinations |
docker |
Compose services and volumes |
exports.env |
Generated environment variables |
docs |
Inline module summary and notes |
Integration modules often have no source files. They connect other modules by requiring their capabilities and contributing dependencies or configuration through extension points.
When adding a module:
- keep technology-specific decisions outside the core engine
- declare all variables, options, and capability contracts
- use an integration module for cross-technology behavior
- add resolver, generation, and conflict tests
- add a real Docker E2E scenario when practical
Open an issue before starting a large module contribution.
Development and testing
Install development dependencies:
uv sync --locked --all-groups
Run the complete test suite:
uv run --locked --all-groups python -m pytest -q
Run Ruff:
uv run --locked --all-groups ruff check .
Build the wheel and source distribution:
uv build
Run the Docker E2E test on Linux or macOS:
BOILR_RUN_DOCKER_E2E=1 uv run --locked --all-groups python -m pytest -q tests/e2e/test_docker_stack.py -m docker_e2e -W error
Run it in Windows PowerShell:
$env:BOILR_RUN_DOCKER_E2E = "1"
uv run --locked --all-groups python -m pytest -q tests\e2e\test_docker_stack.py -m docker_e2e -W error
Remove-Item Env:BOILR_RUN_DOCKER_E2E
CI validates:
- Python 3.11, 3.12, 3.13, and 3.14
- Ubuntu and Windows
- the complete test suite
- Ruff
- wheel and source-distribution contents
- installation of the wheel outside the source tree
- built-in module discovery from the installed wheel
- generation from the installed package
- a real Django, PostgreSQL, and Redis Docker stack
Project status and roadmap
Version 0.1.1 is functional and under active development.
The current engine supports:
- strict manifest and module-input validation
- declarative capability resolution
- advanced provider selection
- typed contributions
- deterministic and exhaustive planning
- safe plan execution
- Dockerized Django, PostgreSQL, and Redis generation
- cross-platform CI and distribution verification
- deterministic generated-project state
- read-only drift observation
- explicit resource-move reconciliation
- safe state-aware project updates
- dependency-aware module lifecycle transitions
- atomic state transactions
Potential future work includes:
- a Django API around the generator
- a web interface
- a module marketplace
- additional backend and frontend modules
- MySQL, MongoDB, and message-queue providers
- reverse-proxy and deployment modules
Contributing
Contributions are welcome. See CONTRIBUTING.MD for development and contribution guidance.
Useful contributions include:
- new modules and integrations
- examples
- documentation
- cross-platform tests
- diagnostics and safety improvements
Why Boilr exists
Modern projects repeatedly need the same foundations: an application framework, database, cache, environment configuration, containers, and service orchestration.
Maintaining a full template for every possible combination does not scale. Boilr instead treats each technology as a reusable module and each cross-technology decision as an explicit integration.
The long-term goal is an interface-agnostic ecosystem in which a CLI, API, web application, or automation can produce the same manifest and rely on the same deterministic generator core.
License
Boilr is distributed under the MIT License.
Metadata
Release files for boilr-generator 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| boilr_generator-0.1.1.tar.gz | 152.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| boilr_generator-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 262.3 kB
Release files / boilr_generator-0.1.1.tar.gz
| Download URL | boilr_generator-0.1.1.tar.gz |
|---|---|
| Size | 152.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0f2e8e925dbb23d0227e6009d71c0b2177e04e1fc5d10e620515f783e2b8807b
|
|
BLAKE2b-256 checksum How to use checksums |
f177259ea583cd611c5848ba31a8b39d1aea65a4ad7283c5c9c4b6a43436df02
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency logRelease files / boilr_generator-0.1.1-py3-none-any.whl
| Download URL | boilr_generator-0.1.1-py3-none-any.whl |
|---|---|
| Size | 109.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
af4e394a7ed565a338b8adfd7c4b41886f8db113acec4e53af5e036e0c8e11b0
|
|
BLAKE2b-256 checksum How to use checksums |
8fd6c69e703a9bb09fcd5c9bd08a9966da7823a1d27f4eec653664985034ea56
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency log