Skip to main content
Jorge Bernhardt Jorge Bernhardt
  1. Posts/

Terraform – Simplified Azure Container Registry (ACR) Deployment

·1407 words·7 mins· 100 views · 5 likes ·
Azure CLI Azure Cloud Shell ACR Microsoft

Hi folks! Today, in this blog post, we’re going to explore how to implement Azure Container Registry (ACR) using Terraform. As we’ve discussed on several occasions in this blog, ACR is an essential Azure service for storing and managing Docker container images. It provides a private and highly available environment for our container-based applications.

In this guide, I will detail the necessary steps to configure and deploy ACR simply and effectively with Terraform, to maximize the benefits of automation and reproducibility that infrastructure as code offers. I’ve aimed to cover everything necessary, from basic registry configuration to integrating security policies and identity management, to ensure that our registry is not only functional but also secure and aligned with best practices.

Prerequisites>

Prerequisites #

  • You need Terraform CLI on your local machine, if you’re new to using Terraform to deploy Microsoft Azure resources, then I recommend you check out this link.
  • A text editor or IDE of your choice (Visual Studio Code with terraform extension is my recommendation)
Declare Azure Provider in Terraform>

Declare Azure Provider in Terraform #

The provider.tf file in Terraform is used to specify and configure the providers used in your Terraform configuration. A provider is a service or platform where the resources will be managed. This could be a cloud provider like Microsoft Azure, AWS, Google Cloud, etc.

This file is important because it tells Terraform which provider’s API to use when creating, updating, and deleting resources. Without it, Terraform wouldn’t know where to manage your resources.

provider "azurerm" {
  features {}
}
Deploy Azure Resources Using Terraform>

Deploy Azure Resources Using Terraform #

In the case of Azure Container Registry deployment, the main.tf file contains the following key components:

  • azurerm_container_registry: This block sets up the Azure Container Registry within the specified resource group, location, and with a defined SKU. It manages multiple registry configurations using the for_each construct, drawing configurations from var.acr_config.
  • dynamic “network_rule_set: Conditionally applied only for Premium SKUs when public network access is disabled. It restricts all traffic except those explicitly allowed through specified IP rules.
  • dynamic “identity” : Manages the identity used by the ACR, supporting both System Assigned and User Assigned identities. It includes dynamic application based on the identity type specified.
  • retention_policy: The purpose of the retention policy in Azure Container Registry (ACR) is to manage the duration that images, especially untagged images, are stored before being automatically deleted. When you set a retention policy, you explicitly define the number of days that untagged container images are retained. After this specified period, these untagged images are removed from the registry.
  • dynamic “georeplications”: Configures georeplication settings for Premium SKUs, allowing ACRs to replicate across multiple regions for higher availability and reduced latency.
resource "azurerm_container_registry" "acr" {
  for_each = var.acr_config

  name                          = each.value.name
  location                      = each.value.location
  resource_group_name           = each.value.resource_group_name
  sku                           = each.value.sku
  admin_enabled                 = each.value.admin_enabled
  public_network_access_enabled = each.value.public_network_access_enabled

  dynamic "network_rule_set" {
    for_each = each.value.sku == "Premium" && !each.value.public_network_access_enabled ? [1] : []
    content {
      default_action = "Deny"

      dynamic "ip_rule" {
        for_each = each.value.ip_rules
        content {
          action   = "Allow"
          ip_range = ip_rule.value
        }
      }
    }

  }

  dynamic "identity" {
    for_each = each.value.identity_type != "" ? [1] : []
    content {
      type = each.value.identity_type
      identity_ids = (each.value.identity_type == "UserAssigned" || each.value.identity_type == "SystemAssigned, UserAssigned") && each.value.user_assigned_identity_id != null ? [each.value.user_assigned_identity_id] : []
    }
  }

  quarantine_policy_enabled = each.value.quarantine_policy_enabled

  retention_policy {
    days    = each.value.retention_days
    enabled = each.value.retention_days > 0
  }

  dynamic "georeplications" {
    for_each = each.value.sku == "Premium" ? each.value.georeplications : []
    content {
      location                = georeplications.value.location
      zone_redundancy_enabled = georeplications.value.zone_redundancy_enabled
      tags                    = georeplications.value.tags
    }
  }
}
Declaration of input variables>

Declaration of input variables #

