Dans cet article
- Les templates Ansible reposent sur le moteur Jinja2 et permettent de générer dynamiquement n’importe quel fichier de configuration
- Le module ansible.builtin.template copie un fichier .j2 vers l’hôte distant en résolvant toutes les variables au passage
- La différence clé entre copy et template : copy transfère un fichier statique, template interprète les expressions Jinja2
- Les filtres Jinja2 les plus utiles (default, join, regex_replace) couvrent plus de 80 % des cas d’usage courants
- Organiser ses templates dans un dossier templates/ au même niveau que tasks/ est la convention recommandée par Ansible
- Combinés à Ansible Vault, les templates permettent d’injecter des secrets sans les exposer en clair dans le dépôt
Sommaire
- Comprendre les templates Ansible et Jinja2
- Le module ansible.builtin.template en détail
- Différence entre copy et template dans Ansible
- Syntaxe Jinja2 : variables, filtres et conditions
- Exemples pratiques de templates Ansible
- Organisation du répertoire templates et bonnes pratiques
- Templates avancés : boucles, includes et macros
- Déboguer ses templates : erreurs courantes et solutions
- Tableau comparatif des paramètres du module template
Quand on gère une dizaine de serveurs, copier manuellement des fichiers de configuration devient vite ingérable. Je l’ai constaté dès mes premiers projets d’automatisation : chaque environnement (développement, staging, production) nécessite des valeurs différentes pour les mêmes fichiers. C’est exactement le problème que résolvent les templates Ansible. En combinant le moteur de rendu Jinja2 avec la puissance des playbooks, vous pouvez générer dynamiquement n’importe quel fichier texte, du simple nginx.conf au docker-compose.yml le plus complexe.
Dans ce guide, je vous accompagne pas à pas pour maîtriser les templates Ansible. On part des fondamentaux jusqu’aux techniques avancées, avec du code que vous pourrez réutiliser directement dans vos projets.
Comprendre les templates Ansible et Jinja2
Un template Ansible est un fichier texte contenant des expressions Jinja2 (délimitées par {{ }} pour les variables et {% %} pour la logique). Lorsque Ansible exécute un playbook, il remplace ces expressions par les valeurs réelles des variables définies dans l’inventaire, les rôles ou les group_vars.
Le moteur Jinja2, développé par le projet Pallets, est un moteur de templates Python très répandu. Ansible l’utilise en interne pour résoudre toutes les expressions, y compris celles présentes directement dans les playbooks. Mais c’est dans les fichiers .j2 que cette fonctionnalité prend tout son sens.
Concrètement, voici le flux de travail :
- Vous créez un fichier template avec l’extension
.j2(par convention) - Vous utilisez le module template dans votre playbook pour déployer ce fichier
- Ansible résout les variables, exécute la logique conditionnelle, puis copie le résultat sur l’hôte distant
L’avantage principal ? Un seul fichier template suffit pour générer des configurations adaptées à chaque serveur. Si votre application doit écouter sur le port 8080 en développement et 443 en production, une simple variable {{ app_port }} dans le template règle le problème.

