# Azure Container Registry Integration Guide
**Cold Bore Ballistics LLC — Confidential**
Last updated: 2026-06-06

> How to wire a new containerized service to the CBB Azure Container Registry (`crcbbprod`). Follow this guide when adding any service that runs as a Docker container in Azure Container Apps. For the ACR resource inventory see `Azure_Architecture_Reference.md` §7. For image push procedures and credential rotation see `Azure_Operating_Runbook.md`.

---

## Overview

CBB runs a single ACR instance for all container images:

| Resource | Value |
|---|---|
| Registry name | `crcbbprod` |
| Login server | `crcbbprod.azurecr.io` |
| SKU | Basic |
| Terraform service | `services/scope-dope` |
| Admin user | **Disabled** — there is no shared registry username/password |
| Auth model | Identity-based: Container Apps pull with their managed identity (**AcrPull** role); pushes use the operator's Entra identity or CI's OIDC federated identity |

All containerized services push images to this registry and pull from it at runtime. A new service does not get its own registry — it gets its own repository path within `crcbbprod`. **No shared credential exists** — both push and pull are identity-based (Entra/managed identity), which keeps the registry out of the secret inventory entirely.

---

## Image Naming Convention

| Pattern | Example |
|---|---|
| `crcbbprod.azurecr.io/<service-name>:<tag>` | `crcbbprod.azurecr.io/feedback-service:latest` |

Tags:
- `latest` — current production image, managed by CI/CD
- `v<version>` — pinned release tag, pushed alongside `latest` at release time

---

## Step-by-Step: Adding a New Containerized Service

### Step 1 — Establish the image repository path

Decide on the service name and its repository path before writing any Terraform or Dockerfiles. Document it in `Azure_Architecture_Reference.md` under the service's resource table.

Example: a new service called `data-export` uses `crcbbprod.azurecr.io/data-export:latest`.

---

### Step 2 — Write the Dockerfile

The Dockerfile lives in the service's git repository. No special ACR configuration is needed in the Dockerfile itself — the registry is specified at push time.

---

### Step 3 — Wire the Container App to pull via managed identity

The `container_app` module's `registry` block uses the Container App's **system-assigned managed identity** (`identity = "System"`) — there is no username/password. Pass only the login server:

```hcl
module "container_app" {
  source = "../../modules/container_app"
  # ...
  container_image = var.container_image      # e.g. "crcbbprod.azurecr.io/data-export:latest"
  registry_server = var.acr_login_server     # "crcbbprod.azurecr.io"
  key_vault_uri   = module.key_vault.uri     # for app secrets, not registry auth
  # ...
}
```

Then grant the Container App's managed identity **AcrPull** on the registry. This uses the
same two-step bootstrap as the Key Vault grant (first apply creates the app + outputs its
`principal_id`; populate the var and re-apply):

```hcl
resource "azurerm_role_assignment" "myservice_acr_pull" {
  count                = var.myservice_principal_id != "" ? 1 : 0
  scope                = var.acr_id          # ACR resource ID (from scope-dope: `terraform output -raw acr_id`)
  role_definition_name = "AcrPull"
  principal_id         = var.myservice_principal_id
}
```

The `container_app` module sets `lifecycle { ignore_changes = [template[0].container[0].image] }` so Terraform does not revert the image after CI/CD pushes a new one. The image tag in `terraform.tfvars` is only used on the first apply.

---

### Step 4 — (none)

There is **no registry credential to seed** — auth is identity-based. (Previously this step
populated `acr-registry-password` in Key Vault; that secret and the ACR admin user have been
removed.)

---

### Step 5 — Set up CI/CD image push (identity-based)

CI/CD builds and pushes images using its **OIDC federated identity** (the same
`azure/login` the Terraform pipeline uses) — no registry password. The identity needs the
**AcrPush** role on the registry. Workflow steps:

```yaml
- uses: azure/login@v2
  with:
    client-id: ${{ vars.AZURE_CLIENT_ID }}
    tenant-id: ${{ vars.AZURE_TENANT_ID }}
    subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}

- name: Log in to ACR (token from the Azure identity, no password)
  run: az acr login --name crcbbprod

- uses: docker/build-push-action@v5
  with:
    push: true
    tags: |
      crcbbprod.azurecr.io/<service-name>:latest
      crcbbprod.azurecr.io/<service-name>:v${{ github.ref_name }}
```

For a local manual push, the operator's own `az login` identity works the same way
(`az acr login --name crcbbprod`), provided they hold AcrPush.

---

### Step 6 — First deploy

On first `terraform apply`, the Container App starts with the image tag specified in `terraform.tfvars`. After the first CI/CD push replaces it with the real image, the `ignore_changes` rule ensures subsequent Terraform runs do not revert it.

Verify the Container App is running the correct image:

```bash
az containerapp show \
  --name <container-app-name> \
  --resource-group rg-cbb-prod \
  --query "properties.template.containers[0].image" \
  --output tsv
```

---

## What NOT to Do

| Do not | Why |
|---|---|
| Create a new ACR per service | One registry serves all CBB services; proliferation increases cost and operational overhead |
| Re-enable the ACR admin user | Identity-based auth (managed identity / OIDC) is the standard; the shared admin credential was deliberately removed |
| Push images manually to production | All production images are pushed by CI/CD; manual pushes are not auditable |
| Use the `latest` tag as the only tag | Always push a versioned tag alongside `latest` so rollback is possible |
| Pin the image tag in `terraform.tfvars` to a specific version | Terraform manages infrastructure, not deployments; the `ignore_changes` rule hands image management to CI/CD after first apply |

---

## Checklist — New Containerized Service

**Before any Terraform:**
- [ ] Repository path decided and documented in `Azure_Architecture_Reference.md`
- [ ] Dockerfile written and tested locally
- [ ] `container_image` + `registry_server` wired (registry block uses `identity = "System"`)
- [ ] AcrPull role assignment for the Container App MI added (two-step `principal_id` bootstrap)

**After Terraform apply:**
- [ ] Container App starts successfully with placeholder or initial image
- [ ] CI/CD workflow added with `azure/login` (OIDC) + `az acr login` (no password)
- [ ] The CI identity holds **AcrPush** on the registry
- [ ] First CI/CD push verified — Container App running the correct image

---

## Document Control

| Field | Value |
|---|---|
| **Document owner** | `jschwefel@coldboreballisticsllc.com` |
| **Classification** | Confidential (03_-_Confidential/) |
| **Location** | `OneDrive/CBB_Corporate/03_-_Confidential/Infrastructure/Azure/ACR_Integration_Guide.md` |
| **Cross-references** | `Azure_Architecture_Reference.md`; `Azure_Operating_Runbook.md` |
| **Created** | 2026-06-07 |
| **Last updated** | 2026-06-07 |
| **Next full review due** | 2027-06-07 |

---

## Revision Control Block

| Rev | Date | Changed By | Reviewed By | Approved By | Summary |
|-----|------|------------|-------------|-------------|---------|
| 0.9 | 2026-06-07 | jschwefel | — | — | Initial release |
