
Ce guide vous apprend à paramétrer vos templates Packer avec les variables, locals et fonctions. Vous pourrez réutiliser le même template pour créer des images de développement, staging et production, simplement en changeant un fichier de configuration.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Déclarer des variables avec typage et validation
- Créer des calculs intermédiaires avec les locals
- Utiliser les fonctions HCL intégrées
- Organiser vos variables avec les fichiers
.pkrvars.hcl - Récupérer des données externes avec les data sources
Prérequis : Packer installé (guide d’installation), connaissance des templates HCL2 (syntaxe HCL2).
Pourquoi paramétrer ses templates ?
Section intitulée « Pourquoi paramétrer ses templates ? »Imaginez que vous devez créer des images pour trois environnements : développement, staging et production. Sans variables, vous auriez trois templates presque identiques, avec juste quelques différences (packages installés, labels, configuration). Cette duplication pose plusieurs problèmes :
| Problème | Conséquence |
|---|---|
| Code dupliqué | Toute modification doit être répétée 3 fois |
| Incohérences | Oubli de propager un changement |
| Maintenance difficile | Plus de code = plus de bugs |
| Pas de traçabilité | Impossible de voir les différences rapidement |
Avec les variables Packer, vous créez un seul template et vous passez les valeurs spécifiques à chaque environnement via des fichiers de configuration ou la ligne de commande. Le template devient une recette paramétrable.
Variables d’entrée
Section intitulée « Variables d’entrée »Les variables d’entrée (ou input variables) sont les paramètres de votre template. Elles permettent de personnaliser le build sans modifier le code source. Cette section explique comment les déclarer, les typer et les utiliser.
Déclarer une variable
Section intitulée « Déclarer une variable »Pour déclarer une variable, utilisez le bloc variable. Le nom de la variable doit être unique dans le template. Voici la syntaxe de base :
variable "image_name" { type = string default = "alpine" description = "Nom de l'image de base à utiliser"}Chaque variable peut avoir plusieurs attributs. Le tableau suivant décrit les plus importants :
| Attribut | Obligatoire | Description |
|---|---|---|
type | Non | Type de la valeur (string, number, bool, list, map, object) |
default | Non | Valeur par défaut (rend la variable optionnelle) |
description | Non | Documentation de la variable |
sensitive | Non | Si true, la valeur est masquée dans les logs |
validation | Non | Règles de validation personnalisées |
Types de variables
Section intitulée « Types de variables »Packer supporte plusieurs types de variables, des plus simples (chaînes, nombres) aux plus complexes (listes, maps, objets). Choisir le bon type permet à Packer de valider les valeurs et de détecter les erreurs avant le build.
Types simples
Section intitulée « Types simples »Les types simples sont les briques de base. Voici comment les déclarer :
# Chaîne de caractèresvariable "image_tag" { type = string default = "3.20" description = "Tag de l'image Docker"}
# Nombre (entier ou décimal)variable "timeout_seconds" { type = number default = 300 description = "Timeout du build en secondes"}
# Booléenvariable "enable_debug" { type = bool default = false description = "Activer les logs de debug"}Types complexes
Section intitulée « Types complexes »Les types complexes permettent de regrouper plusieurs valeurs. Utilisez-les pour les listes de packages, les configurations, ou les tags :
# Liste de chaînesvariable "packages_to_install" { type = list(string) default = ["curl", "jq", "vim"] description = "Liste des paquets à installer"}
# Map clé-valeurvariable "labels" { type = map(string) default = { maintainer = "devops@example.com" environment = "development" } description = "Labels à ajouter à l'image"}
# Object avec structure définievariable "instance_config" { type = object({ size = string count = number public = bool }) default = { size = "small" count = 1 public = false } description = "Configuration de l'instance"}La différence entre map et object est importante. Une map peut contenir n’importe quelles clés, mais toutes les valeurs doivent avoir le même type. Un object a une structure fixe avec des clés définies et potentiellement des types différents.
Utiliser une variable
Section intitulée « Utiliser une variable »Une fois déclarée, une variable est accessible via la syntaxe var.nom_variable. Vous pouvez l’utiliser dans les blocs source, build, locals et dans d’autres expressions :
source "docker" "example" { # Interpolation dans une chaîne image = "${var.image_name}:${var.image_tag}"
# Utilisation directe d'un booléen commit = var.enable_debug
# Transformation d'une map en liste changes = [for k, v in var.labels : "LABEL ${k}=${v}"]}
build { sources = ["source.docker.example"]
provisioner "shell" { # Jointure d'une liste inline = ["apk add --no-cache ${join(" ", var.packages_to_install)}"] }}Valider une variable
Section intitulée « Valider une variable »Packer permet de définir des règles de validation personnalisées pour vos variables. Ces règles s’exécutent avant le build et arrêtent l’exécution si une valeur est invalide. C’est particulièrement utile pour détecter les erreurs tôt.
Chaque bloc validation contient deux attributs : condition (expression booléenne) et error_message (message affiché si la condition est fausse).
variable "environment" { type = string description = "Environnement cible (dev, staging, prod)"
validation { condition = contains(["dev", "staging", "prod"], var.environment) error_message = "L'environnement doit être 'dev', 'staging' ou 'prod'." }}Vous pouvez utiliser des expressions régulières pour valider le format d’une valeur. La fonction can() teste si une expression s’évalue sans erreur :
variable "project_name" { type = string description = "Nom du projet (alphanumériques et tirets)"
validation { condition = length(var.project_name) >= 3 && length(var.project_name) <= 20 error_message = "Le nom doit contenir entre 3 et 20 caractères." }
validation { condition = can(regex("^[a-zA-Z][a-zA-Z0-9-]*$", var.project_name)) error_message = "Le nom doit commencer par une lettre." }}Variables sensibles
Section intitulée « Variables sensibles »Pour les tokens, mots de passe et autres secrets, utilisez l’attribut sensitive = true. Packer masquera la valeur dans les logs et la sortie de packer inspect :
variable "api_token" { type = string sensitive = true description = "Token API (sera masqué dans les logs)"}Résultat de packer inspect :
> input-variables:var.api_token: "<sensitive>"Passer des valeurs aux variables
Section intitulée « Passer des valeurs aux variables »Packer offre plusieurs méthodes pour assigner des valeurs aux variables. Cette section présente chaque méthode, de la plus simple à la plus structurée.
Via la ligne de commande
Section intitulée « Via la ligne de commande »L’option -var permet de passer une valeur directement. Utilisez cette méthode pour les tests rapides ou les valeurs qui changent à chaque build :
# Variable simplepacker build -var="image_tag=3.20" .
# Plusieurs variablespacker build -var="image_tag=3.20" -var="environment=prod" .
# Liste (syntaxe JSON)packer build -var='packages=["curl","jq"]' .
# Map (syntaxe JSON)packer build -var='labels={"env":"prod","team":"backend"}' .Via les variables d’environnement
Section intitulée « Via les variables d’environnement »Packer lit automatiquement les variables d’environnement préfixées par PKR_VAR_. Cette méthode est idéale pour les pipelines CI/CD où les secrets sont injectés via l’environnement :
# Définir la variableexport PKR_VAR_api_token="mon-token-secret"export PKR_VAR_environment="prod"
# Le build utilise automatiquement ces valeurspacker build .Via les fichiers pkrvars
Section intitulée « Via les fichiers pkrvars »Pour les valeurs stables par environnement, utilisez des fichiers .pkrvars.hcl. Ces fichiers contiennent uniquement des assignations de valeurs :
image_name = "alpine"image_tag = "3.20"environment = "dev"
packages = [ "curl", "jq", "vim", "git"]
labels = { Team = "backend" Project = "demo-app"}Utilisez -var-file pour charger le fichier :
# Build avec configuration devpacker build -var-file="dev.pkrvars.hcl" .
# Build avec configuration prodpacker build -var-file="prod.pkrvars.hcl" .Auto-chargement des fichiers
Section intitulée « Auto-chargement des fichiers »Les fichiers nommés *.auto.pkrvars.hcl sont chargés automatiquement sans avoir besoin de -var-file. Utilisez cette convention pour les valeurs par défaut de votre projet :
mon-projet/├── build.pkr.hcl├── variables.pkr.hcl├── defaults.auto.pkrvars.hcl # Chargé automatiquement└── prod.pkrvars.hcl # À charger explicitementOrdre de priorité
Section intitulée « Ordre de priorité »Quand une même variable est définie plusieurs fois, Packer utilise la dernière valeur rencontrée. Voici l’ordre de priorité, du plus faible au plus fort :
- Valeur
defaultdans la déclaration (priorité la plus faible) - Variables d’environnement
PKR_VAR_* - Fichiers
*.auto.pkrvars.hcl(ordre alphabétique) - Fichiers
-var-file(ordre d’apparition) - Options
-var(priorité la plus forte)
Cette hiérarchie permet de définir des valeurs par défaut raisonnables, de les surcharger par environnement, et de les “forcer” ponctuellement via la ligne de commande.
Variables locales (locals)
Section intitulée « Variables locales (locals) »Les variables locales, ou “locals”, sont des calculs intermédiaires que vous pouvez utiliser dans votre template. Contrairement aux variables d’entrée, elles ne sont pas paramétrables de l’extérieur : leur valeur est calculée à partir des variables d’entrée, d’autres locals, ou de fonctions.
Pourquoi utiliser les locals ?
Section intitulée « Pourquoi utiliser les locals ? »Les locals résolvent plusieurs problèmes courants :
| Problème | Solution avec locals |
|---|---|
| Expressions répétées | Calculer une fois, utiliser partout |
| Code illisible | Nommer les calculs complexes |
| Maintenance difficile | Centraliser la logique |
| Valeurs conditionnelles | Calculer selon le contexte |
Syntaxe des locals
Section intitulée « Syntaxe des locals »Il existe deux syntaxes pour définir des locals. Le bloc locals (pluriel) permet de grouper plusieurs valeurs :
locals { # Concaténation simple full_image = "${var.base_image}:${var.image_tag}"
# Nom unique avec timestamp build_id = "${var.project}-${formatdate("YYYYMMDD-hhmm", timestamp())}"
# Map de tags communs common_tags = { Project = var.project Environment = var.environment ManagedBy = "packer" BuildDate = timestamp() }
# Sélection conditionnelle packages = var.environment == "prod" ? ["curl", "ca-certificates"] : ["curl", "jq", "vim"]}Le bloc local (singulier) permet de définir une seule valeur avec l’option sensitive :
local "secret_hash" { expression = md5("${var.project}-${var.environment}") sensitive = true}Utiliser les locals
Section intitulée « Utiliser les locals »Les locals sont accessibles via la syntaxe local.nom_local :
source "docker" "app" { image = local.full_image
# Conversion de la map en labels Docker changes = [for k, v in local.common_tags : "LABEL ${k}=${v}"]}
build { name = local.build_id
sources = ["source.docker.app"]
provisioner "shell" { inline = ["apk add --no-cache ${join(" ", local.packages)}"] }}Locals et dépendances
Section intitulée « Locals et dépendances »Les locals peuvent référencer d’autres locals, mais attention aux cycles. Un local ne peut pas se référencer lui-même, ni référencer un local qui le référence (directement ou indirectement) :
locals { # OK : a référence une variable a = var.project
# OK : b référence a b = "${local.a}-suffix"
# ERREUR : c référence d, et d référence c # c = local.d # d = local.c}Fonctions HCL
Section intitulée « Fonctions HCL »Packer inclut de nombreuses fonctions intégrées pour manipuler les données. Ces fonctions sont identiques à celles de Terraform, ce qui facilite le transfert de connaissances entre les deux outils.
Fonctions de chaînes
Section intitulée « Fonctions de chaînes »Les fonctions de chaînes manipulent le texte. Voici les plus utilisées :
locals { # Conversion de casse project_lower = lower("Mon-Projet") # "mon-projet" project_upper = upper("mon-projet") # "MON-PROJET"
# Remplacement slug = replace("mon projet", " ", "-") # "mon-projet"
# Sous-chaîne prefix = substr("mon-projet", 0, 3) # "mon"
# Trim cleaned = trimspace(" hello ") # "hello" trim_prefix = trimprefix("v1.0.0", "v") # "1.0.0"
# Formatage (comme printf) name = format("%s-build-%d", "app", 42) # "app-build-42"
# Jointure et split tags = join(",", ["a", "b", "c"]) # "a,b,c" parts = split(",", "a,b,c") # ["a", "b", "c"]}Depuis Packer 1.12, des fonctions supplémentaires sont disponibles :
locals { # strcontains (v1.12+) is_web = strcontains("web-server", "web") # true
# startswith / endswith (v1.13+) is_v1 = startswith("v1.2.0", "v1") # true is_tar = endswith("file.tar.gz", ".tar.gz") # true}Fonctions de collections
Section intitulée « Fonctions de collections »Les fonctions de collections opèrent sur les listes et les maps :
locals { liste = ["a", "b", "c", "a"] config = { port = "8080", host = "0.0.0.0" }
# Longueur count = length(local.liste) # 4
# Contient has_b = contains(local.liste, "b") # true
# Concaténation de listes merged = concat(["x"], local.liste) # ["x", "a", "b", "c", "a"]
# Valeurs uniques unique = distinct(local.liste) # ["a", "b", "c"]
# Aplatir les listes imbriquées flat = flatten([["a", "b"], ["c"]]) # ["a", "b", "c"]
# Clés et valeurs d'une map keys = keys(local.config) # ["host", "port"] vals = values(local.config) # ["0.0.0.0", "8080"]
# Fusion de maps full = merge(local.config, { debug = "true" })
# Lookup avec valeur par défaut port = lookup(local.config, "port", "3000") # "8080" ssl = lookup(local.config, "ssl", "false") # "false"
# Coalesce : premier non-null, non-vide result = coalesce("", null, "valeur") # "valeur"}Depuis Packer 1.12, vous disposez de fonctions de test sur les collections :
locals { numbers = [1, 2, 3, 4, 5]
# alltrue : toutes les conditions vraies ? all_positive = alltrue([for n in local.numbers : n > 0]) # true
# anytrue : au moins une condition vraie ? has_five = anytrue([for n in local.numbers : n == 5]) # true
# sum (v1.13+) total = sum(local.numbers) # 15}Fonctions de dates
Section intitulée « Fonctions de dates »Les fonctions de dates manipulent les timestamps :
locals { # Timestamp actuel (format RFC 3339) now = timestamp() # "2026-02-14T09:30:00Z"
# Formatage personnalisé date = formatdate("DD/MM/YYYY", timestamp()) # "14/02/2026" time = formatdate("hh:mm:ss", timestamp()) # "09:30:00" iso = formatdate("YYYY-MM-DD'T'HH:mm:ssZ", timestamp())
# Nom de fichier unique filename = "build-${formatdate("YYYYMMDD-hhmm", timestamp())}.log"}Les codes de format sont inspirés de Go :
| Code | Signification | Exemple |
|---|---|---|
YYYY | Année 4 chiffres | 2026 |
MM | Mois 2 chiffres | 02 |
DD | Jour 2 chiffres | 14 |
hh | Heure 24h | 09 |
mm | Minutes | 30 |
ss | Secondes | 00 |
Fonctions de hashage et encodage
Section intitulée « Fonctions de hashage et encodage »Ces fonctions transforment les données pour le stockage ou la sécurité :
locals { # Hashage hash_md5 = md5("hello") # "5d41402abc4b2a76..." hash_sha = sha256("hello") # "2cf24dba5fb0a30e..."
# UUID unique id = uuidv4() # "a1b2c3d4-e5f6-..."
# Base64 encoded = base64encode("Hello World") # "SGVsbG8gV29ybGQ=" decoded = base64decode("SGVsbG8gV29ybGQ=") # "Hello World"
# JSON json = jsonencode({ name = "app", version = "1.0" })
# URL encode safe = urlencode("hello world") # "hello%20world"}Fonctions numériques
Section intitulée « Fonctions numériques »Ces fonctions effectuent des calculs mathématiques :
locals { # Min / Max smallest = min(1, 5, 3) # 1 largest = max(1, 5, 3) # 5
# Valeur absolue abs_val = abs(-5) # 5
# Arrondi up = ceil(3.2) # 4 down = floor(3.8) # 3}Data sources
Section intitulée « Data sources »Les data sources permettent de récupérer des informations externes pendant le build. Contrairement aux variables qui sont définies avant le build, les data sources sont évaluées au moment de l’exécution.
Cas d’usage
Section intitulée « Cas d’usage »Les data sources sont utiles pour :
- Récupérer l’ID de la dernière AMI disponible
- Lire des secrets depuis un gestionnaire de secrets
- Interroger une API pour obtenir une configuration
Un data source se déclare avec un bloc data. Le premier label est le type, le second est le nom :
data "http" "config" { url = "https://api.example.com/config"}Les attributs de sortie sont accessibles via data.TYPE.NOM.attribut :
locals { config_body = data.http.config.body}Exemple avec HTTP
Section intitulée « Exemple avec HTTP »Voici un exemple qui récupère un fichier de configuration depuis une URL :
variable "config_url" { type = string default = "https://raw.githubusercontent.com/example/config/main/app.json" description = "URL du fichier de configuration"}
data "http" "app_config" { url = var.config_url
request_headers = { Accept = "application/json" }}
locals { # Parser le JSON récupéré config = jsondecode(data.http.app_config.body) version = local.config.version}Exemple pratique complet
Section intitulée « Exemple pratique complet »Mettons en pratique tous ces concepts avec un exemple réaliste. Ce template crée des images Docker paramétrables par environnement.
-
Créez la structure du projet :
Fenêtre de terminal mkdir -p packer-variables-demo && cd packer-variables-demo -
Créez le fichier
variables.pkr.hclavec les déclarations :variable "app_name" {type = stringdefault = "myapp"description = "Nom de l'application"validation {condition = can(regex("^[a-z][a-z0-9-]*$", var.app_name))error_message = "Le nom doit être en minuscules."}}variable "app_version" {type = stringdefault = "1.0.0"description = "Version (format semver)"validation {condition = can(regex("^[0-9]+\\.[0-9]+\\.[0-9]+$", var.app_version))error_message = "Format X.Y.Z requis."}}variable "environment" {type = stringdefault = "dev"validation {condition = contains(["dev", "staging", "prod"], var.environment)error_message = "Environnement invalide."}}variable "packages" {type = map(list(string))default = {dev = ["curl", "jq", "vim", "git"]staging = ["curl", "jq", "git"]prod = ["curl", "ca-certificates"]}} -
Créez le fichier
build.pkr.hclavec la logique :packer {required_version = ">= 1.12.0"required_plugins {docker = {version = ">= 1.1.0"source = "github.com/hashicorp/docker"}}}locals {image_name = "${var.app_name}-${var.environment}"build_date = formatdate("YYYY-MM-DD", timestamp())build_id = substr(md5("${var.app_name}-${timestamp()}"), 0, 8)env_packages = lookup(var.packages, var.environment, var.packages["dev"])is_production = var.environment == "prod"labels = {"app.name" = var.app_name"app.version" = var.app_version"app.environment" = var.environment"app.build.date" = local.build_date}}source "docker" "app" {image = "alpine:3.20"commit = truechanges = [for k, v in local.labels : "LABEL ${k}=${v}"]}build {name = local.image_namesources = ["source.docker.app"]provisioner "shell" {inline = ["echo 'Build: ${local.image_name} v${var.app_version}'","echo 'ID: ${local.build_id}'","echo 'Production: ${local.is_production}'","apk add --no-cache ${join(" ", local.env_packages)}"]}} -
Créez les fichiers de variables par environnement :
dev.pkrvars.hcl app_name = "myapp"app_version = "1.0.0-dev"environment = "dev"prod.pkrvars.hcl app_name = "myapp"app_version = "1.0.0"environment = "prod" -
Testez les builds :
Fenêtre de terminal packer init .packer validate -var-file=dev.pkrvars.hcl .packer build -var-file=dev.pkrvars.hcl .
Dépannage
Section intitulée « Dépannage »Cette section liste les erreurs courantes liées aux variables et leurs solutions.
| Symptôme | Cause probable | Solution |
|---|---|---|
variable "X" is not set | Variable obligatoire sans valeur | Passez une valeur via -var, PKR_VAR_* ou pkrvars |
Invalid value for variable | Échec de validation | Vérifiez que la valeur respecte les règles |
Cannot use local in data source | Local dans un data source | Utilisez une variable à la place |
Cycle detected | Locals qui se référencent mutuellement | Restructurez les dépendances |
Invalid variable type | Type incorrect sur ligne de commande | Utilisez la syntaxe JSON pour les types complexes |
À retenir
Section intitulée « À retenir »Voici les points essentiels de ce guide sur les variables et fonctions Packer :
- Variables d’entrée — Paramétrez vos templates avec
variable {}, typez et validez - Fichiers pkrvars — Séparez les valeurs par environnement (dev, staging, prod)
- Ordre de priorité — default < env < auto.pkrvars < -var-file < -var
- Locals — Calculez des valeurs intermédiaires, évitez la répétition
- Fonctions v1.12+ —
strcontains,alltrue,anytruesimplifient les conditions - Fonctions v1.13+ —
startswith,endswith,sumpour plus de flexibilité - Sensitive — Masquez les secrets dans les logs avec
sensitive = true - Data sources — Récupérez des infos externes, mais utilisez
var(paslocal)
Prochaines étapes
Section intitulée « Prochaines étapes »Maintenant que vous maîtrisez les variables, continuez avec ces guides :