Generating Ansible Jinja2 Templates With AI Safely
Jinja2 templates are where Ansible gets powerful and dangerous. Here's how I use AI to generate templates without shipping broken config to prod.
- #iac
- #ansible
- #ai
- #jinja2
- #templates
Jinja2 templates are where Ansible stops being a list of tasks and starts being a real config generator. A good .j2 template renders the same nginx config across forty hosts with per-host tweaks, no copy-paste. A bad one renders subtly broken config that passes the play and breaks the service. Templates are powerful, fiddly, and exactly the kind of structured text generation AI is fast at — which is great until the AI confidently produces a template with a logic bug you can’t see until it’s live.
So I treat AI as a fast junior engineer for template work: it drafts the Jinja2 quickly, but I review the rendered output, not just the template source, and I never let a template reach prod without seeing exactly what it produces.
Why templates are uniquely risky
A broken task usually fails loudly. A broken template usually succeeds — it renders some text, writes it to a file, and the failure shows up later when a service won’t start or, worse, starts with wrong behavior. The gap between “the play passed” and “the config is correct” is wide, and Jinja2’s flexible-but-loose semantics make it easy to fall into.
Let AI draft, but give it the variable shape
AI writes good Jinja2 when it knows the data it’s templating over. So I give it the variable structure (with fake values) up front:
“Generate an nginx Jinja2 template for Ansible. It iterates over a list
app_upstreams, each item hashostandport. It conditionally enables TLS whentls_enabledis true, using cert paths fromtls_certandtls_key. Use whitespace control so the rendered output is clean.”
A solid result:
upstream app_backend {
{% for upstream in app_upstreams %}
server {{ upstream.host }}:{{ upstream.port }};
{% endfor %}
}
server {
listen {{ '443 ssl' if tls_enabled else '80' }};
server_name {{ server_name }};
{% if tls_enabled %}
ssl_certificate {{ tls_cert }};
ssl_certificate_key {{ tls_key }};
{% endif %}
location / {
proxy_pass http://app_backend;
}
}
This is correct, but I’m reading it skeptically. The loop, the conditional listen directive, the TLS block — each is a place a logic error hides.
The whitespace trap
The single most common Jinja2 bug AI produces is whitespace. Without {%- and -%} trim markers, loops leave blank lines and broken indentation that some config parsers tolerate and others reject. I specifically ask AI to use whitespace control and then I check it:
{% for upstream in app_upstreams -%}
server {{ upstream.host }}:{{ upstream.port }};
{% endfor %}
The -%} strips the newline after the tag. Whether that’s correct depends on the target format — YAML cares deeply about whitespace, nginx config mostly doesn’t. AI doesn’t know which format is sensitive unless I tell it, so I tell it.
Pro Tip: For any template targeting a whitespace-sensitive format (YAML, Python, Makefiles), render it and pipe the output through that format’s own validator. A template that “looks right” in source can produce invalid YAML, and only the rendered output reveals it.
Always render before you deploy
This is the rule that keeps broken templates out of prod: I render the template and read the actual output before any host gets it. The cleanest way is a check-mode run with diff:
ansible-playbook site.yml --check --diff --limit staging-01 --tags nginx
--diff prints the rendered template and shows exactly what would be written. I read that output as if it were hand-written config, because to the service it is. For quick iteration I’ll also render a template standalone against test data:
ansible localhost -m template \
-a "src=templates/nginx.conf.j2 dest=/tmp/rendered.conf" \
-e "@test_vars.yml"
Then I actually open /tmp/rendered.conf and validate it with the real tool (nginx -t, yamllint, whatever applies). Reviewing the template source is not enough — the rendered output is the thing that ships.
Guard against undefined variables
A template that references an undefined variable can render an empty string and produce silently-wrong config. I set ansible.cfg to fail loudly:
[defaults]
error_on_undefined_vars = True
And I ask AI to use explicit defaults where a value is genuinely optional:
worker_processes {{ nginx_workers | default('auto') }};
That default filter is intentional — it says “this is allowed to be absent.” Everything else should fail if undefined, because a missing required value is a bug I want to hear about at render time, not at 3am.
Never template real secrets through AI
Templates often inject secrets — TLS keys, DB passwords, API tokens. The variable names are fine to share with AI; the values never are. When I draft a template that references tls_key, the AI sees the variable name and reasons about the structure. The actual key lives in ansible-vault and is decrypted only on the target host at render time:
ssl_certificate_key {{ tls_key_path }}; # path, not the key material
I prefer templating the path to a secret file over templating the secret content directly, which keeps the secret out of the rendered file’s diff entirely. And I never paste decrypted vault content or my vault password into a prompt.
Make rendered output reviewable
Because the rendered file is what matters, I make it easy to review. In testing I commit a sample rendering alongside the template so a human can diff “what the template produces” in code review, not just the Jinja2 source. The code review dashboard helps surface the risky bits of a config diff, and I keep my template-generation prompts in the prompt workspace.
Jinja2 is where AI’s speed and AI’s overconfidence collide. It drafts templates fast and they usually look right — but “looks right in source” and “renders correct config” are different claims, and only the rendered output settles it. Draft with AI, render everything, validate with the real tool, and keep your secrets out of the prompt. The rest of this series lives in the IaC category, and ChatGPT and Claude both handle Jinja2 generation well.
Generate fast, render always, review the output. That’s how templates stay safe.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.