Skip to main content

Image Management from Docker Hub to Azure Container Registry - Part 1

· 6 min read
Hasan Gural

Keeping your container images up to date is a critical part of managing modern deployments. In this article, I'll explain how you can automate the process of pulling a public Docker image from Docker Hub and pushing it to your Azure Container Registry (ACR) using a PowerShell script. This approach is especially useful for overcoming Docker Hub’s rate limits by storing the image in your ACR.

Automated Image Management: From Docker Hub to Azure Container Registry

This article is part of a two-part series where I demonstrate how to build an automated process that leverages a Service Principal with Federated Credentials and strict Role-Based Access Control (RBAC) assigment. With RBAC in place, only the necessary permissions are granted, ensuring that the Service Principal has the least privileges required for this operation. In this example, we'll use the Cloudflare image as our reference, but you can adapt the process for any public image.

Whether you choose to run this orchestration on GitHub Actions or Azure DevOps pipelines, the core idea remains the same: automate image management securely and efficiently while maintaining tight security controls.

Let's dive in!

🚀 Overview of the Process

The automation process involves several key steps:

  • Azure Authentication & ACR Connection: Log in to Azure using a Service Principal (configured with Federated Credentials) and connect to your Azure Container Registry.Make sure to assign the necessary permissions to the Service Principal for ACR operations (e.g., AcrPush, AcrDelete)
  • Image Fetching: Pull the latest public image (Cloudflare in this example) from Docker Hub. The main idea is to check if a new version of the image is available. Hence, we will be free for docker hub rate limits.
  • Repository Verification: Check if the image repository already exists in ACR.
  • Digest Comparison & Legacy Tagging: Compare the image digest from Docker Hub with that in ACR. If a new version is detected, tag the current ACR image as "legacy" to preserve the previous version.
  • Image Import: Import the new 'latest' image from Docker Hub into your ACR.
  • Orchestration: Schedule the process using GitHub Actions or Azure DevOps pipelines so that updates occur automatically and consistently.

🔧 The PowerShell Script that Automates Image Updates

Below is the complete PowerShell script created as (Sync-AzContainerRegistry.ps1) that automates the image update process. This script is intended to be integrated into your CI/CD pipeline (via GitHub Actions or Azure DevOps), allowing you to run it on a scheduled basis or triggered by events.

Script: Sync-AzContainerRegistry.ps1

# Parameters

param (

[string]$dockerHubImageName = "cloudflare/cloudflared",
[string]$acrName = "testdemoacrtask",
[string]$resourceGroupName = "demoacr",
[string]$acrImageName = "cloudflared"
)

if (-not (Get-AzContext)) {

Write-Output "Logging in to Azure..."
Connect-AzAccount

}

Write-Output "Connecting to Azure Container Registry: $acrName"
Connect-AzContainerRegistry -Name $acrName

$dockerHubApiUrl = "https://hub.docker.com/v2/repositories/$dockerHubImageName/tags/latest"
$response = Invoke-RestMethod -Uri $dockerHubApiUrl -Method Get
Write-Output "Fetching 'latest' tag details from Docker Hub for image: $dockerHubImageName"


if (!$response || -not $response.images) {

Write-Output "Failed to fetch tag details from Docker Hub. Please check the repository name or internet connection."
exit

}

$dockerHubLatestDigest = $response.images[1].digest
Write-Output "Docker Hub 'latest' digest: $dockerHubLatestDigest"

Write-Output "Checking if repository '$acrImageName' exists in ACR..."
$acrManifests = Get-AzContainerRegistryManifest -RepositoryName $acrImageName -RegistryName $acrName -ErrorAction SilentlyContinue