The variables.tf file in Terraform defines the variables I will use in the main.tf file. These variables allow for more flexibility and reusability in the code.

In this example, the variables are defined in the variables.tf include:

  • acr_config: This variable is a map of objects, each representing the configuration for an Azure Container Registry. It encapsulates several properties, from basic setup like name and location to more advanced features like SKU details, network rules, and identity settings.

  • tags: This block declares a variable named tags, which is a map of strings. It is used to assign tags to the Azure resources being created. For example, you can use a key-value pair such as Terraform = true to indicate that the resource was deployed with Terraform.

// Variables for Azcure Container Registries Settings
variable "acr_config" {
  description = "Configuración de los Azure Container Registries"
  type = map(object({
    name                          = string
    location                      = string
    sku                           = string
    resource_group_name           = string
    admin_enabled                 = bool
    public_network_access_enabled = bool
    ip_rules                      = list(string)
    identity_type                 = string           
    user_assigned_identity_id     = optional(string) 
    quarantine_policy_enabled     = bool
    retention_days                = number
    georeplications = list(object({
      location                = string
      zone_redundancy_enabled = bool
      tags                    = map(string)
    }))
  }))

  // Validation for identity_type
  validation {
    condition = alltrue([
      for acr in var.acr_config : contains(["SystemAssigned", "UserAssigned"], acr.identity_type)
    ])
    error_message = "Each ACR configuration must have 'identity_type' set to either 'SystemAssigned' or 'UserAssigned'."
  }
}

// Variable for tags
variable "tags" {
  description = "Common tags for all resources"
  type        = map(string)
  default = {
    Environment = "www.jorgebernhardt.com"
    Terraform   = "true"
  }
}

Important:To ensure that ACR is accessible only from specific IP addresses without being publicly accessible on the internet, you must set the public_network_access_enabled variable to true and then specify the allowed IP addresses in the ip_rules list.

Here is the rationale and method for this setup:

  • Public Network Access Enabled: Setting public_network_access_enabled to true does not imply that your ACR is open to the internet. Instead, it means that public network access is conditionally allowed based on your specified network rules.
  • Specifying IP Rules: By defining ip_rules, you limit the access to your ACR strictly to the IPs listed. This setup is crucial because, even though the public_network_access_enabled is set to true, the ACR will not accept connections from any IP address not explicitly listed in the ip_rules.
Declaration of output values>

Declaration of output values #

The output.tf file in Terraform extracts and displays information about the resources created or managed by your Terraform configuration. These outputs are defined using the output keyword and can be used to return information that can be useful for the user, for other Terraform configurations, or for programmatically using the information in scripts or other tools.

In this example, the output.tf file is configured to return the IDs of the Azure Container Registries that were created. This information is usefull for tracking and referencing the registries within automated scripts or subsequent Terraform deployments.

Once Terraform has finished applying your configuration, it will display the defined outputs.

# Output the Container Registry IDs
output "container_registry_ids" {
  description = "The IDs of the Azure Container Registries."
  value       = { for acr in azurerm_container_registry.acr : acr.name => acr.id }
  sensitive   = false
}

This output will display the IDs of all deployed Azure Container Registries, mapping each registry’s name to its unique Azure ID. Marking this output as sensitive = false indicates that the information can be displayed openly in the CLI output and does not expose sensitive data.

Executing the Terraform Deployment>

Executing the Terraform Deployment #

Now that you’ve declared the resources correctly, it’s time to take the following steps to deploy them in your Azure environment.

  • Initialization: To begin, execute the terraform init command. This will initialize your working directory that holds the .tf files and download the provider specified in the provider.tf file, and configure the Terraform backend. If you want to know how, check this link.

  • Planning: Next, execute the terraform plan. This command creates an execution plan and shows Terraform’s actions to achieve the desired state defined in your .tf files. This gives you a chance to review the changes before applying them.

  • Apply: When you’re satisfied with the plan, execute the terraform apply command. This will implement the required modifications to attain the intended infrastructure state. Before making any changes, you will be asked to confirm your decision.

  • Inspection: After applying the changes, you can use terraform show command to see the current state of your infrastructure.

  • Destroy (optional): when a project is no longer needed or when resources have become outdated. You can use the terraform destroy command. This will remove all the resources that Terraform has created.

References and useful links #

Thank you for taking the time to read my post. I sincerely hope that you find it helpful.