Aller au contenu
Infrastructure as Code medium

Post-processors Packer

26 min de lecture

logo packer

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.

  • 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).

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-processorUsageSortie
manifestTraçabilité des buildsFichier JSON avec métadonnées
checksumVérification d’intégritéFichiers .sha256, .md5
compressArchivage.tar.gz, .zip, .lz4
docker-importImporter un tarball DockerImage Docker locale
docker-tagTaguer une imageImage avec tags
docker-pushPousser vers un registryImage publiée

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.

Le manifest nécessite uniquement le chemin du fichier de sortie. Packer y ajoute automatiquement les informations du build.

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

manifest.json
{
"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.

L’option custom_data permet d’ajouter vos propres métadonnées, utiles pour le versioning et l’audit en contexte CI/CD.

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

OptionTypeDescription
outputstringChemin du fichier JSON de sortie
strip_pathboolNe garder que le nom de fichier (sans chemin)
custom_datamapMétadonnées personnalisées (clé-valeur)

Le manifest est particulièrement utile pour récupérer l’identifiant de l’image dans un pipeline CI/CD :

Récupération de l'artifact_id
# Extraire le dernier artifact_id
ARTIFACT_ID=$(jq -r '.builds[-1].artifact_id' manifest.json)
echo "Image créée : $ARTIFACT_ID"
# Extraire la version personnalisée
VERSION=$(jq -r '.builds[-1].custom_data.version' manifest.json)
echo "Version : $VERSION"

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.

Le checksum nécessite au minimum le type d’algorithme. Par défaut, il génère un fichier avec l’extension correspondante.

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

OptionTypeDescription
checksum_typeslisteAlgorithmes : md5, sha1, sha224, sha256, sha384, sha512
outputstringTemplate du fichier de sortie

Le nom de fichier de sortie accepte des variables pour personnaliser le nommage :

VariableDescription
{{.BuildName}}Nom de la source (ex: example)
{{.BuilderType}}Type de builder (ex: docker)
{{.ChecksumType}}Algorithme (ex: sha256)

Après distribution de votre image, les utilisateurs peuvent vérifier l’intégrité avec les commandes standard :

Vérification SHA256
# Vérifier l'intégrité du fichier téléchargé
sha256sum -c example_sha256.checksum
# Résultat attendu :
# alpine-custom.tar: OK

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.

La compression nécessite uniquement le chemin de sortie. Packer déduit le format de l’extension.

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

OptionTypeDéfautDescription
outputstring-Chemin du fichier compressé (format déduit de l’extension)
compression_levelint6Niveau de compression (1 = rapide, 9 = maximum)
keep_input_artifactboolfalseConserver l’artefact original après compression

Le format est automatiquement détecté selon l’extension du fichier de sortie :

ExtensionFormatNotes
.tar.gz ou .tgzgzipCompression standard, bon équilibre vitesse/taille
.zipZIPCompatible Windows natif
.tartarPas de compression, seulement archivage
.lz4LZ4Compression très rapide
.tar.lz4tar + LZ4Archive avec compression rapide

Le niveau de compression (1-9) affecte le ratio taille/temps :

NiveauVitesseTailleUsage
1Très rapidePlus grandeTests, itérations rapides
6ÉquilibréMoyenneUsage général (défaut)
9LentPlus petiteDistribution, archivage long terme

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.

Packer distingue deux syntaxes avec des comportements différents :

Syntaxe des post-processors
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.

Voici un workflow typique de production : compression → checksum → manifest.

04-chainage.pkr.hcl
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ée
  • app-1.0.0.sha256 : checksum de l’archive
  • app-1.0.0-manifest.json : métadonnées avec custom_data

Vous pouvez définir plusieurs chaînes post-processors qui s’exécutent en parallèle :

Chaînes parallèles
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.

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.

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).

05-docker-workflow.pkr.hcl
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 :

