Safeguard
DevSecOps

Signing container images and generating SBOMs in Azure pi...

A practical walkthrough for Azure container image signing with Notation and ACR content trust, plus generating SBOMs inside Azure DevOps pipelines.

Priya Mehta
DevSecOps Engineer
7 min read

Azure container image signing is the last mile of a secure software supply chain: without it, anyone with push access to your registry can slip an unsigned or tampered image into production, and downstream consumers have no cryptographic way to verify what they're actually running. If your team ships containers through Azure Pipelines into Azure Container Registry (ACR), you need two things working together — a signature attached to every image that proves who built it and that it hasn't changed since, and a software bill of materials (SBOM) that documents exactly what's inside. This guide walks through setting up Azure container image signing with Notation and ACR content trust, generating SBOMs directly in Azure DevOps pipelines with Syft, and verifying both at deploy time so unsigned or undocumented images never reach a cluster. By the end, you'll have a repeatable, auditable pipeline stage that satisfies most SOC 2 and supply-chain compliance requirements out of the box.

Step 1: Set Up Prerequisites for Azure Container Image Signing

Before touching pipeline YAML, you need three Azure resources in place: an Azure Container Registry, an Azure Key Vault to hold your signing certificate, and permissions wired between them.

Create (or reuse) the registry and vault:

az acr create --resource-group sg-rg --name sgregistry --sku Premium
az keyvault create --resource-group sg-rg --name sg-signing-kv --location eastus

Note the Premium SKU — content trust and some Notation integrations require it. Grant your pipeline's service principal Key Vault Certificates Officer and AcrPush roles so it can both sign and push:

az role assignment create --assignee <pipeline-sp-object-id> \
  --role "Key Vault Certificates Officer" \
  --scope /subscriptions/<sub-id>/resourceGroups/sg-rg/providers/Microsoft.KeyVault/vaults/sg-signing-kv

az role assignment create --assignee <pipeline-sp-object-id> \
  --role "AcrPush" \
  --scope /subscriptions/<sub-id>/resourceGroups/sg-rg/providers/Microsoft.ContainerRegistry/registries/sgregistry

Step 2: Generate a Signing Certificate in Azure Key Vault

Notation signs using an X.509 certificate stored in Key Vault rather than a local key file, so nothing sensitive ever touches the build agent's disk. Create a self-signed certificate for testing, or import a CA-issued one for production:

az keyvault certificate create \
  --vault-name sg-signing-kv \
  --name sg-image-signing-cert \
  --policy "$(az keyvault certificate get-default-policy)"

For production pipelines, replace this with a certificate chained to your organization's internal CA or a public CA that your downstream consumers already trust — self-signed certs are fine for proving the mechanics work, but they won't satisfy an external audit.

Step 3: Install Notation and the Azure Key Vault Plugin in the Pipeline

Notation is the CNCF signing tool that ACR content trust is built around. Add an installation step to your Azure Pipelines YAML before the build/push stage:

- script: |
    curl -Lo notation.tar.gz https://github.com/notaryproject/notation/releases/download/v1.2.0/notation_1.2.0_linux_amd64.tar.gz
    tar xzf notation.tar.gz
    sudo mv notation /usr/local/bin/
    notation plugin install azure-kv --url https://github.com/Azure/notation-azure-kv/releases/download/v1.2.1/notation-azure-kv_1.2.1_linux_amd64.tar.gz
  displayName: 'Install Notation CLI and Azure Key Vault plugin'

Verify the plugin registered correctly with notation plugin ls — you should see azure-kv listed before moving on.

Step 4: Perform Azure Container Image Signing in Your Pipeline

This is the step that actually produces the signature. After your docker build and docker push tasks, add a signing task that references the digest of the image you just pushed:

- script: |
    IMAGE_DIGEST=$(az acr repository show \
      --name sgregistry --image myapp:$(Build.BuildId) --query digest -o tsv)

    notation sign \
      sgregistry.azurecr.io/myapp@$IMAGE_DIGEST \
      --key-vault "https://sg-signing-kv.vault.azure.net" \
      --key-name sg-image-signing-cert \
      --id azure-kv
  displayName: 'Sign container image with Notation'

