Managing Azure Container Registry with Terraform

Learn how to set up and manage Azure Container Registry using Terraform, including replication, security, and CI/CD integration

Managing Azure Container Registry with Terraform

Azure Container Registry (ACR) is a managed Docker registry service. This guide demonstrates how to set up and manage ACR using Terraform.

Video Tutorial

Learn more about managing Azure Container Registry with Terraform in this comprehensive video tutorial:

Prerequisites

  • Azure CLI configured with appropriate permissions
  • Terraform installed (version 1.0.0 or later)
  • Resource group created
  • Understanding of container concepts

Project Structure

terraform-azure-acr/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│   └── acr/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── policies/
    └── security.json

Container Registry Configuration

Create modules/acr/main.tf:

# Container Registry
resource "azurerm_container_registry" "main" {
  name                = "${var.project_name}acr"
  resource_group_name = var.resource_group_name
  location            = var.location
  sku                 = "Premium"
  admin_enabled       = false

  georeplications {
    location                = var.secondary_location
    zone_redundancy_enabled = true
    tags                    = var.tags
  }

  network_rule_set {
    default_action = "Deny"
    ip_rule {
      action   = "Allow"
      ip_range = var.allowed_ip_range
    }
    virtual_network {
      action    = "Allow"
      subnet_id = azurerm_subnet.acr.id
    }
  }

  retention_policy {
    days    = 7
    enabled = true
  }

  trust_policy {
    enabled = true
  }

  encryption {
    enabled            = true
    key_vault_key_id   = azurerm_key_vault_key.acr.id
    identity_client_id = azurerm_user_assigned_identity.acr.client_id
  }

  identity {
    type = "UserAssigned"
    identity_ids = [azurerm_user_assigned_identity.acr.id]
  }

  zone_redundancy_enabled = true
  public_network_access_enabled = false

  quarantine_policy_enabled = true
  
  tags = var.tags
}

# Replications
resource "azurerm_container_registry_replication" "main" {
  for_each = var.replication_locations

  location                 = each.key
  container_registry_id    = azurerm_container_registry.main.id
  zone_redundancy_enabled = true
  
  tags = var.tags
}

# Webhook
resource "azurerm_container_registry_webhook" "main" {
  name                = "${var.project_name}-webhook"
  resource_group_name = var.resource_group_name
  registry_name       = azurerm_container_registry.main.name
  location            = var.location

  service_uri = var.webhook_uri
  status      = "enabled"
  scope       = "myapp:*"
  actions     = ["push"]

  custom_headers = {
    "Content-Type" = "application/json"
  }
}

# Task
resource "azurerm_container_registry_task" "main" {
  name                  = "${var.project_name}-task"
  container_registry_id = azurerm_container_registry.main.id
  platform {
    os = "Linux"
  }

  docker_step {
    dockerfile_path      = "Dockerfile"
    context_path        = "https://github.com/your-org/your-repo#main"
    context_access_token = var.github_token
    image_names         = ["myapp:{{.Run.ID}}", "myapp:latest"]
  }

  timer_trigger {
    name     = "daily"
    schedule = "0 0 * * *"
  }

  source_trigger {
    name           = "source-trigger"
    events         = ["commit"]
    repository_url = "https://github.com/your-org/your-repo"
    source_type    = "Github"
    branch         = "main"
  }

  identity {
    type = "UserAssigned"
    identity_ids = [azurerm_user_assigned_identity.acr.id]
  }

  agent_pool_name = "S1"
}

# Scope Map
resource "azurerm_container_registry_scope_map" "main" {
  name                    = "${var.project_name}-scope-map"
  container_registry_name = azurerm_container_registry.main.name
  resource_group_name     = var.resource_group_name
  actions = [
    "repositories/myapp/content/read",
    "repositories/myapp/content/write"
  ]
}

# Token
resource "azurerm_container_registry_token" "main" {
  name                    = "${var.project_name}-token"
  container_registry_name = azurerm_container_registry.main.name
  resource_group_name     = var.resource_group_name
  scope_map_id           = azurerm_container_registry_scope_map.main.id
  
  status = "enabled"
}

Network Configuration

  1. Virtual Network Integration
resource "azurerm_virtual_network" "main" {
  name                = "${var.project_name}-vnet"
  location            = var.location
  resource_group_name = var.resource_group_name
  address_space       = ["10.0.0.0/16"]

  subnet {
    name           = "acr-subnet"
    address_prefix = "10.0.1.0/24"
    service_endpoints = ["Microsoft.ContainerRegistry"]
  }

  tags = var.tags
}