Le module ansible.builtin.template en détail
Le module ansible.builtin.template est le cœur du système. Il fonctionne de manière similaire au module Ansible copy, mais avec une étape de rendu Jinja2 en plus. Voici sa syntaxe de base :
- name: Déployer la configuration Nginx
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
notify: Restart Nginx
Les paramètres essentiels du module sont les suivants :
- src : chemin du fichier template sur la machine de contrôle (relatif au dossier
templates/du rôle) - dest : chemin de destination sur l’hôte distant (chemin absolu obligatoire)
- owner et group : propriétaire et groupe du fichier généré
- mode : permissions du fichier (notation octale entre guillemets)
- backup : créer une sauvegarde du fichier existant avant écrasement (
yesouno) - validate : commande de validation exécutée avant le déploiement final
Le paramètre validate est particulièrement utile pour éviter les configurations cassées. Par exemple, pour un fichier Apache :
- name: Déployer la configuration Apache
ansible.builtin.template:
src: httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
validate: '/usr/sbin/httpd -t -f %s'
Ansible écrira le fichier dans un emplacement temporaire, exécutera la commande de validation (où %s est remplacé par le chemin temporaire), et ne déploiera le fichier que si la validation réussit. Un filet de sécurité que je recommande systématiquement en production.
Différence entre copy et template dans Ansible
Cette question revient très souvent chez mes étudiants en reconversion informatique. La réponse est simple, mais la nuance est importante.
Le module copy transfère un fichier tel quel, sans aucune interprétation. Si votre fichier contient {{ variable }}, cette chaîne apparaîtra littéralement sur le serveur distant. Le module template, lui, passe le fichier par le moteur Jinja2 avant de le déposer. Toutes les expressions sont résolues.
| Critère | Module copy | Module template |
|---|---|---|
| Interprétation Jinja2 | Non | Oui |
| Extension conventionnelle | Aucune (.conf, .txt, etc.) | .j2 |
| Répertoire source par défaut | files/ | templates/ |
| Paramètre remote_src | Oui (copie entre chemins distants) | Non supporté |
| Cas d’usage | Fichiers statiques (certificats, binaires) | Fichiers de configuration dynamiques |
| Performance | Légèrement plus rapide | Étape de rendu supplémentaire |
Ma règle : si le fichier contient la moindre variable ou condition, utilisez template. Si c’est un fichier binaire ou un certificat SSL qui ne doit jamais être modifié, utilisez copy. En cas de doute, partez sur template : le surcoût de performance est négligeable et vous garderez la flexibilité d’ajouter des variables plus tard.
Syntaxe Jinja2 : variables, filtres et conditions
Maîtriser la syntaxe Jinja2 est indispensable pour écrire des templates Ansible efficaces. Voici les trois éléments fondamentaux que vous utiliserez quotidiennement.
Les variables
Les variables s’insèrent avec la double accolade {{ nom_variable }}. Ansible résout ces variables en puisant dans plusieurs sources, par ordre de priorité : les extra vars, les variables de tâche, les variables de bloc, les variables de rôle, les facts, et l’inventaire.
# template : app.conf.j2
server_name={{ server_hostname }}
listen_port={{ app_port }}
workers={{ ansible_processor_vcpus * 2 }}
log_level={{ log_level | default('info') }}
Les filtres
Les filtres transforment les valeurs à la volée grâce au caractère pipe |. Ansible ajoute ses propres filtres à ceux de Jinja2. Voici les plus utilisés :
- default(‘valeur’) : fournit une valeur de repli si la variable n’est pas définie
- upper / lower : transforme la casse
- join(‘,’) : concatène les éléments d’une liste avec un séparateur
- to_yaml / to_json : sérialise un objet dans le format souhaité
- regex_replace : applique une expression régulière
- ipaddr : valide et manipule des adresses IP
# Exemples de filtres dans un template
allowed_hosts={{ allowed_hosts | join(', ') }}
database_url={{ db_url | regex_replace('password=.*@', 'password=***@') }}
config_dump={{ app_config | to_yaml }}
Les conditions
Les blocs conditionnels permettent d’inclure ou d’exclure des portions du fichier généré :
{% if enable_ssl %}
ssl_certificate /etc/ssl/certs/{{ domain }}.crt;
ssl_certificate_key /etc/ssl/private/{{ domain }}.key;
{% else %}
# SSL désactivé sur cet environnement
{% endif %}
Vous pouvez combiner plusieurs conditions avec and, or et not. Le test is defined est particulièrement utile pour vérifier qu’une variable existe avant de l’utiliser.

