Skip to content

Authoring templates

A deckctl template is an ordinary directory with a deckctl.yaml sidecar in it. Nothing else is required.

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 literal

Directory structure is preserved exactly; only the template suffix is stripped from rendered filenames.

deckctl.yaml
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.

KeyPurpose
delimitersOverride the Jinja2 delimiters. Only set what you need to change.
template_suffixesReplace the default .tpl, .tmpl, .jinja, .j2 list.
subdirectoryRender from a subdirectory of the template root instead of the root.
variablesWhat the template expects.

The suffix decides, not the content:

Source fileWhat happensOutput
main.go.tplRendered, suffix strippedmain.go
Dockerfile.tmplRendered, suffix strippedDockerfile
README.md.jinjaRendered, suffix strippedREADME.md
LICENSECopied verbatimLICENSE
media/logo.pngCopied verbatim (binary)media/logo.png
helm/values.yamlCopied verbatim, {{ }} stays literalhelm/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 copied

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.tmpl

This is the same idea as Copier’s _subdirectory, and a copier.yml’s _subdirectory maps onto it directly.

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.

Terminal window
# 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.