This file provides guidance to AI Agents when working with code in this repository.
This repository contains a sophisticated collection of templates (called boilerplates) for managing infrastructure across multiple technologies including Terraform, Docker, Ansible, Kubernetes, etc. The project also includes a Python CLI application that allows an easy management, creation, and deployment of boilerplates.
The CLI is a Python application built with Typer for the command-line interface and Jinja2 for templating.
cli/ - Python CLI application source code
cli/core/ - Core functionality (app, config, commands, logging)cli/modules/ - Technology-specific modules (terraform, docker, compose, config, etc.)library/ - Template collections organized by module
library/ansible/ - Ansible playbooks and configurationslibrary/compose/ - Docker Compose configurationslibrary/docker/ - Docker templateslibrary/kubernetes/ - Kubernetes deploymentslibrary/packer/ - Packer templateslibrary/terraform/ - OpenTofu/Terraform templates and examples# List available commands
python3 -m cli
# List templates for a module
python3 -m cli compose list
# Debugging commands
python3 -m cli --log-level DEBUG compose list
# Generate template to directory named after template (default)
python3 -m cli compose generate nginx
# Generate template to custom directory
python3 -m cli compose generate nginx my-nginx-server
# Generate template interactively (default - prompts for variables)
python3 -m cli compose generate authentik
# Generate template non-interactively (skips prompts, uses defaults and CLI variables)
python3 -m cli compose generate authentik my-auth --no-interactive
# Generate with variable overrides (non-interactive)
python3 -m cli compose generate authentik my-auth \
--var service_name=auth \
--var ports_enabled=false \
--var database_type=postgres \
--no-interactive
# Show template details
python3 -m cli compose show authentik
# Managing default values
python3 -m cli compose defaults set service_name my-app
python3 -m cli compose defaults get
python3 -m cli compose defaults list
The CLI application is built with a modular and extensible architecture.
cli/__main__.py: The main entry point using Typer. It dynamically discovers and imports modules from the cli/modules directory, registering their commands with the main application.
cli/core/registry.py: Provides a ModuleRegistry which acts as a central store for all discovered module classes. This avoids magic and keeps module registration explicit.
cli/core/module.py: Defines the abstract Module base class. Each technology (e.g., compose, terraform) is a subclass of Module. It standardizes the list, search, show, and generate commands and handles their registration.
cli/core/library.py: Implements the LibraryManager and Library classes, which are responsible for finding template files within the library/ directory. It supports a priority system, allowing different template sources to override each other.
cli/core/template.py: Contains the Template class, which is the heart of the engine. It parses a template file, separating the YAML frontmatter (metadata, variable specifications) from the Jinja2 content. It intelligently merges variable definitions from both the module and the template file.
cli/core/variables.py: Defines the data structures for managing variables:
Variable: Represents a single variable, including its type, validation rules, and default value.VariableSection: Groups variables into logical sections for better presentation and conditional logic.VariableCollection: Manages the entire set of sections and variables for a template.cli/core/prompt.py: The PromptHandler provides the interactive CLI experience. It uses the rich library to prompt the user for variable values, organized by the sections defined in the VariableCollection.
cli/core/display.py: The DisplayManager handles all output rendering using rich. It provides consistent and visually appealing displays for lists, search results, variable summaries, and error messages.
Templates are directory-based. Each template is a directory containing all the necessary files and subdirectories for the boilerplate.
Every template directory must contain a main template file named either template.yaml or template.yml. This file serves as the entry point and contains the template's metadata and variable specifications in YAML frontmatter format.
Example template.yaml:
---
kind: compose
metadata:
name: My Nginx Template
description: >
A template for a simple Nginx service.
Project: https://...
Source: https://
Documentation: https://
version: 0.1.0
author: Christian Lempa
date: '2024-10-01'
spec:
general:
vars:
nginx_version:
type: string
description: The Nginx version to use.
default: latest
Jinja2 Templates (.j2): Any file within the template directory that has a .j2 extension will be rendered by the Jinja2 engine. The .j2 extension is removed from the final output file name (e.g., config.json.j2 becomes config.json). These files can use {% include %} and {% import %} statements to share code with other files in the template directory.
Static Files: Any file without a .j2 extension is treated as a static file and will be copied to the output directory as-is, preserving its relative path and filename.
Content Sanitization: All rendered Jinja2 templates are automatically sanitized to improve output quality:
library/compose/my-nginx-template/
├── template.yaml
├── compose.yaml.j2
├── config/
│ └── nginx.conf.j2
└── static/
└── README.md
Variables are a cornerstone of the CLI, allowing for dynamic and customizable template generation. They are defined and processed with a clear precedence and logic.
1. Definition and Precedence:
Variables are sourced and merged from multiple locations, with later sources overriding earlier ones:
spec (Lowest Precedence): Each module (e.g., cli/modules/compose.py) can define a base spec dictionary. This provides default variables and sections for all templates of that kind.spec: The spec block within the template.yaml or template.yml file can override or extend the module's spec. This is the single source of truth for defaults within the template.--var) (Highest Precedence): Providing a variable via the command line (--var KEY=VALUE) has the highest priority and will override any default or previously set value.The Variable.origin attribute is updated to reflect this chain (e.g., module -> template -> cli).
2. Required Sections:
spec can be marked as required: true.general section is implicitly required by default.3. Toggle Settings (Conditional Sections):
toggle property to the name of a boolean variable within that same section.toggle: "advanced_enabled"bool. This is validated at load-time by VariableCollection._validate_section_toggle().false), all other variables within that section are skipped, and the section is visually dimmed in the summary table. This provides a clean way to manage optional or advanced configurations.4. Section Dependencies:
needs property.needs: "traefik" or needs: ["database", "redis"] for multiple dependencies.⊘ Section Name (skipped - requires dependency_name to be enabled).traefik (basic) and traefik_tls (needs traefik) sections.database_backup (needs database) or monitoring_alerts (needs monitoring).email (basic) and email_advanced (needs email) sections.Example Section with Dependencies:
spec:
traefik:
title: "Traefik"
toggle: "traefik_enabled"
vars:
traefik_enabled:
type: "bool"
default: false
traefik_host:
type: "hostname"
traefik_tls:
title: "Traefik TLS/SSL"
needs: "traefik" # Only shown if traefik is enabled
toggle: "traefik_tls_enabled"
vars:
traefik_tls_enabled:
type: "bool"
default: true
traefik_tls_certresolver:
type: "str"
We use a convention to manage TODO items as GitHub issues directly from the codebase. This allows us to track our work and link it back to the specific code that needs attention.
The format for a TODO item is:
TODO[<issue-number>-<slug>] <description>
<issue-number>: The GitHub issue number.<slug>: A short, descriptive slug for the epic or feature.<description>: The description of the TODO item.When you find a TODO item that has not been converted to an issue yet (i.e., it's missing the [<issue-number>-<slug>] part), you can create an issue for it using the gh CLI:
gh issue create --title "<title>" --body "<description>" --assignee "@me" --project "<project-name>" --label "<label>"
After creating the issue, update the TODO line in the AGENTS.md file with the issue number and a descriptive slug.
DEBUG when they should use INFO, and vice versa.list command). This is wasteful when listing many templates.cli/core/display.py to standardize icons used throughout the CLI for file types (.yaml, .j2, .json, etc.), status indicators (success, warning, error, info), and UI elements. This would improve consistency, make icons easier to maintain, and allow for theme customization.template.yaml or template.yml fileconfig/)config module instead of using complex directory structuresdescription, default, or extra properties in the template spec rather than creating a new variable.external_url, smtp_port)type and provide sensible default valuessensitive: true and autogenerated: true for auto-generated secretspwgen filter for password generation: {{ secret_key if secret_key else (none | pwgen(50)) }}Example: For the Traefik template, use the existing authentik section from the module spec instead of creating custom authentik_middleware_* variables. Override the section's description and extra to provide Traefik-specific guidance.
.j2 extension and always use | default() filter for safe fallbacksservice_name, container_name, container_timezone, restart_policyauthentik_secret_key, gitea_root_url)database_type, database_enabled, database_external, database_host, database_port, database_name, database_user, database_passwordnetwork_enabled, network_name, network_externaltraefik_enabled, traefik_host, traefik_tls_enabled, traefik_tls_entrypoint, traefik_tls_certresolverports_enabled, ports_http, ports_https, ports_sshemail_enabled, email_host, email_port, email_username, email_password, email_fromUse separate .env.{service}.j2 files for different services (e.g., .env.authentik.j2, .env.postgres.j2):
env_file directive in compose.yaml.j2# Database Connection)database_enabled, email_enabled, traefik_enabled, ports_enabled, network_enableddepends_on for startup ordering and use named volumes for persistence{% if not database_external %} for conditional service creationrestart: {{ restart_policy | default('unless-stopped') }}