Exemples pratiques de templates Ansible
Passons à la pratique avec trois exemples concrets que j’utilise régulièrement dans mes formations et en production.
Exemple 1 : template Nginx avec virtual hosts
Ce template génère un virtual host Nginx complet, avec gestion optionnelle du SSL :
# templates/nginx_vhost.conf.j2
server {
listen {{ http_port | default(80) }};
server_name {{ server_name }};
{% if enable_ssl | default(false) %}
listen {{ https_port | default(443) }} ssl;
ssl_certificate /etc/ssl/certs/{{ server_name }}.crt;
ssl_certificate_key /etc/ssl/private/{{ server_name }}.key;
ssl_protocols TLSv1.2 TLSv1.3;
{% endif %}
root {{ document_root }};
index index.html index.php;
access_log /var/log/nginx/{{ server_name }}_access.log;
error_log /var/log/nginx/{{ server_name }}_error.log;
{% for location in custom_locations | default([]) %}
location {{ location.path }} {
{{ location.directive }};
}
{% endfor %}
}
Et le playbook associé :
- name: Configurer les virtual hosts Nginx
ansible.builtin.template:
src: nginx_vhost.conf.j2
dest: "/etc/nginx/sites-available/{{ server_name }}.conf"
validate: '/usr/sbin/nginx -t -c %s'
notify: Reload Nginx
Exemple 2 : fichier Docker Compose dynamique
Ce cas illustre bien la puissance des templates pour les environnements conteneurisés. Si vous travaillez avec des pods Kubernetes ou Docker, la logique est similaire :
# templates/docker-compose.yml.j2
version: '3.8'
services:
{% for service in app_services %}
{{ service.name }}:
image: {{ service.image }}:{{ service.tag | default('latest') }}
ports:
{% for port in service.ports | default([]) %}
- "{{ port }}"
{% endfor %}
environment:
{% for key, value in service.env_vars | default({}) | dictsort %}
{{ key }}: "{{ value }}"
{% endfor %}
{% if service.volumes is defined %}
volumes:
{% for volume in service.volumes %}
- {{ volume }}
{% endfor %}
{% endif %}
{% endfor %}
Exemple 3 : fichier de configuration applicative
Un cas classique de configuration pour une application Python/Django :
# templates/settings_local.py.j2
# Fichier généré par Ansible ; ne pas modifier manuellement
DEBUG = {{ django_debug | default(false) | string }}
ALLOWED_HOSTS = [{{ allowed_hosts | map('regex_replace', '^(.*)$', "'\\1'") | join(', ') }}]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.{{ db_engine }}',
'NAME': '{{ db_name }}',
'USER': '{{ db_user }}',
'PASSWORD': '{{ db_password }}',
'HOST': '{{ db_host }}',
'PORT': '{{ db_port | default(5432) }}',
}
}
Pour les secrets comme db_password, je recommande vivement de les stocker avec Ansible Vault. C’est le moyen le plus sûr d’éviter d’exposer des mots de passe en clair dans votre dépôt.
Organisation du répertoire templates et bonnes pratiques
L’organisation de vos fichiers templates a un impact direct sur la maintenabilité de votre infrastructure as code. Voici la structure que je recommande, alignée sur les bonnes pratiques de la documentation officielle Ansible :
roles/
└── webserver/
├── tasks/
│ └── main.yml
├── templates/
│ ├── nginx.conf.j2
│ ├── vhost.conf.j2
│ └── logrotate.conf.j2
├── vars/
│ └── main.yml
├── defaults/
│ └── main.yml
└── handlers/
└── main.yml
Quelques règles que j’applique systématiquement :
- Extension .j2 : toujours ajouter cette extension pour distinguer les templates des fichiers statiques
- Nommage cohérent : le nom du template reflète le fichier de destination (
nginx.conf.j2→/etc/nginx/nginx.conf) - Commentaire d’en-tête : ajoutez systématiquement un commentaire indiquant que le fichier est géré par Ansible
- Valeurs par défaut : utilisez le filtre
default()pour chaque variable optionnelle - Tests unitaires : validez vos templates avec Molecule ou en mode
--check
# En-tête recommandé pour chaque template
# {{ ansible_managed }}
# Ce fichier est géré par Ansible. Toute modification manuelle sera écrasée.
La variable spéciale {{ ansible_managed }} insère automatiquement une chaîne configurable (par défaut : « Ansible managed ») qui avertit quiconque ouvrirait le fichier sur le serveur.

