Templates
Render configuration files and scripts with Jinja before they are built into images or uploaded to the server.
Some files need values that depend on the environment: an nginx vhost with the environment's domain, an entrypoint script with a worker count. List them under templates:, and Dockflow renders them with Jinja before it builds images and uploads files.
The rendering happens in a temporary copy. Your project is never modified.
Example
version: 1
app: shop
templates:
- src: nginx/site.conf.j2
dest: nginx/site.conf
- app/entrypoint.sh
environments:
production:
vars:
DOMAIN: shop.example.com
WORKERS: "4"
staging:
vars:
DOMAIN: staging.shop.example.com
WORKERS: "2"services:
proxy:
build: ./nginx
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost/healthz"]
x-dockflow:
routes:
- domains: ["${DOMAIN}"]
port: 80
app:
build: ./appThe template uses Jinja's {{ }} and {% %}, so nginx's own $host stays as it is:
server {
listen 80;
server_name {{ DOMAIN }};
location /healthz {
return 200;
}
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://app:8080;
{%- if DOCKFLOW_ENV != "production" %}
add_header X-Robots-Tag "noindex";
{%- endif %}
}
}FROM nginx:1.27-alpine
COPY site.conf /etc/nginx/conf.d/default.confnginx/site.conf does not exist in the project. It exists only in the copy the build reads, with the values of the environment being deployed.
Entries
Each entry of templates: is one of:
| Form | Meaning |
|---|---|
src: <path> and dest: <path> | Renders src and writes the result at dest in the temporary copy |
<path> | Renders the file in place: the copy holds the rendered file at the same path |
Both paths are relative to the project directory and must stay inside it. Only the files listed are rendered: nothing else is ever treated as a template.
templates: can also be set inside an environment. There, it replaces the top-level list for that environment.
When templates are rendered
The commands that read build contexts or upload sources render templates, once per command:
dockflow deployanddockflow plan, after thebefore_buildhooks, and before the images are built and the uploads are described;dockflow build.
dockflow validate does not render templates. To catch a template error before a deploy, run dockflow build (when the app builds images) or dockflow plan.
Only builds and uploads read rendered files. Hooks run from the project directory and see the source file, not the rendered one. The exception is a hook script: that is itself listed under templates:: it runs rendered (see Hooks).
Variables
A template sees the same values as the rest of the configuration, as top-level variables:
| Variables | Examples |
|---|---|
| Built-in variables | {{ DOCKFLOW_APP }}, {{ DOCKFLOW_ENV }}, {{ DOCKFLOW_STACK }}, {{ DOCKFLOW_VERSION }}, {{ DOCKFLOW_GIT_SHA }}, {{ DOCKFLOW_GIT_BRANCH }}, {{ DOCKFLOW_SERVER }} |
| The environment's secrets, by their unprefixed name | {{ API_KEY }} for PRODUCTION_API_KEY |
The environment's vars | {{ DOMAIN }} |
When a name exists in several places, a built-in variable wins over a secret, and a secret over vars. See environment variables for where secrets come from.
Every value is a string. Compare with strings (WORKERS == "4"), or convert first (WORKERS | int).
Secrets in rendered files
A secret rendered into a file ends up in that file: inside the image when the file is part of a build context, or on the server when it is uploaded. Render secrets only into files meant to hold them.
Undefined variables are errors
A variable with no value stops the command with exit code 3, naming the template:
error: templates: nginx/site.conf.j2 cannot be rendered: …
hint: every variable must be defined: built-in, secret or vars of the environmentDockflow never silently substitutes an empty value. For an optional value, give a default:
worker_processes {{ WORKERS | default("2") }};A file listed under templates: that does not exist is also an error.
Jinja basics
The engine is gonja, a Go implementation of Jinja.
| Syntax | Use |
|---|---|
{{ NAME }} | Insert a value |
{{ NAME | default("x") }}, | upper, | lower, | int, | replace("a", "b") | Filters |
{% if DOCKFLOW_ENV == "production" %}…{% endif %} | Conditions |
{% for host in ["a", "b"] %}…{% endfor %} | Loops |
{%- … %}, {{- … }} | Remove the whitespace before the tag (or after it, with -%}) |
{% raw %}…{% endraw %} | Leave the content as written |
{% raw %} protects text that uses {{ }} for another tool, such as a Go template in an alerting rule:
# prometheus/rules.yml (listed under templates:)
groups:
- name: {{ DOCKFLOW_STACK }}
rules:
- alert: InstanceDown
expr: up == 0
annotations:
{% raw %} summary: "{{ $labels.instance }} is down"{% endraw %}How the copy is made
Dockflow renders every template first, then builds a temporary directory:
- Each build context and each upload source that holds a rendered file is copied into it, with hard links where the filesystem allows. The copy leaves out
.gitand what the context's.dockerignoreexcludes, since the build would not read them. - The rendered files are written into the copy and replace the hard links, so the source files are never written to.
- Build contexts and upload sources without a rendered file are read in place, without a copy.
The build and the uploads read from the copy. It is deleted when the command ends.
File modes
A file rendered in place keeps its mode, so an executable entrypoint.sh stays executable.
A file rendered to a new dest takes the mode of the file at dest if the project has one, and 644 otherwise. For a script that must stay executable, render it in place instead, or set the mode in the Dockerfile (COPY --chmod=755).