> ## Documentation Index
> Fetch the complete documentation index at: https://docs.techulus.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Google Artifact Registry

> Provision and operate the required user-owned image registry.

Techulus Cloud requires a user-owned [Google Artifact Registry](https://cloud.google.com/artifact-registry/docs) (GAR) Docker repository for images built from GitHub sources. Techulus does not provision or own the repository. Agents use one repository-scoped Writer credential to push and pull images. User-supplied images can still use credentials configured under **Settings → Registries**.

## Image layout

Each source-backed service has one GAR package:

```text theme={null}
<location>-docker.pkg.dev/<gcp-project>/<repository>/<techulus-project-id>/<service-id>:revision-<revision-id>
```

## Provision GAR

Create the repository and Writer service account in [Google Cloud Console](https://console.cloud.google.com/). Use a billing-enabled project and enable the Artifact Registry and IAM APIs if prompted.

The operator needs permission to enable APIs, administer Artifact Registry repositories and their IAM policies, create service accounts, and create service-account keys. The predefined roles that supply these permissions are Service Usage Admin, Artifact Registry Administrator, Service Account Admin, and Service Account Key Admin, or custom roles with equivalent permissions.

### Create the repository

Open **Artifact Registry → Repositories → Create repository**:

| Setting | Value |
| - | - |
| Name | `techulus-images` |
| Format | **Docker** |
| Mode | **Standard**, not remote or virtual |
| Location | A region close to your deployment agents |
| Immutable tags | **Disabled** so cleanup can delete tagged versions |
| Encryption | Google-managed key unless you require CMEK |
| Cleanup policy mode | **Dry run**, not **Delete artifacts** yet |

Keep the repository private. Configure the [cleanup policy](#cleanup-policy) before activating deletion.

Alternatively, use Google Cloud Shell or an authenticated `gcloud` CLI:

```bash theme={null}
export GAR_PROJECT_ID=google-project-id
export GAR_LOCATION=us-central1
export GAR_REPOSITORY_ID=techulus-images

gcloud services enable artifactregistry.googleapis.com iam.googleapis.com \
  --project="$GAR_PROJECT_ID"
gcloud artifacts repositories create "$GAR_REPOSITORY_ID" \
  --project="$GAR_PROJECT_ID" \
  --location="$GAR_LOCATION" \
  --repository-format=docker \
  --description="Techulus Cloud source images"
```

### Create the Writer account

Open **IAM & Admin → Service Accounts** and create `techulus-agent` without granting a project-wide role. In the repository's **Permissions** panel, grant it Artifact Registry Writer (`roles/artifactregistry.writer`) on that repository only. The account pushes and pulls images; Techulus does not need repository administration access.

For the equivalent gcloud setup:

```bash theme={null}
export GAR_AGENT_SA="techulus-agent@${GAR_PROJECT_ID}.iam.gserviceaccount.com"
gcloud iam service-accounts create techulus-agent \
  --project="$GAR_PROJECT_ID" \
  --display-name="Techulus agents"
gcloud artifacts repositories add-iam-policy-binding "$GAR_REPOSITORY_ID" \
  --project="$GAR_PROJECT_ID" \
  --location="$GAR_LOCATION" \
  --member="serviceAccount:${GAR_AGENT_SA}" \
  --role="roles/artifactregistry.writer"
```

### Download and encode the JSON key

For the Writer account, open **Keys → Add key → Create new key → JSON** and download the key to a trusted workstation as `agent-key.json`. Service-account key creation can be prohibited by the `iam.disableServiceAccountKeyCreation` organization policy. If blocked, ask your organization administrator to approve an exemption. Google does not allow an existing private key to be downloaded again.

With gcloud, create the key using:

```bash theme={null}
umask 077
gcloud iam service-accounts keys create agent-key.json \
  --iam-account="$GAR_AGENT_SA" --project="$GAR_PROJECT_ID"
```

Encode the **complete JSON document** as one line. These commands work on macOS and Linux; use a new output filename if `gar-credentials.env` already exists:

```bash theme={null}
umask 077
printf 'GAR_AGENT_KEY_BASE64=%s\n' \
  "$(base64 < agent-key.json | tr -d '\r\n')" > gar-credentials.env
chmod 600 gar-credentials.env
```

`umask 077` makes newly created files private to your user; `chmod 600` explicitly restricts the output to owner read/write access. Base64 is not encryption. Treat both files as private keys: never commit them or paste them into chat.

### Configure Techulus

Securely copy the encoded value into the deployment `.env` or installer and add the repository path:

```env theme={null}
GAR_REPOSITORY=us-central1-docker.pkg.dev/google-project-id/techulus-images
GAR_AGENT_KEY_BASE64=<base64 Writer service-account JSON>
```

`GAR_REPOSITORY` must not include a scheme, image path, tag, or digest. The health endpoint returns 503 when a mandatory GAR value is missing or malformed. After securely transferring the credential, delete temporary files:

```bash theme={null}
rm agent-key.json gar-credentials.env
```

The control plane includes this credential in each agent's encrypted registry bundle. BuildKit uses it to push images and Podman uses it to pull deployments. TLS verification is always enabled. Static repository-scoped keys are intentional because builds can outlast GAR OAuth access tokens.

## Cleanup policy

The tracked [`deployment/gar-cleanup-policy.json`](https://github.com/techulus/cloud/blob/main/deployment/gar-cleanup-policy.json) defines exactly two repository-wide rules:

* keep the 10 most recent versions of every package, with no package-prefix filter;
* delete versions in any tag state once they are older than 30 days.

Keep policies take precedence over delete policies. A version outside the newest 10 remains available until it is older than 30 days. GAR evaluates age from version creation time.

<Warning>
  This policy is intentionally independent of deployments. An active or rollback image can disappear when it falls outside a package's newest 10 versions and becomes older than 30 days. Failed and undeployed builds count toward version history. Multi-platform images create multiple registry versions, so 10 versions does not mean 10 builds or rollouts. The newest versions of deleted services remain, and a seven-day service/data restore does not guarantee that its image is still available.
</Warning>

In the repository's cleanup settings, use **Dry run** and configure:

| Name | Action | Condition |
| - | - | - |
| `keep-most-recent-ten-versions` | Keep most recent versions | Keep count `10`, no package prefixes |
| `delete-versions-after-thirty-days` | Delete | Tag state `Any`, older than `30 days` |

The operator applying policies needs Artifact Registry Administrator permissions. From a Techulus checkout, the equivalent gcloud command is:

```bash theme={null}
gcloud artifacts repositories set-cleanup-policies "$GAR_REPOSITORY_ID" \
  --project="$GAR_PROJECT_ID" \
  --location="$GAR_LOCATION" \
  --policy=deployment/gar-cleanup-policy.json \
  --dry-run
```

GAR evaluates cleanup asynchronously, generally within one day. Enable Artifact Registry Data Write audit logs, wait for a dry-run cycle, and inspect proposed `BatchDeleteVersions` operations. When correct, switch to **Delete artifacts** in Console or run the same command with `--no-dry-run`.

### Updating an existing repository

After deploying the code that no longer manages GAR tags, replace the old protected-tag Keep rule with the tracked policy; Techulus does not update cloud policies automatically. Then remove `GAR_ADMIN_KEY_BASE64` from the deployment environment and revoke/delete the unused Repository Administrator service-account key. Remove the account or its repository role if it has no other purpose. There is no compatibility or migration automation.

## Upgrading from the bundled registry

This is a clean beta cutover. Techulus does not copy old images or rewrite persisted image references. Configure GAR and the Writer key before upgrading. Existing source-built revisions then point to an unavailable old registry and every source-backed service must be rebuilt before it can deploy from GAR.

The upgrade removes the old registry container but leaves its Docker volume. After verifying the upgrade and deciding rollback data is no longer needed, inspect and explicitly remove the orphaned volume:

```bash theme={null}
docker volume ls --filter name=registry-data
docker volume rm <exact-volume-name>
```

Volume deletion is irreversible and is never performed automatically.

## Global custom registries

Administrators can add credentials for user-supplied private images under **Settings → Registries**. A host, including an explicit port when needed, is globally unique and cannot include a scheme or path. The GAR system host is reserved.

Changes are queued for every registered agent, including offline agents. Entries cannot be edited; delete and recreate one to change its username, password, or TLS setting. Use pull-only robot or service-account credentials where possible. Passwords are write-only in the UI, and an actual agent pull remains the authoritative availability check.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.