# Deploy a private container registry with OpenTofu

Source: https://docs.quake.ai/resources/deployments/deploy-harbor-registry-template
Markdown: https://docs.quake.ai/resources/deployments/deploy-harbor-registry-template.md
> Stand up a Harbor container registry on a private subnet with one floating IP for HTTPS access using the harbor-registry OpenTofu template.

---

# Deploy a private container registry with OpenTofu

Stand up [Harbor](https://goharbor.io), an open-source container registry, on a single Quake AI instance using the [validated OpenTofu template](/docs/platform/validation#how-infrastructure-templates-are-checked) `harbor-registry`. You run it yourself; this is a self-hosted registry you operate, with projects, role-based access control, and Trivy vulnerability scanning by default.

<PricingCompanion
  components={[
    { kind: "template", slug: "harbor-registry", required: true },
  ]}
/>

<Figure size="md" caption="Harbor registry topology: private subnet, Harbor stack with data volume, one floating IP on HTTPS, Trivy scanner on push">

```d2
direction: right

client: Docker client {
  push: docker push
  pull: docker pull
}

cloud: Quake AI {
  fip: Floating IP\nHTTPS :443
  private: Private network\n10.50.0.0/24 {
    harbor: Harbor stack\nportal + registry
    trivy: Trivy scanner
    vol: Data volume\n/data {shape: cylinder}
    harbor -> trivy: scan on push
    harbor -> vol: image layers
  }
  router: Router\nto PublicStatic
}

client.push -> cloud.fip
client.pull -> cloud.fip
cloud.fip -> cloud.private.harbor
cloud.router -> cloud.private
```

</Figure>

## Prerequisites

You need:

- A Quake AI account with [application credentials](/docs/tools/generate-app-credentials)
- OpenTofu 1.6.0 or later ([installation guide](https://opentofu.org/docs/intro/install/))
- OpenStack credentials sourced into the shell (`source openrc.sh`). See [the OpenStack CLI guide](/docs/tools/openstack-cli).
- An existing SSH key pair in your project. See [Add an SSH key](/docs/tools/add-ssh-key).
- Docker installed on the client you push from, with permission to edit `/etc/docker/certs.d`
- A copy of the `harbor-registry` template from [the template reference page](/resources/iac-templates/harbor-registry)
- Enough project quota for one `s1a.large` instance, a 40 GB boot volume, a 100 GB data volume, one router, one private network, and one floating IP

## Step 1: Configure variables and apply

Copy `terraform.tfvars.example` to `terraform.tfvars` and set:

```hcl
key_name = "YOUR_KEY_NAME"
```

Leave `domain` commented out for a self-signed certificate on the floating IP. Defaults for flavor, Harbor version, and volume size are documented on the [Harbor registry](/resources/iac-templates/harbor-registry) reference page.

From the template directory, run:

```bash
tofu init
tofu plan
tofu apply
```

Type `yes` when prompted. Provisioning takes several minutes while cloud-init installs Docker, generates the certificate and admin password, and starts the Harbor stack.

When the run finishes, record the outputs:

```bash
REGISTRY_URL=$(tofu output -raw registry_url)
HARBOR_HOST=$(tofu output -raw floating_ip)
```

## Step 2: Retrieve the admin password and trust the certificate

cloud-init takes several minutes after `apply` returns. Wait for it to finish and read the admin password:

```bash
ssh -i ~/.ssh/YOUR_KEY -o StrictHostKeyChecking=accept-new ubuntu@"$HARBOR_HOST" 'cloud-init status --wait'
ssh -i ~/.ssh/YOUR_KEY ubuntu@"$HARBOR_HOST" sudo cat /root/harbor-credentials
```

Harbor serves HTTPS with a self-signed certificate on first boot. Copy the certificate into Docker's per-registry trust directory on your client:

```bash
sudo mkdir -p /etc/docker/certs.d/"$HARBOR_HOST"
ssh -i ~/.ssh/YOUR_KEY ubuntu@"$HARBOR_HOST" sudo cat /data/certs/harbor.crt | \
  sudo tee /etc/docker/certs.d/"$HARBOR_HOST"/ca.crt > /dev/null
```

## Step 3: Sign in and push an image

Open `registry_url` in your browser and sign in as `admin` with the generated password. Create a private project named `demo`.

Sign the Docker client in to the registry:

```bash
docker login "$HARBOR_HOST"
```

Tag and push a test image:

```bash
docker pull hello-world:latest
docker tag hello-world:latest "$HARBOR_HOST"/demo/hello-world:latest
docker push "$HARBOR_HOST"/demo/hello-world:latest
```

Confirm the registry serves the image back:

```bash
docker rmi "$HARBOR_HOST"/demo/hello-world:latest hello-world:latest
docker pull "$HARBOR_HOST"/demo/hello-world:latest
```

With Trivy enabled, Harbor scans the pushed image and reports vulnerabilities on the artifact detail page in the portal.

## Next steps

- [Harbor registry template](/resources/iac-templates/harbor-registry)
- [Deploy a self-hosted git forge and CI](/resources/deployments/deploy-forgejo-git-ci-template)
- [CI/CD pipelines](/resources/solutions/cicd-pipelines)
- [Kubernetes platforms](/resources/solutions/kubernetes-platforms)
- Create a robot account scoped to a project for pipeline and cluster credentials instead of the admin password

## Clean up

Run `tofu destroy` from the project directory when finished. Type `yes` to confirm. Remove the certificate you added on the client if you no longer need it:

```bash
sudo rm -rf /etc/docker/certs.d/"$HARBOR_HOST"
```
