Blog Post

Azure Infrastructure Blog
7 MIN READ

Automating Azure VM Image Baking and Validation with Packer and Azure DevOps

Prakhar-Sharma's avatar
Sep 18, 2026

A repeatable implementation pattern using HashiCorp Packer, Azure DevOps and Azure Compute Gallery

Solution at a glance 
Source image  →  Packer build  →  Azure Compute Gallery  →  Validation VM  →  Evidence  →  Controlled promotion 

Introduction 

Maintaining enterprise virtual machine images manually can lead to configuration drift, inconsistent builds, slow remediation and limited release traceability. A stronger pattern is to treat each image as a versioned build artefact: create it from a controlled source, configure it deterministically, validate the published image and promote only an explicitly identified version. 

This article presents a generic implementation using HashiCorp Packer and Azure DevOps. The examples intentionally use non-production placeholders and avoid customer-specific names, subscriptions, networks, credentials, repositories, gallery names, certificate material, package sources and internal promotion topology. 

What this article demonstrates 

  • Consolidate a layered image process into a parameterised Packer build. 
  • Preserve image metadata and Azure Marketplace lineage through publication. 
  • Validate the published image version rather than only the temporary build VM. 
  • Publish validation evidence and make promotion decisions traceable. 

Solution overview 

1. Source 

2. Build 

3. Publish 

4. Validate 

5. Evidence 

6. Promote 

Marketplace image 

Packer + scripts 

Compute Gallery 

Temporary VM 

JSON + logs 

Approved version 

Reference architecture 

The pipeline separates image creation, validation, security scanning and promotion. This separation makes each release decision explicit and preserves evidence for the exact image version that downstream workloads consume. 

Azure DevOps 
Orchestration 

Packer 
Image build 

Compute Gallery 
Versioned artefact 

Validation VM 
Runtime checks 

Release gate 
Promotion 

Important  A successful Packer build proves that the build completed. It does not prove that the published gallery image behaves correctly when consumed. Validate the published version independently. 

 

Designing the image build 

Why use a single reproducible build? 

Layered pipelines can be useful, but multiple sequential image builds create intermediate artefacts and dependencies that complicate remediation. Where the complete configuration can be reproduced from source, use one ordered flow: select the source image, configure the operating system, apply the chosen hardening profile, install the runtime, configure trust, run smoke checks, clean up and capture the result. 

Parameterise the pipeline 

Keep environment-specific values outside the reusable image logic. Typical queue-time parameters include runtime version, image flavour, build region, optional security scanning and the image version to validate. 

Queue-time pipeline parameters (YAML) 
parameters: 
- name: runtimeVersion 
  type: string 
  default: '17' 
  values: ['8', '11', '17', '21'] 
- name: imageFlavor 
  type: string 
  default: standard 
  values: [standard, cis] 
- name: enableSecurityScan 
  type: boolean 
  default: false 
- name: location 
  type: string 
  default: eastus 

Define the Packer builder and provisioning order 

Use a single template with an explicit provisioning sequence. Fail the build for conditions that make the image unusable; keep advisory checks separate when the release process intentionally treats them as non-blocking

Generic Azure builder and inputs (HCL) 
packer { 
  required_plugins { 
    azure = { 
      source  = "github.com/hashicorp/azure" 
      version = "~> 2.6" 
    } 
  } 
} 
 
variable "location"        { type = string } 
variable "image_name"      { type = string } 
variable "image_version"   { type = string } 
variable "runtime_version" { type = string } 
variable "image_flavour"   { type = string } 
 
