Dans cet article
- Le module ansible.builtin.template utilise le moteur Jinja2 pour générer des fichiers de configuration dynamiques sur les hôtes distants
- Les filtres Jinja2 les plus utilisés (default, join, regex_replace) couvrent plus de 80 % des cas d’usage courants
- La directive validate permet de vérifier la syntaxe d’un fichier généré avant de l’appliquer, évitant les interruptions de service
- Un template Ansible bien structuré réduit de 60 à 90 % le volume de tâches répétitives dans un playbook
- Les boucles et conditions Jinja2 (for, if, elif) permettent de gérer des configurations multi-environnements avec un seul fichier source
- Le répertoire conventionnel templates/ dans un rôle Ansible est détecté automatiquement sans chemin absolu
Sommaire
- Comprendre le module ansible template
- Syntaxe Jinja2 : les bases indispensables
- Paramètres clés du module template
- Exemples pratiques de templates Ansible
- Filtres et tests Jinja2 avancés
- Boucles, conditions et macros
- Template Ansible et gestion multi-environnements
- Bonnes pratiques et erreurs fréquentes
- Comparatif : template vs copy vs lineinfile
Quand je déploie une infrastructure avec Ansible, la génération dynamique de fichiers de configuration est l’une des tâches les plus fréquentes. Le module ansible template associé au moteur Jinja2 transforme cette opération en un processus fiable et reproductible. Plutôt que de dupliquer des fichiers pour chaque serveur ou chaque environnement, je rédige un seul fichier source contenant des variables, des boucles et des conditions. Ansible se charge ensuite de produire la version finale adaptée à chaque hôte.
Dans ce guide, je vous accompagne pas à pas pour maîtriser le template Ansible, depuis la syntaxe Jinja2 de base jusqu’aux techniques avancées que j’utilise quotidiennement en production.
Comprendre le module ansible template
Le module ansible.builtin.template fait partie des modules intégrés d’Ansible. Son rôle est simple : prendre un fichier source au format Jinja2 (extension .j2 par convention), résoudre toutes les expressions qu’il contient, puis déposer le résultat sur l’hôte distant. Ce mécanisme repose entièrement sur le moteur de templates Jinja2, le moteur de templates Python utilisé aussi par Flask et Django.
Contrairement au module Ansible Copy qui transfère un fichier statique tel quel, le module template interprète chaque expression entre doubles accolades {{ }} et chaque bloc logique {% %} avant le transfert. C’est cette différence fondamentale qui en fait l’outil privilégié pour la gestion de configuration.
Voici un premier exemple minimal pour illustrer le fonctionnement :
# playbook.yml
- 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
Le fichier nginx.conf.j2 correspondant pourrait contenir :
# nginx.conf.j2
worker_processes {{ ansible_processor_vcpus }};
server {
listen {{ nginx_port | default(80) }};
server_name {{ inventory_hostname }};
root {{ document_root }};
}
Lors de l’exécution, Ansible remplace {{ ansible_processor_vcpus }} par le nombre réel de vCPU du serveur cible, {{ inventory_hostname }} par le nom de l’hôte et {{ document_root }} par la valeur définie dans les variables du playbook.