if (-not $acrManifests) {

Write-Output "Repository '$acrImageName' does not exist in ACR. Importing the 'latest' image for the first time..."

$sourceImage = "$($dockerHubImageName):latest"
$targetImage = "$($acrImageName):latest"

Write-Output "Importing 'latest' image from Docker Hub to ACR for the first time..."
Import-AzContainerRegistryImage -ResourceGroupName $resourceGroupName `
-RegistryName $acrName `
-SourceImage "$sourceImage" `
-TargetTag "$targetImage" `
-SourceRegistryUri "docker.io" `
-Mode 'Force'

Write-Output "Successfully imported 'latest' image to ACR as '$targetImage'."
exit

}

$acrLatestDigest = $acrManifests.ManifestsAttributes | Where-Object { $_.Tags -contains "latest" } | Select-Object -ExpandProperty Digest

if ($acrLatestDigest) {

Write-Output "ACR 'latest' digest: $acrLatestDigest"

}
else {

Write-Output "No 'latest' image found in ACR for $acrImageName."

}

if ($dockerHubLatestDigest -eq $acrLatestDigest) {

Write-Output "The ACR 'latest' image is already up to date. No action needed."

}
else {

Write-Output "New version detected on Docker Hub. Updating ACR with the latest image from Docker Hub."

if ($acrLatestDigest) {

Write-Output "Creating a new 'legacy' tag for the current 'latest' image in ACR."

$acrResourceId = (Get-AzContainerRegistry -RegistryName $acrName -ResourceGroupName $resourceGroupName).Id

Import-AzContainerRegistryImage -ResourceGroupName $resourceGroupName `
-RegistryName $acrName `
-SourceImage "$acrImageName@$acrLatestDigest" `
-TargetTag "$($acrImageName):legacy" `
-SourceResourceId $acrResourceId `
-Mode 'Force'

Write-Output "Successfully tagged the current 'latest' image as 'legacy' in ACR."
}

$sourceImage = "$($dockerHubImageName):latest"
$targetImage = "$($acrImageName):latest"

Write-Output "Importing new 'latest' image from Docker Hub to ACR..."
Import-AzContainerRegistryImage -ResourceGroupName $resourceGroupName `
-RegistryName $acrName `
-SourceImage "$sourceImage" `
-TargetTag "$targetImage" `
-SourceRegistryUri "docker.io" `
-Mode 'Force'

Write-Output "Successfully imported new 'latest' image to ACR as '$targetImage'."

}

Write-Output "Script completed successfully."

📺 Demo Execution

To illustrate how the script works in practice, here’s a walkthrough of a demo execution:

Script Execution Output:

When you run the script (either manually in a terminal or triggered by your CI/CD pipeline), you’ll see output similar to the following:


Logging in to Azure...
Connecting to Azure Container Registry: testdemoacrtask
Fetching 'latest' tag details from Docker Hub for image: cloudflare/cloudflared
Docker Hub 'latest' digest: sha256:abcd1234efgh5678...
Checking if repository 'cloudflared' exists in ACR...
ACR 'latest' digest: sha256:1234abcd5678efgh...
New version detected on Docker Hub. Updating ACR with the latest image from Docker Hub.
Creating a new 'legacy' tag for the current 'latest' image in ACR.
Successfully tagged the current 'latest' image as 'legacy' in ACR.
Importing new 'latest' image from Docker Hub to ACR...
Successfully imported new 'latest' image to ACR as 'cloudflared:latest'.
Script completed successfully.

These log messages indicate each step of the process—from authentication, fetching the image details, comparing digests, tagging, and finally importing the new image.

If you're executing the script for the first time locally, you can expect output similar to the following, indicating that the image repository doesn't yet exist in ACR and that the image is being imported for the first time:

Demo Execution: First-Time Image Import

For subsequent executions, script will compare the digests and update the ACR with the latest image from Docker Hub:

Demo Execution: Subsequent Image Updates

This demo not only confirms that your script is running as expected in a local execution environment but also serves as a reference for integrating it into your CI/CD pipeline. We’ll explore this integration in the next part of this series.

Will continue in Part 2 of this series where we will integrate this script into a GitHub Actions workflow to automate the image update process. Stay tuned!