Skip to content

Container Registry

Use the container_registry variable to create an Azure Container Registry (ACR) and, optionally, one or more artifact cache rules that mirror images from an upstream registry (Docker Hub, GitHub Container Registry, Quay, etc.) into your ACR.

This is how we make an image published to ghcr.io available for private, in-Azure pulls: the ACR caches the image on first pull and serves it from the registry afterward.

Copy/Paste Quick Reference

The example below is the live configuration for the coder environment. It creates the coderdevcontainerreg registry and a cache rule that mirrors ghcr.io/sapphire-health/ansible-epic/azure into the ansible-epic/azure repository of the ACR.

container_registry = {
  coder = {
    name = "coderdevcontainerreg",
    resource_group = "CoderDevContainer",
    cache_rules = {
      syncghcr = {
        name = "syncghcr"
        container_registry = "coder"
        target_repo = "ansible-epic/azure"
        source_repo = "ghcr.io/sapphire-health/ansible-epic/azure"
      }
    }
  }
}

The sku argument is omitted, so the registry is created with the default Basic SKU. Artifact cache works on all SKUs, so Basic is sufficient for simple pull-through caching.

Because the registry has neither the admin user nor anonymous pull enabled, any identity that needs to pull an image must be granted the built-in AcrPull role. The coder environment grants it to the existing ansible_container user-assigned identity via the Role Assignments module:

role_assignment = {
  ansible_container_acrpull = {
    scope                = "/subscriptions/<subscription_id>/resourceGroups/CoderDevContainer/providers/Microsoft.ContainerRegistry/registries/coderdevcontainerreg"
    role_definition_name = "AcrPull"
    principal = {
      type = "user_assigned_identity"
      key  = "ansible_container"
    }
    description = "Allow the ansible-epic container identity to pull images (including the GHCR-cached image) from the Coder dev container registry"
  }
}

Once applied, pull the cached image using the ACR login server:

docker pull coderdevcontainerreg.azurecr.io/ansible-epic/azure:latest

Overview

The container_registry variable is a map. Each key is a registry, and each registry may contain a cache_rules map of artifact cache rules.

Registry arguments

name: The globally-unique name of the registry (alphanumeric, no hyphens). If omitted, a name is generated from the container_registry naming prefix/suffix and the map key.

resource_group: The key of the resource group (from resource_groups.tfvars) the registry lives in.

sku: Basic, Standard, or Premium. Defaults to Basic, which the coder environment relies on. Artifact cache is available on all tiers, so only move to Premium when you need a Premium-only feature such as private endpoints or geo-replication.

existing: Set to true to reference a registry that already exists rather than creating one. Defaults to false.

location: Overrides the environment's default location for this registry. Optional.

admin_enabled: Enables the registry admin user (a shared username/password). Defaults to false. Prefer RBAC (AcrPull/AcrPush) over the admin user.

tags: A map of tags merged over the environment's default_tags. Optional.

Cache rule arguments

Each entry in cache_rules creates an azurerm_container_registry_cache_rule.

container_registry: The key of the registry (in the same container_registry map) that the rule is created on. In the example, "coder" attaches the rule to the coderdevcontainerreg registry.

source_repo: The upstream repository path, with no tag. For our image this is ghcr.io/sapphire-health/ansible-epic/azure. Tags are cached individually on pull — never include a :tag here.

target_repo: The repository path inside your ACR where cached artifacts are stored. Pulling <login-server>/<target_repo>:<tag> triggers the cache. In the example, ansible-epic/azure.

name: The name of the cache rule (5–50 chars, ^[a-zA-Z0-9-]*$ — no underscores). Optional; derived from naming conventions if omitted.

How artifact cache behaves

Artifact cache is pull-through, not a scheduled mirror. Keep the following in mind:

  • Nothing is copied until you pull. The target_repo repository does not appear under the registry's repositories until the first pull of <login-server>/ansible-epic/azure:latest. ACR does not watch ghcr.io for new pushes.
  • Re-pulling a mutable tag refreshes it. Each time a client pulls :latest, ACR revalidates against the upstream digest and pulls the new image if it changed, otherwise it serves the cached copy. There is no push-driven auto-sync — a client must actually issue the pull for the cache to update.
  • Public upstream needs no credentials. ghcr.io/sapphire-health/ansible-epic/azure is public, so no credential set is configured. Authenticated upstreams (e.g. Docker Hub) require a credential set, which this module does not yet support.

Private access and networking

To let workloads in isolated (no-internet) VNets pull from the registry:

  • The ACR → upstream pull runs on the ACR service backend over Azure's own egress. It does not route through your VNet, so VNets with no internet route do not block cache pulls from ghcr.io.
  • The client → ACR pull can go over a private endpoint with a privatelink.azurecr.io private DNS zone linked to the consuming VNets. Private endpoints are a Premium-only feature, so using them requires setting sku = "Premium" — the default Basic registry is reachable over its public endpoint only.
  • If you move to Premium and disable public network access on the registry, keep network_rule_bypass_option = "AzureServices" (the azurerm default). Setting it to "None" blocks the internal cache service from writing pulled layers into the registry — cache pulls appear to succeed but the repository shows 0 tags/manifests.