Templates avancés : boucles, includes et macros
Une fois les bases maîtrisées, vous pouvez exploiter des fonctionnalités avancées de Jinja2 pour réduire la duplication et améliorer la lisibilité de vos templates.
Boucles avec for
Les boucles {% for %} itèrent sur des listes ou dictionnaires :
# Générer des entrées DNS
{% for host in dns_records %}
{{ host.ip }} {{ host.fqdn }} {{ host.aliases | default([]) | join(' ') }}
{% endfor %}
Pour les boucles sur des dictionnaires, utilisez dictsort afin de garantir un ordre déterministe :
{% for key, value in environment_variables | dictsort %}
export {{ key }}="{{ value }}"
{% endfor %}
Includes et imports
Vous pouvez découper un template volumineux en sous-fichiers avec {% include %} :
# templates/nginx.conf.j2
http {
{% include 'nginx_upstream.conf.j2' %}
{% include 'nginx_gzip.conf.j2' %}
{% for vhost in virtual_hosts %}
{% include 'nginx_vhost.conf.j2' %}
{% endfor %}
}
Ansible recherche automatiquement les fichiers inclus dans le même répertoire templates/. Cette technique est idéale pour les configurations complexes qui dépassent les 100 lignes.
Macros Jinja2
Les macros sont l’équivalent des fonctions dans un template. Elles permettent de réutiliser des blocs de configuration :
{% macro upstream_block(name, servers, method='round_robin') %}
upstream {{ name }} {
{% if method == 'least_conn' %}
least_conn;
{% elif method == 'ip_hash' %}
ip_hash;
{% endif %}
{% for server in servers %}
server {{ server.host }}:{{ server.port }} weight={{ server.weight | default(1) }};
{% endfor %}
}
{% endmacro %}
{{ upstream_block('app_backend', app_servers, 'least_conn') }}
{{ upstream_block('api_backend', api_servers) }}
Les macros sont particulièrement utiles lorsque le même motif se répète plusieurs fois dans un fichier. Pour les architectures impliquant de la virtualisation de serveurs avec de multiples services, cette approche réduit considérablement la taille des templates.
Contrôle des espaces blancs
Un problème fréquent avec Jinja2 : les blocs logiques génèrent des lignes vides indésirables. Pour y remédier, utilisez le tiret - dans les balises :
{%- if enable_feature -%}
feature_enabled=true
{%- endif -%}
Le tiret avant %} supprime les espaces après le bloc ; le tiret après {% supprime ceux avant. Testez progressivement, car un usage trop agressif peut fusionner des lignes qui devraient rester séparées.
Déboguer ses templates : erreurs courantes et solutions
Même avec de l’expérience, les templates Ansible peuvent générer des erreurs déroutantes. Voici les problèmes que je rencontre le plus souvent dans mes projets et en accompagnant des équipes de gestion de projets informatiques.
Variable non définie (UndefinedError)
C’est l’erreur numéro un. Ansible s’arrête avec un message du type AnsibleUndefinedVariable: 'db_host' is undefined. Solutions :
- Ajoutez
| default('valeur')pour les variables optionnelles - Vérifiez l’existence avec
{% if variable is defined %} - Assurez-vous que les variables sont définies au bon niveau (host_vars, group_vars, defaults)
Problèmes d’indentation YAML
Si votre template génère du YAML (comme un fichier Docker Compose), une indentation incorrecte provoquera une erreur sur l’hôte distant. Utilisez les filtres to_nice_yaml ou indent :
config:
{{ complex_dict | to_nice_yaml | indent(2, true) }}
Mode check et diff
Avant de déployer en production, lancez toujours votre playbook en mode check + diff pour voir exactement ce qui sera modifié :
ansible-playbook site.yml --check --diff
Ce mode exécute le rendu du template et affiche les différences sans appliquer les changements. C’est un filet de sécurité indispensable, surtout quand on travaille avec des services Kubernetes ou des infrastructures critiques.
Debug avec le filtre comment
Pour inspecter la valeur d’une variable pendant le développement :
- name: Afficher le rendu du template
ansible.builtin.debug:
msg: "{{ lookup('template', 'mon_template.j2') }}"
Le lookup template rend le fichier sans le déployer, ce qui vous permet de vérifier le résultat directement dans la console.
Tableau comparatif des paramètres du module template
Pour référence rapide, voici les paramètres complets du module ansible.builtin.template avec leur comportement par défaut et leurs cas d’usage :
| Paramètre | Obligatoire | Valeur par défaut | Description |
|---|---|---|---|
| src | Oui | — | Chemin du fichier .j2 sur la machine de contrôle |
| dest | Oui | — | Chemin absolu de destination sur l’hôte distant |
| owner | Non | utilisateur courant | Propriétaire du fichier généré |
| group | Non | groupe courant | Groupe propriétaire du fichier |
| mode | Non | système | Permissions en octal (ex : ‘0644’) |
| backup | Non | no | Créer une copie de sauvegarde avant écrasement |
| validate | Non | — | Commande de validation (%s = chemin temporaire) |
| force | Non | yes | Écraser même si le fichier distant existe déjà |
| newline_sequence | Non | \n | Séquence de fin de ligne (\n, \r, \r\n) |
| trim_blocks | Non | yes | Supprimer le saut de ligne après un bloc Jinja2 |
| lstrip_blocks | Non | no | Supprimer les espaces avant un bloc Jinja2 |
| output_encoding | Non | utf-8 | Encodage du fichier de sortie |
Le paramètre mode mérite une attention particulière. Je recommande de toujours le spécifier explicitement entre guillemets simples (ex : '0644'). Sans guillemets, YAML peut interpréter le nombre en octal de manière inattendue, ce qui conduit à des permissions incorrectes sur le fichier déployé.
Notez que contrairement au module copy, template ne supporte pas le paramètre remote_src. Le fichier source doit toujours se trouver sur la machine de contrôle Ansible. Si vous avez besoin de modifier un fichier déjà présent sur l’hôte distant, le module lineinfile ou blockinfile sera plus adapté.
Pour aller plus loin dans la gestion centralisée de vos configurations, Ansible Tower (AWX) offre une interface graphique qui simplifie le suivi des déploiements de templates à grande échelle. C’est un complément naturel quand votre infrastructure dépasse la vingtaine de serveurs.
Les templates Ansible sont l’un des piliers de l’infrastructure as code. En combinant la flexibilité de Jinja2 avec la puissance des variables et de l’inventaire Ansible, vous obtenez un système de gestion de configuration à la fois puissant et maintenable. Que vous gériez trois serveurs ou trois cents, les principes restent les mêmes : un template bien écrit, des variables bien organisées, et une validation systématique avant chaque déploiement.
À retenir
- Utilisez le module template pour les fichiers dynamiques et copy uniquement pour les fichiers statiques
- Ajoutez systématiquement le filtre default() sur chaque variable optionnelle pour éviter les erreurs UndefinedError
- Activez le paramètre validate sur tous les templates de configuration critique (Nginx, Apache, sudoers)
- Lancez –check –diff avant chaque déploiement en production pour visualiser les changements
- Stockez les secrets dans Ansible Vault plutôt que de les écrire en clair dans les variables
Questions fréquentes
Qu’est-ce qu’un template Ansible ?
Un template Ansible est un fichier texte contenant des expressions Jinja2 (variables, conditions, boucles) qui sont résolues au moment de l’exécution du playbook. Le module ansible.builtin.template génère le fichier final en remplaçant les expressions par les valeurs réelles des variables, puis le dépose sur l’hôte distant. L’extension conventionnelle est .j2.
Où placer les fichiers templates dans un rôle Ansible ?
Les fichiers templates doivent être placés dans le répertoire templates/ à la racine du rôle. Ansible recherche automatiquement les fichiers .j2 dans ce dossier lorsque vous utilisez un chemin relatif dans le paramètre src du module template. Pour un playbook hors rôle, le template doit se trouver dans le même répertoire que le playbook ou dans un sous-dossier templates/.
Quelle est la différence entre copy et template dans Ansible ?
Le module copy transfère un fichier statique sans interprétation : les expressions Jinja2 restent telles quelles. Le module template, lui, passe le fichier par le moteur Jinja2 et résout toutes les variables et conditions avant de déposer le résultat. Autre différence notable : copy supporte le paramètre remote_src (copie entre chemins distants), tandis que template exige que le fichier source soit sur la machine de contrôle.
Comment déboguer un template Ansible qui génère des erreurs ?
Trois techniques complémentaires permettent de déboguer efficacement. Premièrement, utilisez le mode –check –diff pour visualiser le rendu sans appliquer les changements. Deuxièmement, le lookup template dans une tâche debug affiche le contenu généré dans la console. Troisièmement, ajoutez le paramètre validate au module template pour vérifier la syntaxe du fichier généré avant son déploiement effectif.
Peut-on utiliser des boucles et des conditions dans les templates Ansible ?
Oui, les templates Ansible supportent toute la syntaxe Jinja2 : boucles for, conditions if/elif/else, macros, includes et filtres. Les boucles s’écrivent avec la balise pour itérer sur des listes ou dictionnaires. Les conditions utilisent les tests Jinja2 comme is defined, is number ou des comparaisons classiques. On peut aussi utiliser des macros pour factoriser des blocs de configuration récurrents.
Comment gérer les espaces blancs indésirables dans un template Jinja2 ?
Jinja2 offre deux mécanismes principaux. Le premier consiste à utiliser le tiret dans les balises : un tiret après l’ouverture ou avant la fermeture supprime les espaces adjacents. Le second utilise les paramètres trim_blocks (supprime le saut de ligne après un bloc) et lstrip_blocks (supprime les espaces avant un bloc), configurables directement dans le module template d’Ansible.
Formatrice IT indépendante depuis 2016, ancienne étudiante BTS SIO SLAM. 6 ans d'expérience en entreprise.