Authoring templates
A deckctl template is an ordinary directory with a deckctl.yaml sidecar in it.
Nothing else is required.
Layout
Section titled “Layout”go-service/ deckctl.yaml # required — makes this directory a template main.go.tmpl # rendered → main.go README.md.jinja # rendered → README.md LICENSE # copied verbatim media/logo.png # copied verbatim (binary preserved) src/ handler.go.j2 # rendered → src/handler.go config.yaml # copied verbatim — any {{ }} stays literalDirectory structure is preserved exactly; only the template suffix is stripped from rendered filenames.
The sidecar
Section titled “The sidecar”delimiters: variable_start: "[[" variable_end: "]]" block_start: "[%" block_end: "%]" comment_start: "[#" comment_end: "#]"
template_suffixes: - ".tmpl" - ".jinja"
subdirectory: template
variables: - name: app_name description: "Name of the application" type: str default: "myapp"
- name: port description: "HTTP listen port" type: int default: 8080
- name: enable_metrics description: "Expose /metrics" type: bool default: true
- name: runtime description: "Runtime to target" type: choice choices: ["go", "python", "node"] default: "go"Every top-level key is optional. The full schema — including validation rules — is in the sidecar reference.
| Key | Purpose |
|---|---|
delimiters | Override the Jinja2 delimiters. Only set what you need to change. |
template_suffixes | Replace the default .tpl, .tmpl, .jinja, .j2 list. |
subdirectory | Render from a subdirectory of the template root instead of the root. |
variables | What the template expects. |
Render vs. copy
Section titled “Render vs. copy”The suffix decides, not the content:
| Source file | What happens | Output |
|---|---|---|
main.go.tpl | Rendered, suffix stripped | main.go |
Dockerfile.tmpl | Rendered, suffix stripped | Dockerfile |
README.md.jinja | Rendered, suffix stripped | README.md |
LICENSE | Copied verbatim | LICENSE |
media/logo.png | Copied verbatim (binary) | media/logo.png |
helm/values.yaml | Copied verbatim, {{ }} stays literal | helm/values.yaml |
Setting template_suffixes replaces the default list rather than adding to
it:
template_suffixes: - ".tmpl" # now ONLY .tmpl triggers rendering; .j2 files are copiedThe subdirectory key
Section titled “The subdirectory key”By default deckctl renders the whole template directory, sidecar included. Set
subdirectory to keep the template tree separate from the repository’s own
files:
go-service/ deckctl.yaml # subdirectory: template README.md # about the template itself — never rendered or copied .gitlab-ci.yml # CI for the template repo — never rendered or copied template/ # ← everything below here is what gets applied main.go.tmpl go.mod.tmplThis is the same idea as Copier’s _subdirectory, and a copier.yml’s
_subdirectory maps onto it directly.
Jinja2 syntax
Section titled “Jinja2 syntax”Rendering goes through gonja, a Go implementation of Jinja2.
{{ app_name }}{{ version | default("0.1.0") }}{{ app_name | upper }}{{ items | join(", ") }}{% if enable_metrics %}import _ "net/http/pprof"{% endif %}
{% for dep in dependencies %}- {{ dep }}{% endfor %}
{# this comment never reaches the output #}Template inheritance ({% extends %} / {% block %}) works too.
Test it before you publish
Section titled “Test it before you publish”# What does the template claim to need?deckctl template variables go-service --source local
# Does the sidecar parse and validate?deckctl validate --target sidecar
# What would an apply produce?deckctl template apply go-service /tmp/out --source local --dry-run--dry-run prints the full rendered content of every file plus the copy list,
and writes nothing.
See also
Section titled “See also”- Variables and prompting — types, defaults, resolution order
- Custom delimiters — Helm- and Terraform-safe templates
- Copier compatibility — reuse existing Copier templates
- Sidecar reference — the complete schema