Skip to Content
⚠️ Dockflow is currently under development. Bugs may occur. Please report any issues on GitHub.

Templates

Dockflow renders files with Nunjucks  templating before deployment, injecting environment-specific values into your configuration.

Automatic Rendering

All files inside .dockflow/ are automatically rendered with Nunjucks during deployment. No configuration needed.

# .dockflow/docker/docker-compose.yml — automatically rendered services: app: environment: DOMAIN: {{ current.env.domain_name }} ports: - {{ current.env.app_port }}:3000

To serve an app behind the host’s nginx, use the built-in nginx plugin.

Custom Templates

For files outside .dockflow/, declare them in .dockflow/config.yml. Paths are relative to the project root:

# .dockflow/config.yml templates: - "config/application.yml" - "nginx/nginx.conf" - "scripts/init.sh"

These files are rendered in-place. To output to a different location, use the extended format:

# .dockflow/config.yml templates: - "scripts/init.sh" # Rendered in-place - src: "config/app.yml.njk" # Source → different destination dest: "config/app.yml"

Available Variables

Basic Variables

VariableDescriptionExample
{{ env }}Target environmentproduction
{{ version }}Deployment version1.2.3
{{ branch }}Git branch namemain
{{ config.project_name }}Project name from configmy-app

Current Server ({{ current }})

The current object contains information about the server being deployed to:

VariableDescriptionExample
{{ current.name }}Server name from servers.ymlmanager_1
{{ current.host }}Server IP or hostname192.168.1.10
{{ current.port }}SSH port22
{{ current.user }}SSH userdockflow
{{ current.role }}Server rolemanager or worker
{{ current.tags }}Server tags['production']
{{ current.env.my_var }}Environment variable (lowercase key)my-value

All environment variable keys must be lowercase in templates. Even if you define DB_HOST in servers.yml, you must reference it as {{ current.env.db_host }}.

An undefined variable renders as an empty string. Deploy, build and --dry-run warn about every current.env reference a file writes out whose key is not defined for the target server:

.dockflow/hooks/key.sh:4: current.env.deploy_key is not defined for main_server and renders as an empty string.

This matters most for a secret that exists in CI but not in your local .env.dockflow: deployed from your machine, it silently becomes empty. A reference guarded by default or or, or only tested in a condition, is treated as optional and not reported.

Config ({{ config }})

All values from your config.yml:

database: {{ config.project_name }}-db

All Servers ({{ servers }})

All servers in the current environment, keyed by name. Each server has the same properties as current.

Cluster Metadata ({{ cluster }})

VariableDescriptionExample
{{ cluster.size }}Total number of nodes5
{{ cluster.manager_count }}Number of managers1
{{ cluster.worker_count }}Number of workers4
{{ cluster.managers }}List of manager IPs['192.168.1.10']
{{ cluster.workers }}List of worker IPs['192.168.1.11', '192.168.1.12']

Docker build args

Template variables also work inside build.args in your docker-compose.yml. Since the compose file is rendered before the build runs, the values are already resolved when docker build executes:

services: api: build: args: SENTRY_DSN: "{{ current.env.sentry_dsn }}" APP_VERSION: "{{ version }}"

Build args are visible in docker history. Use them for non-sensitive config (URLs, versions, feature flags). For secrets that must not appear in image layers, inject them at runtime via environment: instead.

Template Syntax

Conditionals

logging: {% if env == 'production' %} level: "warn" format: "json" {% else %} level: "debug" format: "pretty" {% endif %} features: debug_toolbar: {{ 'true' if env != 'production' else 'false' }}

Default values

database: pool_size: {{ current.env.db_pool_size | default(10) }} timeout: {{ current.env.db_timeout | default(30) }}

Multi-line and special values in YAML

A value is inserted as plain text. In a YAML file, one that holds a line break, a quote or a # — a private key, a generated password — breaks the file, whatever quotes surround it. The dump filter writes the value as a JSON string, which is valid YAML and keeps it intact. It adds its own quotes, so write none:

ssh_key: {{ current.env.ssh_private_key | dump }}

When the value sits inside a longer string, build the whole string first, then dump it:

database_url: {{ ("postgres://app:" ~ current.env.db_password ~ "@db:5432/app") | dump }}

In docker-compose.yml, Docker then substitutes $VAR itself: a $ in a value must be written $$.

Loops

upstream backend { {% for name, srv in servers.items() %} {% if 'app' in srv.tags %} server {{ srv.host }}:{{ srv.env.app_port | default(3000) }}; {% endif %} {% endfor %} }

Execution Order

Templates are rendered early in the deployment process:

  1. Load configuration (config.yml, servers.yml)
  2. Render all files in .dockflow/ (docker-compose, Dockerfiles, etc.)
  3. Render custom templates from config.yml
  4. Auto-tag Docker images in docker-compose.yml (if image_auto_tag is enabled)
  5. Build Docker images
  6. Deploy to swarm

Templates are rendered before the Docker build, so the rendered files will be included in your Docker images if they are in the build context.

Examples

These are example template files you could create in your project.

Application config (e.g. config/application.yml)

database: host: "{{ current.env.db_host }}" port: {{ current.env.db_port | default(5432) }} name: "{{ current.env.db_name }}" password: "{{ current.env.db_password }}" server: environment: "{{ env }}" version: "{{ version }}"

Nginx reverse proxy (e.g. nginx/myapp.conf.njk)

server { listen 80; server_name {{ current.env.domain_name }}; location / { proxy_pass http://127.0.0.1:{{ current.env.app_port }}; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

Init script (e.g. scripts/init.sh)

#!/bin/bash echo "Initializing {{ config.project_name }} v{{ version }}" echo "Environment: {{ env }}" {% if env == 'production' %} echo "Running production optimizations..." {% else %} echo "Running in development mode" {% endif %}

PostgreSQL access control (e.g. config/pg_hba.conf.njk)

{% for name, srv in servers.items() %} {% if srv.role == 'worker' or 'app' in srv.tags %} host all all {{ srv.host }}/32 md5 {% endif %} {% endfor %}