Syntaxe Jinja2 : les bases indispensables
Avant de plonger dans les paramètres du module, je vous propose de consolider les fondamentaux de la syntaxe Jinja2. Trois types de délimiteurs structurent tout fichier template :
{{ expression }}: affiche le résultat d’une variable ou d’une expression. C’est le délimiteur le plus utilisé.{% instruction %}: exécute une logique (boucle, condition, assignation). Ne produit aucune sortie directe.{# commentaire #}: ajoute un commentaire ignoré lors du rendu. Pratique pour documenter vos templates.
Variables et facts Ansible
Dans un template Ansible, toutes les variables du contexte d’exécution sont accessibles : variables d’inventaire, facts collectés par gather_facts, variables de rôle, variables enregistrées avec register. Voici les plus courantes :
# Accès aux facts système
Hostname: {{ ansible_hostname }}
Adresse IP: {{ ansible_default_ipv4.address }}
Distribution: {{ ansible_distribution }} {{ ansible_distribution_version }}
Mémoire totale: {{ ansible_memtotal_mb }} Mo
# Accès aux variables personnalisées
Application: {{ app_name }}
Version: {{ app_version }}
Environnement: {{ env_type }}
Pour les variables imbriquées (dictionnaires), deux notations fonctionnent : {{ variable.cle }} ou {{ variable['cle'] }}. Je privilégie la notation par point pour sa lisibilité, sauf quand la clé contient des caractères spéciaux.
Le filtre default
Le filtre default (ou son alias d) est indispensable pour éviter les erreurs quand une variable n’est pas définie :
max_connections = {{ max_conn | default(100) }}
log_level = {{ log_level | default('info') }}
Ce filtre constitue votre première ligne de défense contre les templates cassés en production.
Paramètres clés du module template
Le module ansible.builtin.template accepte de nombreux paramètres. Voici ceux que j’utilise le plus souvent dans mes playbooks :
- name: Déployer un fichier de configuration
ansible.builtin.template:
src: app.conf.j2 # Chemin du template source (relatif au dossier templates/)
dest: /etc/app/app.conf # Chemin de destination sur l'hôte distant
owner: appuser # Propriétaire du fichier
group: appgroup # Groupe du fichier
mode: '0640' # Permissions du fichier
backup: yes # Créer une sauvegarde avant écrasement
validate: '/usr/sbin/nginx -t -c %s' # Commande de validation
force: yes # Écraser même si le contenu est identique
lstrip_blocks: yes # Supprimer les espaces en début de ligne pour les blocs
trim_blocks: yes # Supprimer le saut de ligne après un bloc
Le paramètre validate
Le paramètre validate mérite une attention particulière. Il exécute une commande de vérification sur le fichier généré avant de le placer à sa destination finale. Le marqueur %s est remplacé par le chemin du fichier temporaire. Si la commande retourne un code d’erreur, Ansible abandonne la tâche et conserve le fichier existant intact.
# Validation pour Apache
- ansible.builtin.template:
src: httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
validate: '/usr/sbin/httpd -t -f %s'
# Validation pour sudoers
- ansible.builtin.template:
src: sudoers.j2
dest: /etc/sudoers
validate: '/usr/sbin/visudo -cf %s'
Cette fonctionnalité est cruciale pour les fichiers critiques. Une erreur de syntaxe dans /etc/sudoers peut vous verrouiller hors du serveur ; la validation empêche ce scénario.
Le paramètre mode
Le paramètre mode définit les permissions du fichier déployé. Je recommande de toujours le spécifier explicitement et d’utiliser le format chaîne avec guillemets ('0644') plutôt que le format octal sans guillemets, pour éviter toute ambiguïté d’interprétation YAML.
Exemples pratiques de templates Ansible
Passons à des cas concrets. Voici plusieurs exemples de templates que je déploie régulièrement en environnement de production.
Template pour un fichier /etc/hosts
# hosts.j2
127.0.0.1 localhost
{{ ansible_default_ipv4.address }} {{ ansible_fqdn }} {{ ansible_hostname }}
# Serveurs du cluster
{% for host in groups['webservers'] %}
{{ hostvars[host]['ansible_default_ipv4']['address'] }} {{ host }}
{% endfor %}
Ce template génère automatiquement les entrées DNS locales de tous les serveurs du groupe webservers. Chaque fois qu’un serveur est ajouté à l’inventaire, le fichier est mis à jour sur l’ensemble du cluster.
Template pour un virtualhost Apache
# vhost.conf.j2
<VirtualHost *:{{ http_port | default(80) }}>
ServerName {{ server_name }}
{% if server_aliases is defined %}
{% for alias in server_aliases %}
ServerAlias {{ alias }}
{% endfor %}
{% endif %}
DocumentRoot {{ document_root }}
ErrorLog ${APACHE_LOG_DIR}/{{ server_name }}-error.log
CustomLog ${APACHE_LOG_DIR}/{{ server_name }}-access.log combined
{% if enable_ssl | default(false) %}
SSLEngine on
SSLCertificateFile {{ ssl_cert_path }}
SSLCertificateKeyFile {{ ssl_key_path }}
{% endif %}
</VirtualHost>
Template pour un fichier .env applicatif
# dotenv.j2
# Généré par Ansible - Ne pas modifier manuellement
# Dernière mise à jour : {{ ansible_date_time.iso8601 }}
APP_NAME={{ app_name }}
APP_ENV={{ app_env }}
APP_DEBUG={{ app_debug | default('false') }}
APP_URL=https://{{ server_name }}
DB_HOST={{ db_host }}
DB_PORT={{ db_port | default(3306) }}
DB_DATABASE={{ db_name }}
DB_USERNAME={{ db_user }}
DB_PASSWORD={{ db_password }}
{% if redis_host is defined %}
REDIS_HOST={{ redis_host }}
REDIS_PORT={{ redis_port | default(6379) }}
{% endif %}
Pour sécuriser les mots de passe et secrets présents dans ce type de template, je vous recommande d’utiliser Ansible Vault pour chiffrer les variables sensibles.

Filtres et tests Jinja2 avancés
Les filtres Jinja2 transforment les données avant leur affichage. Ansible enrichit les filtres natifs de Jinja2 avec ses propres filtres. Voici ceux que j’utilise le plus fréquemment :
Filtres de manipulation de chaînes
# Conversion de casse
{{ hostname | upper }} # MONSERVEUR
{{ hostname | lower }} # monserveur
{{ hostname | capitalize }} # Monserveur
{{ hostname | title }} # Mon Serveur
# Remplacement et regex
{{ path | replace('/', '_') }}
{{ line | regex_replace('^#\\s*', '') }}
# Encodage
{{ password | hash('sha512') }}
{{ data | b64encode }}
{{ encoded_data | b64decode }}
Filtres de manipulation de listes
# Jointure
{{ dns_servers | join(', ') }} # 8.8.8.8, 8.8.4.4
{{ allowed_ips | join('\n') }}
# Tri et déduplication
{{ packages | sort }}
{{ items | unique }}
# Sélection
{{ users | map(attribute='name') | list }}
{{ services | selectattr('enabled', 'equalto', true) | list }}
# Combinaison
{{ list1 | union(list2) }}
{{ list1 | intersect(list2) }}
{{ list1 | difference(list2) }}
Filtres spécifiques à Ansible
# Conversion YAML/JSON
{{ complex_var | to_nice_yaml(indent=2) }}
{{ complex_var | to_nice_json(indent=4) }}
# Réseau
{{ '192.168.1.0/24' | ipaddr('network') }}
{{ my_ip | ipv4 }}
# Fichiers
{{ '/etc/nginx/nginx.conf' | basename }} # nginx.conf
{{ '/etc/nginx/nginx.conf' | dirname }} # /etc/nginx
{{ 'config' | hash('md5') }}
La documentation officielle d’Ansible référence plus de 80 filtres spécifiques. Pour un panorama complet, consultez la liste des filtres Ansible dans la documentation officielle.
Tests Jinja2
Les tests permettent de vérifier une condition sur une variable. Ils s’utilisent avec le mot-clé is :
{% if my_var is defined %}
{% if my_var is string %}
{% if my_list is iterable %}
{% if my_var is number %}
{% if result is failed %}
{% if result is changed %}
Boucles, conditions et macros
La véritable puissance d’un template Ansible réside dans sa capacité à intégrer de la logique. Les structures de contrôle Jinja2 transforment un fichier statique en un générateur de configuration intelligent.
Boucles for
La boucle for itère sur des listes ou des dictionnaires :
# Itération simple sur une liste
{% for server in upstream_servers %}
server {{ server }}:{{ upstream_port | default(8080) }};
{% endfor %}
# Itération sur un dictionnaire
{% for key, value in app_settings.items() %}
{{ key }} = {{ value }}
{% endfor %}
# Boucle avec index et conditions spéciales
{% for user in users %}
{% if not loop.first %},{% endif %}
"{{ user.name }}"{% if loop.last %}
{% endif %}
{% endfor %}
Les variables spéciales de boucle (loop.index, loop.first, loop.last, loop.length) sont particulièrement utiles pour formater correctement la sortie, par exemple pour gérer les virgules dans une liste JSON.
Conditions if/elif/else
# Configuration conditionnelle selon l'environnement
{% if env_type == 'production' %}
log_level = warn
workers = {{ ansible_processor_vcpus * 2 }}
{% elif env_type == 'staging' %}
log_level = info
workers = {{ ansible_processor_vcpus }}
{% else %}
log_level = debug
workers = 2
{% endif %}
# Inclusion conditionnelle de blocs
{% if monitoring_enabled | default(false) | bool %}
[monitoring]
endpoint = {{ monitoring_url }}
interval = {{ monitoring_interval | default(60) }}
{% endif %}
Macros : les fonctions de Jinja2
Les macros permettent de factoriser des blocs de template réutilisables :
# Définition d'une macro
{% macro upstream_block(name, servers, port=8080) %}
upstream {{ name }} {
{% for server in servers %}
server {{ server }}:{{ port }};
{% endfor %}
}
{% endmacro %}
# Utilisation de la macro
{{ upstream_block('api', api_servers, 3000) }}
{{ upstream_block('web', web_servers) }}
{{ upstream_block('static', cdn_servers, 8888) }}
Cette technique évite la duplication de code dans les templates complexes et facilite la maintenance sur le long terme.
Template Ansible et gestion multi-environnements
L’un des cas d’usage les plus fréquents du module template concerne le déploiement de configurations adaptées à chaque environnement (développement, staging, production). Voici comment je structure mes projets pour gérer cette complexité.
Organisation du répertoire templates
Dans un rôle Ansible, le répertoire templates/ est automatiquement recherché par le module. Voici une structure type :
roles/
webserver/
templates/
nginx.conf.j2
vhost.conf.j2
logrotate.j2
defaults/
main.yml # Variables par défaut
vars/
main.yml # Variables prioritaires
tasks/
main.yml
Quand je référence src: nginx.conf.j2 dans un rôle, Ansible cherche automatiquement dans roles/webserver/templates/nginx.conf.j2. Aucun chemin absolu n’est nécessaire.
Déploiement d’un répertoire complet de templates
Pour déployer plusieurs fichiers de templates d’un coup, je combine la boucle with_fileglob avec le module template :
- name: Déployer tous les fichiers de configuration
ansible.builtin.template:
src: "{{ item }}"
dest: "/etc/app/conf.d/{{ item | basename | regex_replace('\\.j2$', '') }}"
owner: root
group: root
mode: '0644'
with_fileglob:
- templates/conf.d/*.j2
notify: reload app
Cette approche est idéale quand un service lit sa configuration depuis un répertoire entier (comme conf.d/ pour Nginx ou Apache). Pour la gestion centralisée de vos déploiements à grande échelle, Ansible Tower offre une interface graphique qui simplifie le suivi des exécutions.

Variables par environnement
Pour différencier les environnements, j’utilise la hiérarchie de variables d’Ansible :
# group_vars/production.yml
app_env: production
app_debug: false
db_host: db-prod.internal
log_level: warn
workers: 8
# group_vars/staging.yml
app_env: staging
app_debug: true
db_host: db-staging.internal
log_level: info
workers: 4
# group_vars/development.yml
app_env: development
app_debug: true
db_host: localhost
log_level: debug
workers: 2
Un même template produit alors des fichiers de configuration différents selon le groupe d’hôtes ciblé, sans aucune modification du fichier .j2.
Bonnes pratiques et erreurs fréquentes
Après plusieurs années d’utilisation intensive des templates Ansible en production, voici les bonnes pratiques que je recommande systématiquement à mes étudiants et à mes clients.
Les indispensables
- Toujours ajouter un en-tête dans vos fichiers générés pour indiquer qu’ils sont gérés par Ansible :
# Managed by Ansible - DO NOT EDIT MANUALLY - Utiliser trim_blocks et lstrip_blocks pour éviter les lignes vides parasites générées par les blocs
{% %} - Versionner vos templates dans Git comme tout autre code d’infrastructure
- Tester vos templates avec
ansible-playbook --check --diffavant de les appliquer - Activer backup: yes pour les fichiers critiques afin de pouvoir revenir en arrière rapidement
Les erreurs à éviter
- Oublier les guillemets autour du mode : écrire
mode: 0644au lieu demode: '0644'peut produire des permissions incorrectes en raison de l’interprétation octale de YAML - Ne pas utiliser le filtre default : un template qui référence une variable non définie provoque une erreur fatale
- Mélanger espaces et tabulations : Jinja2 est sensible à l’indentation, surtout dans les fichiers YAML générés
- Ignorer le paramètre validate : pour les fichiers de configuration critiques (sudoers, sshd_config, nginx.conf), la validation est une assurance indispensable
- Hardcoder des valeurs dans les templates au lieu d’utiliser des variables : cela annule tout l’intérêt de la génération dynamique
Contrôle des espaces blancs
Les blocs Jinja2 génèrent souvent des lignes vides indésirables. Deux solutions complémentaires existent :
# Solution 1 : paramètres du module
- ansible.builtin.template:
src: config.j2
dest: /etc/config
trim_blocks: yes
lstrip_blocks: yes
# Solution 2 : contrôle dans le template avec le tiret
{%- if condition -%}
valeur sans espaces autour
{%- endif -%}
Le tiret (-) placé à l’intérieur des délimiteurs supprime les espaces blancs adjacents. C’est la méthode la plus précise pour contrôler le formatage de votre sortie.
Comparatif : template vs copy vs lineinfile
Pour choisir le bon module selon votre besoin, voici un comparatif que je partage souvent en formation :
| Critère | template | copy | lineinfile |
|---|---|---|---|
| Cas d’usage | Fichier entier dynamique | Fichier statique identique | Modifier une seule ligne |
| Moteur Jinja2 | Oui | Non | Non |
| Variables dynamiques | Oui, toutes | Non | Regex uniquement |
| Boucles et conditions | Oui | Non | Non |
| Fichier source | .j2 sur le contrôleur | Fichier sur le contrôleur | Fichier existant sur la cible |
| Idempotence | Oui | Oui | Oui |
| Validation | Oui (validate) | Non | Oui (validate) |
| Backup | Oui | Oui | Oui |
| Complexité | Moyenne | Faible | Faible à moyenne |
En résumé : utilisez template quand le fichier doit varier selon l’hôte ou l’environnement, copy pour les fichiers identiques partout, et lineinfile pour des modifications ponctuelles sur un fichier existant que vous ne gérez pas entièrement.
À retenir
- Utilisez systématiquement le filtre default sur toute variable qui pourrait ne pas être définie dans certains contextes
- Activez validate pour les fichiers critiques (sudoers, nginx.conf, sshd_config) afin de prévenir les interruptions de service
- Structurez vos variables avec group_vars/ par environnement pour éviter de dupliquer vos templates
- Ajoutez toujours un en-tête « Managed by Ansible » dans vos fichiers générés pour éviter les modifications manuelles accidentelles
- Testez vos templates avec –check –diff avant chaque déploiement en production
Questions fréquentes
Quelle est la différence entre le module template et le module copy dans Ansible ?
Le module template interprète les expressions Jinja2 (variables, boucles, conditions) avant de déposer le fichier sur l’hôte distant, tandis que le module copy transfère un fichier statique sans aucune transformation. Utilisez template quand le contenu doit varier selon l’hôte ou l’environnement, et copy pour les fichiers identiques sur tous les serveurs.
Combinez le module template avec la directive Comment déployer un répertoire entier de templates avec Ansible ?
with_fileglob pour itérer sur tous les fichiers .j2 d’un répertoire. Utilisez le filtre basename associé à regex_replace pour retirer l’extension .j2 du nom de fichier de destination. Cette technique est idéale pour les services qui lisent leur configuration depuis un répertoire conf.d/.
Utilisez le paramètre validate du module template. Il exécute une commande de vérification sur le fichier généré avant de le placer à sa destination finale. Par exemple, Comment valider un fichier de configuration généré par un template Ansible ?
validate: '/usr/sbin/nginx -t -c %s' pour Nginx ou validate: '/usr/sbin/visudo -cf %s' pour sudoers. Si la validation échoue, le fichier existant reste intact.
Les filtres les plus utilisés sont default (valeur par défaut si variable absente), join (concaténer une liste), to_nice_yaml et to_nice_json (formater des structures complexes), regex_replace (substitution par regex), hash (hachage de chaînes) et ipaddr (manipulation d’adresses réseau). Ansible propose plus de 80 filtres spécifiques en complément des filtres natifs Jinja2.Quels sont les filtres Jinja2 les plus utiles dans un template Ansible ?
Deux méthodes complémentaires existent. Au niveau du module, activez les paramètres Comment gérer les espaces blancs parasites dans un template Jinja2 ?
trim_blocks: yes et lstrip_blocks: yes. Au niveau du template, ajoutez un tiret à l’intérieur des délimiteurs ({%- ... -%}) pour supprimer les espaces adjacents. La combinaison des deux approches garantit un fichier de sortie propre et sans lignes vides indésirables.
Non, contrairement au module copy, le module template ne supporte pas le paramètre Le module template Ansible fonctionne-t-il avec remote_src ?
remote_src. Le fichier source .j2 doit toujours se trouver sur la machine contrôleur Ansible. Si vous devez utiliser un fichier présent sur l’hôte distant comme source, récupérez-le d’abord avec le module fetch, puis appliquez le template depuis le contrôleur.
Formatrice IT indépendante depuis 2016, ancienne étudiante BTS SIO SLAM. 6 ans d'expérience en entreprise.