source "azure-arm" "base" { 
  use_azure_cli_auth = true 
  location            = var.location 
  os_type             = "Linux" 
  vm_size             = "Standard_D4s_v5" 
  image_publisher     = "RedHat" 
  image_offer         = "RHEL" 
  image_sku           = "8-lvm-gen2" 
  image_version       = "latest" 
  managed_image_resource_group_name = "rg-image-build" 
  managed_image_name = "${var.image_name}-${var.image_version}" 
} 
Deterministic provisioning sequence (HCL)
build { 
  sources = ["source.azure-arm.base"] 
 
  provisioner "shell" { script = "scripts/os-update.sh" } 
  provisioner "shell" { script = "scripts/configure-system.sh" } 
  provisioner "shell" { 
    environment_vars = [ 
      "RUNTIME_VERSION=${var.runtime_version}", 
      "IMAGE_FLAVOUR=${var.image_flavour}" 
    ] 
    script = "scripts/install-runtime.sh" 
  } 
  provisioner "shell" { script = "scripts/configure-trust.sh" } 
  provisioner "shell" { script = "scripts/image-smoke-test.sh" } 
  provisioner "shell" { script = "scripts/image-cleanup.sh" } 
} 

Authentication  Use Azure DevOps service connections or workload identity. Do not place client secrets in Packer templates or ordinary YAML parameters, and do not write sensitive values to logs or image artefacts. 

Marketplace lineage  When the selected source image includes purchase-plan metadata, preserve the required plan information and verify the gallery definition before promotion. 

Version images independently 

Maintain a separate incrementing version stream for each runtime and image-flavour combination. This keeps versions meaningful within the corresponding image definition. 

Version key concept 
imageVersion: '8.1.$(imageMinor)' 
# Counter key: <runtime>-<flavour> 

Validate the published image 

After publication, deploy a temporary VM from the exact gallery image version, run validation remotely, publish the results and always remove temporary resources. 

Create and validate a VM from the published version (YAML) 
- stage: ValidatePublishedImage 
  dependsOn: BuildImage 
  jobs: 
  - job: ValidationVM 
    steps: 
    - task: AzureCLI@2 
      inputs: 
        azureSubscription: $(validationServiceConnection) 
        scriptType: bash 
        scriptLocation: inlineScript 
        inlineScript: | 
          set -euo pipefail 
          az vm create \ 
            --resource-group "$(validationResourceGroup)" \ 
            --name "$(validationVmName)" \ 
            --image "$(galleryImageVersionId)" \ 
            --admin-username imageadmin \ 
            --generate-ssh-keys 
          az vm run-command invoke \ 
            --resource-group "$(validationResourceGroup)" \ 
            --name "$(validationVmName)" \ 
            --command-id RunShellScript \ 
            --scripts @scripts/image-validation.sh 
Validation coverage 
  • Operating-system baseline and expected distribution/version. 
  • Runtime version and required environment variables. 
  • Required packages, services and cloud-agent readiness. 
  • Truststore existence and basic integrity. 
  • Expected hardening state for the selected image flavour. 
Minimal in-guest validation script (Bash) 
#!/usr/bin/env bash 
set -euo pipefail 
report_dir="${REPORT_DIR:-validation-results}" 
mkdir -p "$report_dir" 
 
check() { 
  local name="$1"; shift 
  if "$@"; then 
    printf '%s\tPASS\n' "$name" | tee -a "$report_dir/result.txt" 
  else 
    printf '%s\tFAIL\n' "$name" | tee -a "$report_dir/result.txt" 
    return 1 
  fi 
} 
 
check os-baseline grep -q '^ID="rhel"' /etc/os-release 
check runtime-version java -version 
check java-home test -n "${JAVA_HOME:-}" 
check truststore test -s "${JAVA_HOME}/lib/security/cacerts" 
check cloud-agent systemctl is-active --quiet waagent 
Publish evidence and enforce the gate 
- task: PublishPipelineArtifact@1 
  displayName: Publish image validation evidence 
  condition: always() 
  inputs: 
    targetPath: validation-results 
    artifact: image-validation 
 
- bash: jq -e '.result == "PASS"' validation-results/result.json 
  displayName: Enforce validation gate 

The report should identify the image definition and version and provide an explicit result for every check. Correlate build logs, validation artefacts, scan outputs and promotion records with the pipeline run.

Promotion and release controls 

Bake once, then promote the same identified image version rather than rebuilding independently for each environment. If validation or scanning is required for a production release, represent that requirement as a pipeline dependency or approval gate. 