resource "azurerm_private_endpoint" "acr" {
  name                = "${var.project_name}-pe"
  location            = var.location
  resource_group_name = var.resource_group_name
  subnet_id           = azurerm_virtual_network.main.subnet.*.id[0]

  private_service_connection {
    name                           = "${var.project_name}-psc"
    private_connection_resource_id = azurerm_container_registry.main.id
    is_manual_connection          = false
    subresource_names            = ["registry"]
  }

  private_dns_zone_group {
    name                 = "default"
    private_dns_zone_ids = [azurerm_private_dns_zone.acr.id]
  }
}

resource "azurerm_private_dns_zone" "acr" {
  name                = "privatelink.azurecr.io"
  resource_group_name = var.resource_group_name
}

resource "azurerm_private_dns_zone_virtual_network_link" "acr" {
  name                  = "${var.project_name}-vnet-link"
  resource_group_name   = var.resource_group_name
  private_dns_zone_name = azurerm_private_dns_zone.acr.name
  virtual_network_id    = azurerm_virtual_network.main.id
}

Security Configuration

  1. Role Assignments
resource "azurerm_role_assignment" "acr_pull" {
  scope                = azurerm_container_registry.main.id
  role_definition_name = "AcrPull"
  principal_id         = var.aks_principal_id
}

resource "azurerm_role_assignment" "acr_push" {
  scope                = azurerm_container_registry.main.id
  role_definition_name = "AcrPush"
  principal_id         = var.cicd_principal_id
}
  1. Managed Identity
resource "azurerm_user_assigned_identity" "acr" {
  name                = "${var.project_name}-identity"
  resource_group_name = var.resource_group_name
  location            = var.location
}

resource "azurerm_key_vault_access_policy" "acr" {
  key_vault_id = var.key_vault_id
  tenant_id    = data.azurerm_client_config.current.tenant_id
  object_id    = azurerm_user_assigned_identity.acr.principal_id

  key_permissions = [
    "Get",
    "UnwrapKey",
    "WrapKey"
  ]
}

resource "azurerm_key_vault_key" "acr" {
  name         = "${var.project_name}-key"
  key_vault_id = var.key_vault_id
  key_type     = "RSA"
  key_size     = 2048

  key_opts = [
    "decrypt",
    "encrypt",
    "sign",
    "unwrapKey",
    "verify",
    "wrapKey",
  ]
}

Monitoring Configuration

  1. Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "acr" {
  name                       = "${var.project_name}-diag"
  target_resource_id        = azurerm_container_registry.main.id
  log_analytics_workspace_id = var.log_analytics_workspace_id

  log {
    category = "ContainerRegistryRepositoryEvents"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }

  metric {
    category = "AllMetrics"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }
}

resource "azurerm_monitor_metric_alert" "acr" {
  name                = "${var.project_name}-storage-alert"
  resource_group_name = var.resource_group_name
  scopes              = [azurerm_container_registry.main.id]
  description         = "Alert when storage usage is high"

  criteria {
    metric_namespace = "Microsoft.ContainerRegistry/registries"
    metric_name      = "StorageUsed"
    aggregation      = "Average"
    operator         = "GreaterThan"
    threshold        = 80
  }

  action {
    action_group_id = var.action_group_id
  }
}

Advanced Features

  1. Content Trust
resource "azurerm_container_registry" "trusted" {
  # ... other configuration ...

  trust_policy {
    enabled = true
  }

  identity {
    type = "UserAssigned"
    identity_ids = [azurerm_user_assigned_identity.acr.id]
  }
}
  1. Agent Pool
resource "azurerm_container_registry_agent_pool" "main" {
  name                    = "${var.project_name}-pool"
  resource_group_name     = var.resource_group_name
  location                = var.location
  container_registry_name = azurerm_container_registry.main.name
  
  tier = "S1"
  
  virtual_network_subnet_id = azurerm_subnet.acr.id
}

Best Practices

  1. Performance

    • Use geo-replication
    • Enable zone redundancy
    • Configure caching
    • Use Premium SKU
  2. Security

    • Disable admin access
    • Use RBAC
    • Enable encryption
    • Implement network isolation
  3. CI/CD Integration

    • Use webhooks
    • Configure tasks
    • Implement scanning
    • Set up automated builds
  4. Cost Optimization

    • Clean up old images
    • Monitor storage usage
    • Use appropriate SKU
    • Implement retention policies

Cost Optimization

  1. Resource Optimization
    • Use appropriate SKU
    • Implement retention policies

Conclusion

You’ve learned how to set up and manage Azure Container Registry using Terraform. This setup provides:

  • Secure container storage
  • Global replication
  • CI/CD integration
  • Monitoring and alerts

Remember to:

  • Monitor storage usage
  • Review security settings
  • Clean up old images
  • Update access controls