OptionTypeDescription
repositorystringNom du repository (ex: local/myapp)
tagstringTag de l’image (ex: 1.0.0)
changeslisteInstructions Dockerfile à appliquer
keep_input_artifactboolConserver le tarball après import
platformstringPlateforme cible (multi-arch)

L’option changes accepte les instructions Dockerfile suivantes :

InstructionSyntaxeExemple
CMDCMD commandeCMD ["nginx", "-g", "daemon off;"]
ENTRYPOINTENTRYPOINT scriptENTRYPOINT /start.sh
ENVENV NOM valeurENV APP_ENV production
EXPOSEEXPOSE portsEXPOSE 80 443
LABELLABEL key=valueLABEL version=1.0
USERUSER usernameUSER appuser
WORKDIRWORKDIR pathWORKDIR /app
VOLUMEVOLUME pathsVOLUME /data /logs

Le post-processor docker-tag ajoute des tags supplémentaires à une image existante. Il s’utilise après docker-import dans une chaîne.

docker-tag
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.

OptionTypeDescription
repositorystringNom du repository
tagslisteTags à appliquer
forceboolForcer même si le tag existe (ignoré depuis Docker 1.12)

Le post-processor docker-push pousse l’image vers un registry Docker (Docker Hub, Harbor, ECR, GCR, etc.).

docker-push
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 :

OptionTypeDescription
loginboolSe connecter avant le push
login_serverstringURL du registry
login_usernamestringNom d’utilisateur
login_passwordstringMot de passe ou token
ecr_loginboolUtiliser l’authentification AWS ECR
keep_input_artifactboolConserver l’image locale après push

Voici un exemple complet de workflow Docker :

Workflow complet
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}"
}
}
}

Cet exemple combine plusieurs techniques pour un workflow de build complet avec multi-sources et post-processors.

06-exemple-complet.pkr.hcl
packer {
required_version = ">= 1.10.0"
required_plugins {
docker = {
version = ">= 1.1.0"
source = "github.com/hashicorp/docker"
}
}
}
# Variables
variable "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"
}
# Locals
locals {
full_version = "${var.app_version}-${var.build_number}"
artifact_name = "${var.app_name}-${local.full_version}"
output_dir = "output"
}
# Source développement
source "docker" "dev" {
image = "alpine:3.19"
export_path = "${local.output_dir}/${var.app_name}-dev.tar"
}
# Source production
source "docker" "prod" {
image = "alpine:3.19"
export_path = "${local.output_dir}/${var.app_name}-prod.tar"
}
# Build multi-sources
build {
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 :

Build avec variables
# Créer le répertoire de sortie
mkdir -p output
# Build avec numéro de build CI/CD
packer build \
-var "app_version=2.1.0" \
-var "build_number=${CI_BUILD_NUMBER:-local}" \
06-exemple-complet.pkr.hcl

Ré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.json

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 : utilisez export_path (pas commit)
  • Pour docker-import : utilisez export_path
  • Pour docker-tag : utilisez commit (pas export_path)
Export pour compress/checksum
source "docker" "example" {
image = "alpine:3.19"
export_path = "image.tar" # Requis pour compress/checksum
}

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 :

Chaînage correct
# ❌ 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" { ... }
}

Erreur : unauthorized: authentication required

Cause : Credentials incorrects ou manquants.

Solution :

  1. Vérifiez que login = true est défini
  2. Vérifiez le login_server (ex: ghcr.io, pas https://ghcr.io)
  3. Pour Docker Hub, omettez login_server
  4. Utilisez un token d’accès plutôt qu’un mot de passe
  5. Testez la connexion manuellement : docker login registry.example.com

Erreur : Le fichier manifest.json n’existe pas après le build.

Cause : Le build a échoué avant d’atteindre le post-processor.

Solution :

  1. Vérifiez les logs pour les erreurs de provisioner
  2. Exécutez avec -debug pour voir les étapes
  3. Le manifest n’est créé qu’après un build réussi
  • 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)

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.