Promotion dependency and approval boundary (YAML) 
- stage: Promote 
  dependsOn: 
  - BuildImage 
  - ValidatePublishedImage 
  - SecurityScan 
  condition: | 
    and( 
      succeeded('BuildImage'), 
      succeeded('ValidatePublishedImage'), 
      or( 
        eq('${{ parameters.enableSecurityScan }}', false), 
        succeeded('SecurityScan') 
      ) 
    ) 
  jobs: 
  - deployment: PromoteImage 
    environment: image-production-approval 
    strategy: 
      runOnce: 
        deploy: 
          steps: 
          - bash: ./scripts/promote-image-version.sh 
Failure handling and clean-up 
- task: AzureCLI@2 
  displayName: Remove temporary validation resources 
  condition: always() 
  inputs: 
    azureSubscription: $(validationServiceConnection) 
    scriptType: bash 
    scriptLocation: inlineScript 
    inlineScript: | 
      az vm delete \ 
        --resource-group "$(validationResourceGroup)" \ 
        --name "$(validationVmName)" --yes || true 
      az disk delete \ 
        --resource-group "$(validationResourceGroup)" \ 
        --name "$(validationOsDiskName)" --yes || true 
  • Revoke temporary disk access before deleting staging resources. 
  • Use the pipeline run or image version in temporary resource names. 
  • Do not overwrite a successfully published image version. 
  • Define retention for gallery versions and capture intermediates. 
  • Rollback by directing new deployments to a previous known-good version. 

Production considerations 

Release gates 

Gate 

Expected evidence 

Build 

Packer completes; the image version reaches a successful provisioning state; expected metadata is present. 

Functional 

A VM created from the published version passes OS, runtime, package, service, agent and trust checks. 

Security 

Hardening and vulnerability outputs meet the organisation’s defined release threshold. 

Promotion 

The validated version is copied or replicated without rebuilding, and target metadata matches. 

Operational 

Temporary resources are removed, evidence is retained, ownership is clear and rollback is recorded. 

Stable validation contract 

Use a small, predictable report schema so the pipeline, release approvers and downstream automation interpret the same top-level result. 

Example validation report (JSON) 

{ 
  "schemaVersion": "1.0", 
  "image": { 
    "definition": "rhel-runtime-standard", 
    "version": "8.1.12" 
  }, 
  "result": "PASS", 
  "checks": [ 
    {"name": "os-baseline", "status": "PASS"}, 
    {"name": "runtime-version", "status": "PASS"}, 
    {"name": "truststore", "status": "PASS"}, 
    {"name": "required-services", "status": "PASS"} 
  ] 
} 
Operational checklist 
  • Verify source-image terms and permissions before each bake. 
  • Pin Packer and plug-in versions; test upgrades deliberately. 
  • Give build and promotion identities only the scopes they require. 
  • Confirm validation targets the same subscription, gallery, definition and version produced by the build. 
  • Define promotion, retention, retry, notification and ownership policies before production handover. 
  • Keep a build summary, validation report, scan output, promotion record and cleanup record for each released version. 
Consumer rollout 

The image pipeline ends at a versioned, validated gallery artefact. Consumers such as Virtual Machine Scale Sets should reference an explicit version during controlled rollout, validate health after deployment and move new deployments to a previous known-good version when rollback is required. 

Conclusion 

Image baking becomes easier to operate when it is treated as a software-delivery problem: source-controlled configuration, deterministic builds, explicit metadata, versioned outputs, independent validation, published evidence and controlled promotion. Packer supplies the repeatable image build, Azure DevOps supplies orchestration and governance, and Azure Compute Gallery supplies the distribution boundary. 

The most important addition is validating the image after publication so release decisions are based on the same artefact that downstream workloads will consume. 

Adapt before use  The examples are intentionally generic. Replace placeholder names and policy thresholds for the target environment; keep secrets in protected variables or an approved secret store. 

References 

Updated Sep 18, 2026
Version 1.0
No CommentsBe the first to comment