Always sign by digest, never by tag — tags are mutable and can be repointed after signing, which would silently invalidate the trust guarantee you're building. This single step is the core of Azure container image signing: every image that leaves this pipeline now carries a verifiable, tamper-evident signature stored as an OCI artifact alongside it in the registry.

Step 5: Generate an SBOM in the Same Pipeline

SBOM Azure DevOps integration doesn't require a separate system — you can generate the bill of materials right where you build the image, using Syft or Microsoft's own SBOM tool. Syft is a good default because it produces SPDX-compliant output and understands most language ecosystems out of the box:

- script: |
    curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
    syft sgregistry.azurecr.io/myapp:$(Build.BuildId) -o spdx-json=sbom.spdx.json
  displayName: 'Generate SBOM with Syft'

- publish: sbom.spdx.json
  artifact: sbom

Attach the SBOM to the image as an OCI referrer so it travels with the artifact instead of living only in your build logs:

notation sign \
  sgregistry.azurecr.io/myapp@$IMAGE_DIGEST \
  --key-vault "https://sg-signing-kv.vault.azure.net" \
  --key-name sg-image-signing-cert \
  --id azure-kv \
  --plugin-config file=sbom.spdx.json

Now the digest carries both a signature and a discoverable SBOM, so anyone pulling the image can inspect its exact contents without asking your team for a spreadsheet.

Step 6: Enforce ACR Content Trust at Pull Time

Signing is only half the story — you also need something that refuses to pull unsigned images. Enable ACR content trust on the registry and configure a policy so deployments fail closed rather than open:

az acr config content-trust update \
  --registry sgregistry \
  --status enabled

If you're deploying to AKS, pair this with a Notation-aware admission controller (such as Ratify) so the cluster itself validates signatures before scheduling a pod, rather than relying on the pipeline being the only checkpoint:

- script: |
    notation verify sgregistry.azurecr.io/myapp@$IMAGE_DIGEST \
      --trust-policy ./trustpolicy.json
  displayName: 'Verify signature before deploy'

Step 7: Wire Verification into the Release Stage

Add a gate in your release pipeline that runs notation verify against the target digest before any deploy task executes. If verification fails, the stage should fail hard — no manual override, no "deploy anyway" button. This is the control that actually enforces everything you built in Steps 1–6; without it, signing is just documentation.

Troubleshooting and Verification

A few issues come up repeatedly when teams first wire this together:

  • notation sign fails with a Key Vault authorization error. Double-check the pipeline's service principal has Key Vault Certificates Officer and Key Vault Crypto User — signing requires both certificate read and crypto sign permissions, and it's easy to grant only one.
  • Signature succeeds but notation verify can't find it downstream. Confirm you signed by digest and that the consuming environment resolves the same digest, not a tag that may have moved. Run az acr manifest list-referrers --name myapp --registry sgregistry to confirm the signature artifact is actually attached.
  • ACR content trust rejects pulls unexpectedly after enabling the policy. Any image pushed before you enabled content trust and signing has no signature and will be blocked. Re-sign your existing production tags or scope the policy to new repositories first.
  • SBOM generation times out on large images. Point Syft at the image tarball instead of pulling live from the registry (syft docker-archive:myapp.tar) to cut scan time on multi-gigabyte images with many layers.
  • Verify your setup end-to-end by deliberately pushing an unsigned image to a content-trust-enabled repository and confirming the pull is refused — a passing pipeline that has never actually seen a rejection hasn't proven anything.

How Safeguard Helps

Setting up Notation, Azure Key Vault, and ACR content trust correctly is straightforward once, but keeping it correct across every repo, every pipeline, and every team that onboards after you is where these controls quietly rot — a new service gets added without the signing step, a key rotation breaks verification silently, or an SBOM gets generated but never actually attached to the artifact it describes.

Safeguard continuously validates that every image flowing through your Azure DevOps pipelines is signed, that the signature chains back to a certificate you actually trust, and that a corresponding SBOM exists and matches the running artifact — not just at build time, but for everything already deployed. Instead of trusting that each team configured Azure container image signing correctly, Safeguard gives you a single view across registries showing which images are signed, which are missing SBOMs, and which have drifted out of policy, so you can catch gaps before an auditor or an incident does.

If you're rolling out Azure container image signing and SBOM generation across more than a handful of pipelines, talk to Safeguard about automating the enforcement and visibility layer on top of what you just built.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.