Aller au contenu
Infrastructure as Code medium

Templates HCL2 Packer : syntaxe et structure

21 min de lecture

logo packer

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.

  • La syntaxe HCL2 et ses avantages par rapport à JSON
  • Les trois blocs essentiels : packer, source et build
  • 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).

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 :

AspectJSONHCL2
LisibilitéStructure dense, difficile à parcourirClair, proche du langage naturel
CommentairesNon supportés# ou // supportés
VariablesLimitées, syntaxe complexeNatives, avec typage et validation
RéutilisationCopier-collerBlocs source réutilisables
Multi-fichiersImpossibleNatif (.pkr.hcl)
FormatageManuelpacker fmt automatique
Débogagepacker 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'"]
}
}

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.

Packer identifie les templates HCL2 grâce à leur extension. Voici les conventions à respecter :

ExtensionUsage
*.pkr.hclTemplate HCL2 (recommandé)
*.pkr.jsonTemplate JSON au format HCL2
*.jsonAncien 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.

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 bloc
type_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érales
image = "alpine:3.20" # chaîne
commit = true # booléen
count = 3 # nombre
# Listes et maps
changes = ["LABEL app=demo", "ENV FOO=bar"]
tags = { environment = "dev", project = "demo" }

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 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érateurSignificationExemple
=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 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 configuration
source "docker" "alpine" {
image = "alpine:3.20"
commit = true
}
source "docker" "debian" {
image = "debian:bookworm-slim"
commit = true
}

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}'"
]
}
}

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.

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 projet

Chaque fichier a une responsabilité claire :

FichierContenuResponsabilité
plugins.pkr.hclBloc packerVersion et dépendances
sources.pkr.hclBlocs sourceConfiguration des builders
builds.pkr.hclBlocs buildOrchestration
variables.pkr.hclBlocs variableParamétrage

Voici un projet multi-fichiers complet et fonctionnel. Commençons par le fichier de plugins :

plugins.pkr.hcl
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 :

sources.pkr.hcl
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 :

builds.pkr.hcl
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 :

Fenêtre de terminal
# Initialiser les plugins
packer init .
# Valider tous les fichiers
packer validate .
# Construire toutes les images
packer build .

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.

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.

Fenêtre de terminal
# Formater tous les fichiers du répertoire
packer 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=true
changes=["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"]
}

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.

Fenêtre de terminal
# Valider un fichier spécifique
packer validate mon-template.pkr.hcl
# Valider tous les fichiers d'un répertoire
packer validate .
# Valider avec des variables
packer 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.

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.

Fenêtre de terminal
# Inspecter un template
packer inspect mon-template.pkr.hcl
# Inspecter un répertoire multi-fichiers
packer 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:
manifest

Cette sortie montre que le template contient un build nommé “mon-build” qui utilise deux sources Docker, un provisioner shell et un post-processor manifest.

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.

  1. Créez un répertoire pour votre projet :

    Fenêtre de terminal
    mkdir packer-demo && cd packer-demo
  2. Créez le fichier demo.pkr.hcl avec 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"
    }
    }
  3. Initialisez les plugins :

    Fenêtre de terminal
    packer init .
  4. Validez la syntaxe :

    Fenêtre de terminal
    packer validate .

    Sortie attendue : The configuration is valid.

  5. Lancez le build :

    Fenêtre de terminal
    packer build .
  6. 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...

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ômeCause probableSolution
Unknown source "source.xxx.yyy"Source non définie ou mal référencéeVérifiez le nom : source.TYPE.NOM
Plugin not foundpacker init non exécutéExécutez packer init .
required_version errorVersion Packer trop ancienneMettez à jour Packer
Invalid block definitionErreur de syntaxe HCLExécutez packer fmt . pour voir l’erreur
Duplicate sourceDeux sources avec le même nomRenommez une des sources

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 fondamentauxpacker (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.hcl d’un répertoire
  • packer init obligatoire — Télécharge les plugins déclarés dans required_plugins
  • packer validate avant build — Vérifie la syntaxe sans exécuter
  • packer fmt pour le style — Formate automatiquement selon les conventions
  • Provisioners séquentiels — S’exécutent dans l’ordre de déclaration

Maintenant que vous maîtrisez la syntaxe HCL2, approfondissez vos compétences avec ces guides :

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.