
Ce guide vous apprend à écrire des templates HCL2 pour Packer. Vous comprendrez la syntaxe, les trois blocs fondamentaux, et comment organiser vos fichiers pour des projets maintenables. À la fin, vous saurez créer, valider et formater vos templates comme un professionnel.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- La syntaxe HCL2 et ses avantages par rapport à JSON
- Les trois blocs essentiels :
packer,sourceetbuild - Comment organiser un projet multi-fichiers
- Les commandes de développement :
fmt,validate,inspect
Prérequis : Packer installé (voir le guide d’installation), notions de base sur Packer (concepts fondamentaux).
Pourquoi HCL2 plutôt que JSON ?
Section intitulée « Pourquoi HCL2 plutôt que JSON ? »Avant la version 1.7, Packer utilisait exclusivement JSON pour ses templates. Depuis, HCL2 (HashiCorp Configuration Language 2) est devenu le format recommandé. Cette section explique pourquoi vous devriez adopter HCL2 pour tous vos nouveaux projets.
HCL2 offre plusieurs avantages majeurs qui simplifient l’écriture et la maintenance de vos templates. Le tableau suivant compare les deux formats :
| Aspect | JSON | HCL2 |
|---|---|---|
| Lisibilité | Structure dense, difficile à parcourir | Clair, proche du langage naturel |
| Commentaires | Non supportés | # ou // supportés |
| Variables | Limitées, syntaxe complexe | Natives, avec typage et validation |
| Réutilisation | Copier-coller | Blocs source réutilisables |
| Multi-fichiers | Impossible | Natif (.pkr.hcl) |
| Formatage | Manuel | packer fmt automatique |
| Débogage | packer inspect limité | packer inspect complet |
Pour illustrer la différence de lisibilité, voici le même template dans les deux formats. Remarquez comment HCL2 permet les commentaires et une structure plus aérée :
# Template HCL2 - Clair et commentésource "docker" "alpine" { image = "alpine:3.20" commit = true}
build { sources = ["source.docker.alpine"]
provisioner "shell" { inline = ["echo 'Hello World'"] }}{ "builders": [{ "type": "docker", "image": "alpine:3.20", "commit": true }], "provisioners": [{ "type": "shell", "inline": ["echo 'Hello World'"] }]}Structure d’un fichier HCL2
Section intitulée « Structure d’un fichier HCL2 »Un template Packer HCL2 utilise des fichiers avec l’extension .pkr.hcl. Packer reconnaît automatiquement ces fichiers et les traite comme des templates HCL2. Cette section détaille les conventions de nommage et la structure de base.
Convention de nommage
Section intitulée « Convention de nommage »Packer identifie les templates HCL2 grâce à leur extension. Voici les conventions à respecter :
| Extension | Usage |
|---|---|
*.pkr.hcl | Template HCL2 (recommandé) |
*.pkr.json | Template JSON au format HCL2 |
*.json | Ancien format JSON (legacy) |
Lorsque vous exécutez packer build sur un répertoire, Packer combine automatiquement tous les fichiers .pkr.hcl présents. Cela permet d’organiser votre configuration en plusieurs fichiers logiques, comme nous le verrons plus loin.
Éléments syntaxiques
Section intitulée « Éléments syntaxiques »La syntaxe HCL2 repose sur trois concepts fondamentaux : les blocs, les arguments et les expressions. Comprendre ces éléments vous permettra de lire et écrire n’importe quel template Packer.
Un bloc est un conteneur qui regroupe des configurations liées. Chaque bloc a un type, zéro ou plusieurs labels, et un corps contenant des arguments :
# Structure générale d'un bloctype_bloc "label_1" "label_2" { # Corps du bloc argument = valeur}Un argument assigne une valeur à un nom. Les arguments apparaissent uniquement à l’intérieur des blocs :
source "docker" "alpine" { image = "alpine:3.20" # argument "image" commit = true # argument "commit"}Une expression représente une valeur, soit littérale, soit calculée. Packer supporte les chaînes, nombres, booléens, listes et maps :
# Expressions littéralesimage = "alpine:3.20" # chaînecommit = true # booléencount = 3 # nombre
# Listes et mapschanges = ["LABEL app=demo", "ENV FOO=bar"]tags = { environment = "dev", project = "demo" }Les trois blocs fondamentaux
Section intitulée « Les trois blocs fondamentaux »Tout template Packer HCL2 s’articule autour de trois blocs fondamentaux. Chacun a un rôle précis dans le processus de création d’images. Cette section détaille chaque bloc avec des exemples concrets.
Le bloc packer
Section intitulée « Le bloc packer »Le bloc packer configure les exigences globales du template. Il définit la version minimale de Packer requise et déclare les plugins nécessaires. Ce bloc est optionnel mais fortement recommandé pour garantir la reproductibilité de vos builds.
Voici un exemple complet qui illustre les deux fonctionnalités principales du bloc packer :
packer { # Version minimale requise required_version = ">= 1.12.0"
# Plugins nécessaires required_plugins { docker = { version = ">= 1.1.0" source = "github.com/hashicorp/docker" } }}Le champ required_version utilise des contraintes de version similaires à celles de Terraform. Les opérateurs disponibles sont :
| Opérateur | Signification | Exemple |
|---|---|---|
= | Version exacte | = 1.12.0 |
!= | Exclut une version | != 1.11.0 |
>, >=, <, <= | Comparaison | >= 1.12.0 |
~> | Pessimiste (patch) | ~> 1.12.0 équivaut à >= 1.12.0, < 1.13.0 |
Le bloc required_plugins liste les plugins que packer init doit télécharger. Chaque plugin a deux champs obligatoires :
- version : contrainte de version (mêmes opérateurs que ci-dessus)
- source : adresse du plugin, au format
github.com/<namespace>/<type>
Le bloc source
Section intitulée « Le bloc source »Le bloc source définit une configuration de builder réutilisable. C’est ici que vous configurez comment créer l’image : plateforme cible, image de base, paramètres de connexion, etc. Un template peut contenir plusieurs blocs source pour créer des images sur différentes plateformes.
La syntaxe du bloc source suit ce format :
source "TYPE" "NOM" { # Configuration du builder}- TYPE : le type de builder (docker, qemu, proxmox, amazon-ebs, etc.)
- NOM : un identifiant unique pour cette source
Voici un exemple concret avec le builder Docker. Les commentaires expliquent chaque option :
source "docker" "ubuntu" { # Image de base à utiliser image = "ubuntu:24.04"
# Mode de sortie : commit crée une image Docker locale commit = true
# Métadonnées ajoutées à l'image résultante changes = [ "LABEL maintainer=devops@example.com", "LABEL version=1.0", "ENV APP_ENV=production" ]}Un avantage majeur des blocs source est leur réutilisabilité. Vous pouvez définir plusieurs sources et les utiliser dans un ou plusieurs blocs build :
# Deux variantes de la même configurationsource "docker" "alpine" { image = "alpine:3.20" commit = true}
source "docker" "debian" { image = "debian:bookworm-slim" commit = true}Le bloc build
Section intitulée « Le bloc build »Le bloc build orchestre tout le processus de création d’image. Il référence les sources à construire, définit les provisioners qui configurent l’image, et spécifie les post-processors qui traitent l’artefact final. Un template peut contenir plusieurs blocs build pour des workflows distincts.
Voici la structure complète d’un bloc build avec tous ses composants :
build { # Nom du build (optionnel, visible dans les logs) name = "mon-build"
# Sources à construire (référence : source.TYPE.NOM) sources = [ "source.docker.alpine", "source.docker.debian" ]
# Provisioners : configurent l'image (exécutés dans l'ordre) provisioner "shell" { inline = [ "echo 'Configuration en cours...'", "apk update && apk add curl" ] }
# Post-processors : traitent l'artefact (exécutés après le build) post-processor "manifest" { output = "manifest.json" }}Les provisioners s’exécutent dans l’ordre où ils apparaissent dans le bloc build. Cette séquentialité est importante pour les dépendances entre étapes :
build { sources = ["source.docker.base"]
# Étape 1 : mise à jour du système provisioner "shell" { inline = ["apk update"] }
# Étape 2 : installation (dépend de l'étape 1) provisioner "shell" { inline = ["apk add --no-cache curl"] }
# Étape 3 : validation (dépend de l'étape 2) provisioner "shell" { inline = ["curl --version"] }}À l’intérieur des provisioners et post-processors, vous pouvez accéder aux informations de la source en cours via les variables source.name et source.type. Cette fonctionnalité est particulièrement utile pour les builds multi-sources :
build { sources = ["source.docker.alpine", "source.docker.debian"]
provisioner "shell" { inline = [ "echo 'Construction de ${source.name}'", "echo 'Type de builder : ${source.type}'" ] }}Organisation multi-fichiers
Section intitulée « Organisation multi-fichiers »Pour les projets conséquents, regrouper toute la configuration dans un seul fichier devient difficile à maintenir. Packer résout ce problème en combinant automatiquement tous les fichiers .pkr.hcl d’un répertoire. Cette section présente une organisation recommandée pour les projets professionnels.
Structure recommandée
Section intitulée « Structure recommandée »Voici une organisation qui sépare les responsabilités et facilite la navigation :
mon-projet-packer/├── plugins.pkr.hcl # Bloc packer (version, plugins)├── sources.pkr.hcl # Tous les blocs source├── builds.pkr.hcl # Tous les blocs build├── variables.pkr.hcl # Variables (si utilisées)└── README.md # Documentation du projetChaque fichier a une responsabilité claire :
| Fichier | Contenu | Responsabilité |
|---|---|---|
plugins.pkr.hcl | Bloc packer | Version et dépendances |
sources.pkr.hcl | Blocs source | Configuration des builders |
builds.pkr.hcl | Blocs build | Orchestration |
variables.pkr.hcl | Blocs variable | Paramétrage |
Exemple concret
Section intitulée « Exemple concret »Voici un projet multi-fichiers complet et fonctionnel. Commençons par le fichier de plugins :
packer { required_version = ">= 1.12.0"
required_plugins { docker = { version = ">= 1.1.0" source = "github.com/hashicorp/docker" } }}Ensuite, le fichier des sources qui définit les différentes cibles :
source "docker" "alpine" { image = "alpine:3.20" commit = true
changes = [ "LABEL project=demo", "LABEL environment=development" ]}
source "docker" "debian" { image = "debian:bookworm-slim" commit = true
changes = [ "LABEL project=demo", "LABEL environment=development" ]}Enfin, le fichier de build qui orchestre le tout :
build { name = "multi-os"
sources = [ "source.docker.alpine", "source.docker.debian" ]
provisioner "shell" { inline = [ "echo 'Construction de ${source.name}'", "echo 'Type: ${source.type}'" ] }
post-processor "manifest" { output = "builds-manifest.json" }}Pour utiliser cette structure, pointez Packer vers le répertoire :
# Initialiser les pluginspacker init .
# Valider tous les fichierspacker validate .
# Construire toutes les imagespacker build .Commandes de développement
Section intitulée « Commandes de développement »Packer fournit plusieurs commandes pour vous aider à développer et maintenir vos templates. Ces outils vérifient la syntaxe, formatent le code et inspectent la structure. Utilisez-les systématiquement pour garantir la qualité de vos templates.
packer fmt : formater automatiquement
Section intitulée « packer fmt : formater automatiquement »La commande packer fmt reformate vos fichiers HCL2 selon les conventions officielles. Elle corrige l’indentation, aligne les signes =, et uniformise le style. Utilisez-la avant chaque commit pour maintenir un code cohérent.
# Formater tous les fichiers du répertoirepacker fmt .
# Voir les changements sans modifier (mode diff)packer fmt -diff .
# Vérifier le formatage sans modifier (utile en CI)packer fmt -check .Voici un exemple de correction appliquée par packer fmt. Avant :
source "docker" "test" {image="alpine:3.20"commit=truechanges=["LABEL test=1","ENV FOO=bar"]}Après packer fmt :
source "docker" "test" { image = "alpine:3.20" commit = true changes = ["LABEL test=1", "ENV FOO=bar"]}packer validate : vérifier la syntaxe
Section intitulée « packer validate : vérifier la syntaxe »La commande packer validate vérifie la syntaxe de vos templates sans exécuter le build. Elle détecte les erreurs de configuration, les références invalides et les arguments manquants. Exécutez-la toujours avant packer build.
# Valider un fichier spécifiquepacker validate mon-template.pkr.hcl
# Valider tous les fichiers d'un répertoirepacker validate .
# Valider avec des variablespacker validate -var="version=1.0" .Exemple de sortie en cas de succès :
The configuration is valid.Exemple de sortie en cas d’erreur :
Error: Unknown source "source.docker.alpine"
on builds.pkr.hcl line 4, in build: 4: "source.docker.alpine"
This source was not found. Please make sure that it is defined.packer inspect : analyser la structure
Section intitulée « packer inspect : analyser la structure »La commande packer inspect affiche une vue d’ensemble de votre template : variables, locals, sources et builds. Elle est utile pour comprendre la structure d’un projet sans lire tous les fichiers.
# Inspecter un templatepacker inspect mon-template.pkr.hcl
# Inspecter un répertoire multi-fichierspacker inspect .Exemple de sortie :
Packer Inspect: HCL2 mode
> input-variables:
> local-variables:
> builds:
> mon-build:
sources:
docker.alpine docker.debian
provisioners:
shell
post-processors:
0: manifestCette sortie montre que le template contient un build nommé “mon-build” qui utilise deux sources Docker, un provisioner shell et un post-processor manifest.
Exemple pratique complet
Section intitulée « Exemple pratique complet »Mettons en pratique tout ce que nous avons appris avec un exemple réaliste. Nous allons créer un template qui construit une image Docker avec curl installé, puis génère un fichier manifest.
-
Créez un répertoire pour votre projet :
Fenêtre de terminal mkdir packer-demo && cd packer-demo -
Créez le fichier
demo.pkr.hclavec le contenu suivant :packer {required_version = ">= 1.12.0"required_plugins {docker = {version = ">= 1.1.0"source = "github.com/hashicorp/docker"}}}source "docker" "base" {image = "alpine:3.20"commit = true}build {name = "demo"sources = ["source.docker.base"]provisioner "shell" {inline = ["echo '=== Mise à jour des paquets ==='","apk update"]}provisioner "shell" {inline = ["echo '=== Installation de curl ==='","apk add --no-cache curl"]}provisioner "shell" {inline = ["echo '=== Validation ==='","curl --version"]}post-processor "manifest" {output = "manifest.json"}} -
Initialisez les plugins :
Fenêtre de terminal packer init . -
Validez la syntaxe :
Fenêtre de terminal packer validate .Sortie attendue :
The configuration is valid. -
Lancez le build :
Fenêtre de terminal packer build . -
Vérifiez le résultat :
Fenêtre de terminal cat manifest.json
Le build devrait se terminer en quelques secondes avec un message similaire à :
==> Builds finished. The artifacts of successful builds are:--> demo.docker.base: Imported Docker image: sha256:abc123...Dépannage
Section intitulée « Dépannage »Cette section recense les erreurs les plus fréquentes et leurs solutions. Si vous rencontrez un problème non listé, consultez la documentation officielle ou utilisez packer build -debug pour obtenir plus d’informations.
| Symptôme | Cause probable | Solution |
|---|---|---|
Unknown source "source.xxx.yyy" | Source non définie ou mal référencée | Vérifiez le nom : source.TYPE.NOM |
Plugin not found | packer init non exécuté | Exécutez packer init . |
required_version error | Version Packer trop ancienne | Mettez à jour Packer |
Invalid block definition | Erreur de syntaxe HCL | Exécutez packer fmt . pour voir l’erreur |
Duplicate source | Deux sources avec le même nom | Renommez une des sources |
À retenir
Section intitulée « À retenir »Voici les points essentiels à retenir de ce guide sur les templates HCL2 Packer :
- HCL2 est le format recommandé — Plus lisible que JSON, avec support des commentaires et multi-fichiers
- Trois blocs fondamentaux —
packer(version/plugins),source(configuration builder),build(orchestration) - Extension
.pkr.hcl— Permet à Packer de reconnaître les templates HCL2 - Multi-fichiers natif — Packer fusionne automatiquement tous les
.pkr.hcld’un répertoire packer initobligatoire — Télécharge les plugins déclarés dansrequired_pluginspacker validateavant build — Vérifie la syntaxe sans exécuterpacker fmtpour le style — Formate automatiquement selon les conventions- Provisioners séquentiels — S’exécutent dans l’ordre de déclaration
Prochaines étapes
Section intitulée « Prochaines étapes »Maintenant que vous maîtrisez la syntaxe HCL2, approfondissez vos compétences avec ces guides :