
Ce guide vous apprend à traiter automatiquement vos artefacts après le build Packer. Vous saurez générer des manifests JSON pour la traçabilité CI/CD, créer des checksums pour vérifier l’intégrité, compresser vos images et les pousser vers un registry Docker.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Générer un manifest JSON avec les métadonnées du build
- Créer des fichiers de checksum (SHA256, MD5)
- Compresser les artefacts en tar.gz, zip ou lz4
- Chaîner plusieurs post-processors en séquence
- Importer et taguer des images Docker
- Pousser vers un registry (Docker Hub, ECR, Harbor)
Prérequis : Packer installé (guide d’installation), connaissance des templates HCL2 (syntaxe HCL2) et des provisioners (guide provisioners).
Qu’est-ce qu’un post-processor ?
Section intitulée « Qu’est-ce qu’un post-processor ? »Un post-processor est un bloc de configuration Packer qui transforme l’artefact après le build. Si le builder crée l’image et le provisioner la configure, le post-processor la prépare pour la distribution : compression, génération de checksums, upload vers un registry, etc.
Pensez aux post-processors comme à l’emballage d’un produit fini. L’usine (builder) fabrique le produit, le contrôle qualité (provisioner) le vérifie et l’améliore, puis l’emballage (post-processor) le prépare pour l’expédition avec étiquettes, codes-barres et protection.
Les post-processors s’exécutent après tous les provisioners. Ils peuvent être exécutés en parallèle (blocs post-processor séparés) ou en séquence (bloc post-processors avec plusieurs post-processor).
| Post-processor | Usage | Sortie |
|---|---|---|
manifest | Traçabilité des builds | Fichier JSON avec métadonnées |
checksum | Vérification d’intégrité | Fichiers .sha256, .md5 |
compress | Archivage | .tar.gz, .zip, .lz4 |
docker-import | Importer un tarball Docker | Image Docker locale |
docker-tag | Taguer une image | Image avec tags |
docker-push | Pousser vers un registry | Image publiée |
Post-processor manifest
Section intitulée « Post-processor manifest »Le post-processor manifest génère un fichier JSON contenant les informations du build : identifiant de l’artefact, type de builder, timestamp, et métadonnées personnalisées. Ce fichier est essentiel pour l’intégration CI/CD et la traçabilité des builds.
Configuration de base
Section intitulée « Configuration de base »Le manifest nécessite uniquement le chemin du fichier de sortie. Packer y ajoute automatiquement les informations du build.
build { sources = ["source.docker.example"]
provisioner "shell" { inline = ["apk add --no-cache curl"] }
# Génère un fichier JSON avec les infos du build post-processor "manifest" { output = "manifest.json" }}Le fichier généré contient la structure suivante :
{ "builds": [ { "name": "example", "builder_type": "docker", "build_time": 1771057729, "artifact_id": "sha256:6e83e8c766b2...", "packer_run_uuid": "0481b8f7-4954-3327-e3bf-0e5970d713c8" } ], "last_run_uuid": "0481b8f7-4954-3327-e3bf-0e5970d713c8"}Les champs automatiques incluent le nom de la source, le type de builder, le timestamp Unix, l’identifiant de l’artefact (SHA256 pour Docker, chemin pour les fichiers) et un UUID unique par exécution.
Métadonnées personnalisées
Section intitulée « Métadonnées personnalisées »L’option custom_data permet d’ajouter vos propres métadonnées, utiles pour le versioning et l’audit en contexte CI/CD.
post-processor "manifest" { output = "manifest.json"
# Chemin relatif uniquement (sans le répertoire complet) strip_path = true
# Métadonnées personnalisées custom_data = { version = var.app_version environment = var.environment build_date = timestamp() git_commit = var.git_commit author = "DevOps Team" }}Le tableau suivant décrit les options du post-processor manifest :
| Option | Type | Description |
|---|---|---|
output | string | Chemin du fichier JSON de sortie |
strip_path | bool | Ne garder que le nom de fichier (sans chemin) |
custom_data | map | Métadonnées personnalisées (clé-valeur) |
Utilisation en CI/CD
Section intitulée « Utilisation en CI/CD »Le manifest est particulièrement utile pour récupérer l’identifiant de l’image dans un pipeline CI/CD :
# Extraire le dernier artifact_idARTIFACT_ID=$(jq -r '.builds[-1].artifact_id' manifest.json)echo "Image créée : $ARTIFACT_ID"
# Extraire la version personnaliséeVERSION=$(jq -r '.builds[-1].custom_data.version' manifest.json)echo "Version : $VERSION"Post-processor checksum
Section intitulée « Post-processor checksum »Le post-processor checksum génère des fichiers de sommes de contrôle pour vérifier l’intégrité des artefacts après téléchargement ou transfert. Les algorithmes supportés incluent MD5, SHA1, SHA256, SHA384 et SHA512.
Configuration de base
Section intitulée « Configuration de base »Le checksum nécessite au minimum le type d’algorithme. Par défaut, il génère un fichier avec l’extension correspondante.
source "docker" "example" { image = "alpine:3.19" export_path = "alpine-custom.tar"}
build { sources = ["source.docker.example"]
provisioner "shell" { inline = ["apk add --no-cache htop vim"] }
# Génère des fichiers de checksum post-processor "checksum" { checksum_types = ["sha256", "md5"] output = "{{.BuildName}}_{{.ChecksumType}}.checksum" }}Ce template génère deux fichiers : example_sha256.checksum et example_md5.checksum. Chaque fichier contient le hash suivi du nom de fichier, compatible avec les outils sha256sum et md5sum.
Le tableau suivant décrit les options du post-processor checksum :
| Option | Type | Description |
|---|---|---|
checksum_types | liste | Algorithmes : md5, sha1, sha224, sha256, sha384, sha512 |
output | string | Template du fichier de sortie |
Variables de template
Section intitulée « Variables de template »Le nom de fichier de sortie accepte des variables pour personnaliser le nommage :
| Variable | Description |
|---|---|
{{.BuildName}} | Nom de la source (ex: example) |
{{.BuilderType}} | Type de builder (ex: docker) |
{{.ChecksumType}} | Algorithme (ex: sha256) |
Vérification d’intégrité
Section intitulée « Vérification d’intégrité »Après distribution de votre image, les utilisateurs peuvent vérifier l’intégrité avec les commandes standard :
# Vérifier l'intégrité du fichier téléchargésha256sum -c example_sha256.checksum
# Résultat attendu :# alpine-custom.tar: OKPost-processor compress
Section intitulée « Post-processor compress »Le post-processor compress crée une archive compressée de l’artefact. Formats supportés : .tar.gz, .zip, .tar, .lz4, .tar.lz4. Le format est déduit de l’extension du fichier de sortie.
Configuration de base
Section intitulée « Configuration de base »La compression nécessite uniquement le chemin de sortie. Packer déduit le format de l’extension.
source "docker" "example" { image = "alpine:3.19" export_path = "alpine-base.tar"}
build { sources = ["source.docker.example"]
provisioner "shell" { inline = ["apk add --no-cache nginx"] }
# Compresse l'artefact en tar.gz post-processor "compress" { output = "{{.BuildName}}-{{timestamp}}.tar.gz" compression_level = 6 keep_input_artifact = true }}Cet exemple compresse le tarball Docker en .tar.gz avec un timestamp dans le nom. L’option keep_input_artifact = true conserve le fichier original après compression.
Le tableau suivant décrit les options du post-processor compress :
| Option | Type | Défaut | Description |
|---|---|---|---|
output | string | - | Chemin du fichier compressé (format déduit de l’extension) |
compression_level | int | 6 | Niveau de compression (1 = rapide, 9 = maximum) |
keep_input_artifact | bool | false | Conserver l’artefact original après compression |
Formats supportés
Section intitulée « Formats supportés »Le format est automatiquement détecté selon l’extension du fichier de sortie :
| Extension | Format | Notes |
|---|---|---|
.tar.gz ou .tgz | gzip | Compression standard, bon équilibre vitesse/taille |
.zip | ZIP | Compatible Windows natif |
.tar | tar | Pas de compression, seulement archivage |
.lz4 | LZ4 | Compression très rapide |
.tar.lz4 | tar + LZ4 | Archive avec compression rapide |
Niveaux de compression
Section intitulée « Niveaux de compression »Le niveau de compression (1-9) affecte le ratio taille/temps :
| Niveau | Vitesse | Taille | Usage |
|---|---|---|---|
| 1 | Très rapide | Plus grande | Tests, itérations rapides |
| 6 | Équilibré | Moyenne | Usage général (défaut) |
| 9 | Lent | Plus petite | Distribution, archivage long terme |
Chaînage de post-processors
Section intitulée « Chaînage de post-processors »Les post-processors peuvent être chaînés dans un bloc post-processors (au pluriel). Chaque post-processor reçoit l’artefact du précédent, permettant de construire des pipelines de traitement.
Bloc post-processors vs post-processor
Section intitulée « Bloc post-processors vs post-processor »Packer distingue deux syntaxes avec des comportements différents :
build { sources = ["source.docker.example"]
# Bloc post-processors (pluriel) = chaîne séquentielle # Chaque post-processor reçoit l'artefact du précédent post-processors { post-processor "compress" { ... } # Étape 1 : compresse post-processor "checksum" { ... } # Étape 2 : checksum de l'archive post-processor "manifest" { ... } # Étape 3 : manifest final }
# Bloc post-processor (singulier) = parallèle # Reçoit l'artefact original, pas celui de la chaîne post-processor "manifest" { ... } # Indépendant}Dans le bloc post-processors, chaque étape traite la sortie de l’étape précédente. Les blocs post-processor individuels s’exécutent en parallèle et reçoivent l’artefact original du builder.
Exemple de chaînage complet
Section intitulée « Exemple de chaînage complet »Voici un workflow typique de production : compression → checksum → manifest.
variable "version" { type = string default = "1.0.0"}
variable "environment" { type = string default = "dev"}
build { sources = ["source.docker.app"]
provisioner "shell" { inline = [ "echo 'Installing application stack...'", "apk add --no-cache python3 py3-pip nginx" ] }
# Chaîne de post-processors post-processors { # Étape 1 : Compresser l'artefact post-processor "compress" { output = "app-${var.version}.tar.gz" compression_level = 9 keep_input_artifact = false }
# Étape 2 : Générer le checksum de l'archive compressée post-processor "checksum" { checksum_types = ["sha256"] output = "app-${var.version}.sha256" }
# Étape 3 : Générer le manifest final post-processor "manifest" { output = "app-${var.version}-manifest.json" strip_path = true custom_data = { version = var.version environment = var.environment build_date = timestamp() } } }}Résultat de l’exécution :
app-1.0.0.tar.gz: archive compresséeapp-1.0.0.sha256: checksum de l’archiveapp-1.0.0-manifest.json: métadonnées avec custom_data
Chaînes parallèles
Section intitulée « Chaînes parallèles »Vous pouvez définir plusieurs chaînes post-processors qui s’exécutent en parallèle :
build { sources = ["source.docker.example"]
# Chaîne 1 : Archive tar.gz post-processors { post-processor "compress" { output = "app.tar.gz" } post-processor "checksum" { checksum_types = ["sha256"] output = "app.tar.gz.sha256" } }
# Chaîne 2 : Archive zip (en parallèle) post-processors { post-processor "compress" { output = "app.zip" } post-processor "checksum" { checksum_types = ["sha256"] output = "app.zip.sha256" } }}Cette configuration produit deux paires de fichiers (tar.gz + checksum, zip + checksum) en parallèle.
Post-processors Docker
Section intitulée « Post-processors Docker »Les post-processors Docker permettent d’intégrer Packer dans un workflow de conteneurs : importer un tarball comme image, la taguer et la pousser vers un registry.
docker-import
Section intitulée « docker-import »Le post-processor docker-import importe un tarball Docker (créé avec export_path) comme image dans le daemon Docker local. Il permet également de modifier les métadonnées de l’image (équivalent aux instructions Dockerfile).
packer { required_plugins { docker = { version = ">= 1.1.0" source = "github.com/hashicorp/docker" } }}
variable "image_name" { type = string default = "myapp"}
variable "image_version" { type = string default = "1.0.0"}
source "docker" "app" { image = "alpine:3.19" export_path = "myapp.tar"}
build { sources = ["source.docker.app"]
provisioner "shell" { inline = [ "echo 'Building ${var.image_name}:${var.image_version}'", "apk add --no-cache curl jq" ] }
# Importer le tarball comme image Docker post-processor "docker-import" { repository = "local/${var.image_name}" tag = var.image_version
# Métadonnées Docker (équivalent Dockerfile) changes = [ "ENV APP_VERSION=${var.image_version}", "LABEL maintainer=devops@example.com", "LABEL version=${var.image_version}", "WORKDIR /app", "EXPOSE 8080" ] }}Le tableau suivant décrit les options du post-processor docker-import :
| Option | Type | Description |
|---|---|---|
repository | string | Nom du repository (ex: local/myapp) |
tag | string | Tag de l’image (ex: 1.0.0) |
changes | liste | Instructions Dockerfile à appliquer |
keep_input_artifact | bool | Conserver le tarball après import |
platform | string | Plateforme cible (multi-arch) |
Instructions changes supportées
Section intitulée « Instructions changes supportées »L’option changes accepte les instructions Dockerfile suivantes :
| Instruction | Syntaxe | Exemple |
|---|---|---|
CMD | CMD commande | CMD ["nginx", "-g", "daemon off;"] |
ENTRYPOINT | ENTRYPOINT script | ENTRYPOINT /start.sh |
ENV | ENV NOM valeur | ENV APP_ENV production |
EXPOSE | EXPOSE ports | EXPOSE 80 443 |
LABEL | LABEL key=value | LABEL version=1.0 |
USER | USER username | USER appuser |
WORKDIR | WORKDIR path | WORKDIR /app |
VOLUME | VOLUME paths | VOLUME /data /logs |
docker-tag
Section intitulée « docker-tag »Le post-processor docker-tag ajoute des tags supplémentaires à une image existante. Il s’utilise après docker-import dans une chaîne.
post-processors { post-processor "docker-import" { repository = "local/${var.image_name}" tag = var.image_version }
# Ajouter des tags supplémentaires post-processor "docker-tag" { repository = "local/${var.image_name}" tags = ["latest", var.image_version, "${var.image_version}-alpine"] }}Résultat : l’image aura les tags 1.0.0, latest et 1.0.0-alpine.
| Option | Type | Description |
|---|---|---|
repository | string | Nom du repository |
tags | liste | Tags à appliquer |
force | bool | Forcer même si le tag existe (ignoré depuis Docker 1.12) |
docker-push
Section intitulée « docker-push »Le post-processor docker-push pousse l’image vers un registry Docker (Docker Hub, Harbor, ECR, GCR, etc.).
post-processors { post-processor "docker-import" { repository = "registry.example.com/myapp" tag = var.image_version }
post-processor "docker-tag" { repository = "registry.example.com/myapp" tags = ["latest", var.image_version] }
# Pousser vers le registry post-processor "docker-push" { login = true login_server = "registry.example.com" login_username = var.registry_username login_password = var.registry_password }}Le tableau suivant décrit les options du post-processor docker-push :
| Option | Type | Description |
|---|---|---|
login | bool | Se connecter avant le push |
login_server | string | URL du registry |
login_username | string | Nom d’utilisateur |
login_password | string | Mot de passe ou token |
ecr_login | bool | Utiliser l’authentification AWS ECR |
keep_input_artifact | bool | Conserver l’image locale après push |
Workflow Docker complet
Section intitulée « Workflow Docker complet »Voici un exemple complet de workflow Docker :
build { sources = ["source.docker.app"]
provisioner "shell" { inline = ["apk add --no-cache ca-certificates curl"] }
# Chaîne Docker : import → tag → push post-processors { post-processor "docker-import" { repository = "ghcr.io/myorg/myapp" tag = var.version changes = [ "ENV APP_VERSION=${var.version}", "LABEL org.opencontainers.image.version=${var.version}", "EXPOSE 8080" ] }
post-processor "docker-tag" { repository = "ghcr.io/myorg/myapp" tags = ["latest", var.version] }
post-processor "docker-push" { login = true login_server = "ghcr.io" login_username = var.github_username login_password = var.github_token } }
# Manifest pour traçabilité (parallèle) post-processor "manifest" { output = "docker-manifest.json" custom_data = { registry = "ghcr.io/myorg/myapp" version = var.version tags = "latest,${var.version}" } }}Exemple complet de production
Section intitulée « Exemple complet de production »Cet exemple combine plusieurs techniques pour un workflow de build complet avec multi-sources et post-processors.
packer { required_version = ">= 1.10.0"
required_plugins { docker = { version = ">= 1.1.0" source = "github.com/hashicorp/docker" } }}
# Variablesvariable "app_name" { type = string description = "Nom de l'application" default = "mywebapp"}
variable "app_version" { type = string description = "Version de l'application" default = "2.0.0"}
variable "build_number" { type = string description = "Numéro de build CI/CD" default = "local"}
# Localslocals { full_version = "${var.app_version}-${var.build_number}" artifact_name = "${var.app_name}-${local.full_version}" output_dir = "output"}
# Source développementsource "docker" "dev" { image = "alpine:3.19" export_path = "${local.output_dir}/${var.app_name}-dev.tar"}
# Source productionsource "docker" "prod" { image = "alpine:3.19" export_path = "${local.output_dir}/${var.app_name}-prod.tar"}
# Build multi-sourcesbuild { name = "complete-build"
sources = [ "source.docker.dev", "source.docker.prod" ]
# Provisioners communs provisioner "shell" { inline = [ "echo '=== Building ${var.app_name} v${local.full_version} ==='", "apk add --no-cache curl wget" ] }
# Provisioner spécifique à la production provisioner "shell" { only = ["docker.prod"] inline = [ "echo 'Adding production tools...'", "apk add --no-cache nginx" ] }
# Chaîne de post-processors par source post-processors { post-processor "compress" { output = "${local.output_dir}/${local.artifact_name}-${source.name}.tar.gz" compression_level = 9 keep_input_artifact = false }
post-processor "checksum" { checksum_types = ["sha256"] output = "${local.output_dir}/${local.artifact_name}-${source.name}.sha256" } }
# Manifest consolidé post-processor "manifest" { output = "${local.output_dir}/manifest-${local.artifact_name}.json" strip_path = true custom_data = { app_name = var.app_name app_version = var.app_version full_version = local.full_version build_number = var.build_number build_date = timestamp() } }}Exécution avec variables CI/CD :
# Créer le répertoire de sortiemkdir -p output
# Build avec numéro de build CI/CDpacker build \ -var "app_version=2.1.0" \ -var "build_number=${CI_BUILD_NUMBER:-local}" \ 06-exemple-complet.pkr.hclRésultat :
output/├── mywebapp-2.1.0-42-dev.tar.gz├── mywebapp-2.1.0-42-dev.sha256├── mywebapp-2.1.0-42-prod.tar.gz├── mywebapp-2.1.0-42-prod.sha256└── manifest-mywebapp-2.1.0-42.jsonDépannage classique
Section intitulée « Dépannage classique »Artefact non trouvé par le post-processor
Section intitulée « Artefact non trouvé par le post-processor »Erreur : Post-processor failed: artifact could not be found
Cause : Le builder génère un artefact non compatible avec le post-processor.
Solution : Vérifiez que le builder produit le bon type d’artefact :
- Pour
compress/checksum: utilisezexport_path(pascommit) - Pour
docker-import: utilisezexport_path - Pour
docker-tag: utilisezcommit(pasexport_path)
source "docker" "example" { image = "alpine:3.19" export_path = "image.tar" # Requis pour compress/checksum}Chaînage ne fonctionne pas
Section intitulée « Chaînage ne fonctionne pas »Erreur : Le checksum porte sur le fichier original, pas l’archive compressée.
Cause : Utilisation de blocs post-processor séparés au lieu d’un bloc post-processors.
Solution : Regroupez les post-processors dans un bloc post-processors :
# ❌ Incorrect : blocs séparés (pas de chaînage)post-processor "compress" { ... }post-processor "checksum" { ... }
# ✅ Correct : bloc post-processors (chaînage)post-processors { post-processor "compress" { ... } post-processor "checksum" { ... }}docker-push échoue avec “unauthorized”
Section intitulée « docker-push échoue avec “unauthorized” »Erreur : unauthorized: authentication required
Cause : Credentials incorrects ou manquants.
Solution :
- Vérifiez que
login = trueest défini - Vérifiez le
login_server(ex:ghcr.io, pashttps://ghcr.io) - Pour Docker Hub, omettez
login_server - Utilisez un token d’accès plutôt qu’un mot de passe
- Testez la connexion manuellement :
docker login registry.example.com
Manifest non créé
Section intitulée « Manifest non créé »Erreur : Le fichier manifest.json n’existe pas après le build.
Cause : Le build a échoué avant d’atteindre le post-processor.
Solution :
- Vérifiez les logs pour les erreurs de provisioner
- Exécutez avec
-debugpour voir les étapes - Le manifest n’est créé qu’après un build réussi
À retenir
Section intitulée « À retenir »- Les post-processors transforment les artefacts après le build (compression, checksums, upload)
- Le manifest génère un JSON avec les métadonnées du build, essentiel pour la traçabilité CI/CD
- Le checksum crée des fichiers de vérification d’intégrité (préférez SHA256+)
- Le compress archive en tar.gz, zip ou lz4 avec niveau de compression configurable
- Les blocs
post-processors(pluriel) créent des chaînes séquentielles où chaque étape reçoit l’artefact du précédent - Les blocs
post-processor(singulier) s’exécutent en parallèle sur l’artefact original - docker-import importe un tarball comme image, docker-tag ajoute des tags, docker-push publie
- Les credentials ne doivent jamais être en clair dans les templates (utilisez des variables)