Skip to content

Harbor registry

Template · Updated Jun 2026
Validated Jun 2026

Harbor registry

This pattern composes Compute, Network, and Block Storage into a self-hosted container registry on infrastructure you control.

What this template does#

Provisions a single instance running Harbor, an open-source container registry. Harbor stores and serves Docker and OCI images over HTTPS, with projects, role-based access control, and (by default) Trivy vulnerability scanning. Container and Kubernetes pipelines push built images here and pull them at deploy time:

  • Compute instance that runs the Harbor stack in Docker, sized to the registry's recommended baseline
  • Private network, subnet, router, port, and security group; a floating IP for public access
  • A block volume mounted at /data (Harbor's data_volume), so image layers, the registry database, and the scanner's vulnerability database live on a volume you can grow rather than on the boot disk
  • cloud-init installs Docker, generates a self-signed TLS certificate and the admin password, downloads the Harbor installer, and brings the stack up on first boot

No credential ships with this template. The instance generates the admin password and the database password on first boot and writes the admin password to /root/harbor-credentials (readable only by root); retrieve it over SSH and rotate it after first login.

Harbor and the plain registry image#

This template runs Harbor, which adds projects, users, replication, and image scanning on top of the registry. For a much lighter target with none of that, the Distribution registry image runs in a single container with no UI or access control. Harbor is the right pick when you want a registry that people and pipelines share; the plain registry image is enough when you only need a private place to push and pull.

Parameters#

ParameterDescriptionDefault
key_nameSSH keypair name (must already exist)No default
flavor_nameInstance size (8 GiB meets Harbor's recommended baseline)s1a.large
image_nameOperating system imageUbuntu-24.04
app_nameDisplay name prefix for resourcesharbor
harbor_versionHarbor release tag to installv2.14.4
domainPublic domain for the registry; empty uses the floating IP""
volume_sizeBlock volume size in GiB, mounted at /data100
enable_trivyInstall the Trivy vulnerability scannertrue
external_networkExternal network for floating IP allocationPublicStatic
private_cidrCIDR for the private subnet10.50.0.0/24

Resource baseline#

Harbor's documented baseline is 2 vCPU / 4 GiB minimum and 4 vCPU / 8 GiB recommended, because the stack runs a dozen containers: the registry, the core service, the portal, a database, a job service, Redis, and the Trivy scanner. The default s1a.large flavor (8 shared vCPU, 8 GiB RAM) meets the recommended memory with headroom for image pushes and scans. To run on a 4 GiB flavor, set enable_trivy = false to drop the scanner.

Ports and access#

PortPurpose
22Host SSH for administration and retrieving the admin password
443Harbor portal, core API, and docker/OCI push and pull over HTTPS

Harbor serves HTTPS only; there is no plaintext registry port. On first boot the certificate is self-signed, so a client trusts it by copying /data/certs/harbor.crt into its Docker per-registry trust directory before the first docker login. For anything exposed to the internet, set domain, point its DNS A record at the floating IP, and replace the first-boot certificate with a CA-issued one. The README in the template directory covers both paths.

How images flow#

Harbor groups repositories into projects. The default library project is public; create your own projects from the portal for private images. A typical push from a client that trusts the certificate:

bash
docker login HOST          # username admin, password from /root/harbor-credentials
docker tag myapp:latest HOST/library/myapp:latest
docker push HOST/library/myapp:latest

With Trivy enabled, Harbor scans each pushed image for known vulnerabilities and reports them in the portal. You can require a passing scan before an image can be pulled by setting a project's deployment security policy.

When to use this pattern#

Run your own container registry on a VM you operate, so your build and deploy pipelines push and pull images without depending on a third-party registry. It pairs with the Forgejo git + CI template (build images in CI, push them here) and the Coolify host (pull from here on deploy). A Kubernetes cluster pulls images from this registry by referencing HOST/project/image:tag in a pod spec, with an image pull secret holding the Harbor credentials.

For a managed build-and-deploy dashboard rather than a registry, use the Coolify host template.

Estimated cost#

Monthly cost estimate

Pricing calculator ↗

Sized as a custom package on shared vCPU.

Starting template$72.20/mo

Monthly total for the required template above. Use the configurator below to add optional pieces and see the total update.

What each resource is for

Harbor

s1a.large · 8 shared vCPU, 8 GiB RAM, 0.5 Gbps

$66.00/mo

Compute shown per role at custom-package rates ($29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM). The headline above is the billed total: the cheaper of a named plan and the custom package, plus add-ons.

Included in baseline

s1a.large

8 shared vCPU, 8 GiB RAM, 0.5 Gbps

$66.00

Compute + RAM rate basis

8 vCPU + 8 GiB RAM at $29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM (regular). Totals apply the flat −$5/mo package promotion.

—

Block storage (140 GiB)

140 GiB at $0.08/GiB/mo

$11.20

Public IP (included)

1 included with the custom package

$0.00

Package promotional discount

Flat −$5.00/mo on the custom package (same promotion as named plans).

$-5.00

Included at no charge

These line items are zero on Quake AI. Many other providers meter them separately.

Data transfer (inbound and outbound)

Unlimited data transfer on every plan; Quake AI does not meter per-GB egress.

AWS, GCP, and Azure meter outbound transfer per GB. DigitalOcean and Hetzner include an allowance on compute plans, then charge overage.

Learn more
$0.00

Private networking

Private networks, subnets, Neutron routers, and security groups are included with the plan.

VPC objects are usually free to create elsewhere, but NAT gateways bill hourly plus per-GB processed. Quake AI uses router SNAT with no separate NAT line item.

$0.00

Control-plane API requests

OpenStack API calls for provisioning and management are included.

Some managed services on other clouds meter API calls or charge for premium control-plane features.

$0.00

Pricing data last validated: . For current rates, check quake.ai/pricing.

Template source#

7 files. Download the zip or expand to copy any file.Download harbor-registry.zip
Show source (7 files)
main.tfHCL
data "openstack_images_image_v2" "os" {
  name        = var.image_name
  most_recent = true
}

data "openstack_networking_network_v2" "external" {
  name = var.external_network
}

resource "openstack_networking_network_v2" "private" {
  name           = "${var.app_name}-net"
  admin_state_up = true
}

resource "openstack_networking_subnet_v2" "private" {
  name            = "${var.app_name}-subnet"
  network_id      = openstack_networking_network_v2.private.id
  cidr            = var.private_cidr
  ip_version      = 4
  dns_nameservers = ["1.1.1.1", "8.8.8.8"]
}

resource "openstack_networking_router_v2" "main" {
  name                = "${var.app_name}-router"
  external_network_id = data.openstack_networking_network_v2.external.id
}

resource "openstack_networking_router_interface_v2" "private" {
  router_id = openstack_networking_router_v2.main.id
  subnet_id = openstack_networking_subnet_v2.private.id
}

resource "openstack_networking_secgroup_v2" "harbor" {
  name        = "${var.app_name}-sg"
  description = "Admin SSH and HTTPS for the Harbor registry"
}

# Host SSH for administration and retrieving the generated admin password.
resource "openstack_networking_secgroup_rule_v2" "ssh" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 22
  port_range_max    = 22
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.harbor.id
}

# HTTPS carries the Harbor portal, the core API, and docker/OCI push and pull.
# Harbor is configured for HTTPS only; there is no plaintext registry port.
resource "openstack_networking_secgroup_rule_v2" "https" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 443
  port_range_max    = 443
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.harbor.id
}

resource "openstack_networking_port_v2" "harbor" {
  name               = "${var.app_name}-port"
  network_id         = openstack_networking_network_v2.private.id
  security_group_ids = [openstack_networking_secgroup_v2.harbor.id]

  fixed_ip {
    subnet_id = openstack_networking_subnet_v2.private.id
  }

  depends_on = [openstack_networking_router_interface_v2.private]
}

resource "openstack_blockstorage_volume_v3" "data" {
  name = "${var.app_name}-data"
  size = var.volume_size
}

resource "openstack_networking_floatingip_v2" "harbor" {
  pool = var.external_network
}

resource "openstack_compute_instance_v2" "harbor" {
  name        = var.app_name
  flavor_name = var.flavor_name
  key_pair    = var.key_name

  user_data = templatefile("${path.module}/cloud-init/harbor.yaml.tftpl", {
    harbor_version = var.harbor_version
    domain         = var.domain
    enable_trivy   = var.enable_trivy
    floating_ip    = openstack_networking_floatingip_v2.harbor.address
  })

  block_device {
    uuid                  = data.openstack_images_image_v2.os.id
    source_type           = "image"
    destination_type      = "volume"
    volume_size           = 40
    boot_index            = 0
    delete_on_termination = true
  }

  network {
    port = openstack_networking_port_v2.harbor.id
  }
}

resource "openstack_compute_volume_attach_v2" "data" {
  instance_id = openstack_compute_instance_v2.harbor.id
  volume_id   = openstack_blockstorage_volume_v3.data.id
}

resource "openstack_networking_floatingip_associate_v2" "harbor" {
  floating_ip = openstack_networking_floatingip_v2.harbor.address
  port_id     = openstack_networking_port_v2.harbor.id
}
variables.tfHCL
variable "key_name" {
  description = "SSH keypair name (must already exist in your project)"
  type        = string
}

variable "flavor_name" {
  description = "Instance size. Harbor's documented baseline is 2 vCPU / 4 GiB minimum, 4 vCPU / 8 GiB recommended, because the bundled stack (registry, core, database, job service, and the Trivy scanner) runs as a dozen containers on one host. The default s1a.large (8 shared vCPU / 8 GiB) meets the recommended memory with headroom for image pushes and scans. Drop to a 4 GiB flavor and set enable_trivy = false for a lighter footprint."
  type        = string
  default     = "s1a.large"
}

variable "image_name" {
  description = "Operating system image. cloud-init targets a Debian-family distribution; Ubuntu 24.04 is the recommended base."
  type        = string
  default     = "Ubuntu-24.04"
}

variable "app_name" {
  description = "Display name prefix for compute and network resources"
  type        = string
  default     = "harbor"
}

variable "harbor_version" {
  description = "Harbor release tag to install (matches a tag on github.com/goharbor/harbor/releases, for example v2.14.4). cloud-init downloads the matching online installer. Bump this to upgrade; the installer migrates the config on the next boot."
  type        = string
  default     = "v2.14.4"
}

variable "domain" {
  description = "Public domain for the registry, used as the Harbor hostname and in the registry URL (for example registry.example.com). Leave empty to use the floating IP. The instance always serves HTTPS; with a domain set, point its DNS A record at the floating IP and replace the first-boot self-signed certificate with a CA-issued one (see the README)."
  type        = string
  default     = ""
}

variable "volume_size" {
  description = "Block volume size in GiB for image layers, the registry database, and Trivy's vulnerability database. The volume is mounted at /data (Harbor's data_volume) so all registry state lives on a resizable volume rather than the boot disk. Harbor's documented minimum is 40 GiB; image storage is the main driver of growth."
  type        = number
  default     = 100
}

variable "enable_trivy" {
  description = "Install the Trivy vulnerability scanner alongside the registry. Trivy adds image scanning at the cost of roughly 1 GiB of RAM and a vulnerability-database download. Set false to fit a 4 GiB flavor."
  type        = bool
  default     = true
}

variable "external_network" {
  description = "Shared external network for router gateway and floating IPs; defaults to PublicStatic (persisted FIP / production pattern). Override with PublicEphemeral for ephemeral demos."
  type        = string
  default     = "PublicStatic"
}

variable "private_cidr" {
  description = "CIDR for the private tenant network the instance lives in"
  type        = string
  default     = "10.50.0.0/24"
}
outputs.tfHCL
output "instance_id" {
  description = "ID of the compute instance running Harbor"
  value       = openstack_compute_instance_v2.harbor.id
}

output "floating_ip" {
  description = "Public floating IP address of the registry"
  value       = openstack_networking_floatingip_v2.harbor.address
}

output "private_ip" {
  description = "Private IP address of the instance"
  value       = openstack_compute_instance_v2.harbor.access_ip_v4
}

output "registry_url" {
  description = "Harbor portal and registry endpoint. Uses the domain when set, otherwise the floating IP. The first-boot certificate is self-signed; replace it with a CA-issued certificate before relying on the domain (see the README)."
  value       = var.domain != "" ? "https://${var.domain}" : "https://${openstack_networking_floatingip_v2.harbor.address}"
}
versions.tfHCL
terraform {
  required_version = ">= 1.6.0"

  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "~> 2.0"
    }
  }
}

provider "openstack" {}
terraform.tfvars.exampleHCL
# Required: SSH keypair must already exist in your project
key_name = "YOUR_KEY_NAME"

# Recommended: set a domain so the registry URL and pushed image references use a
# stable name. Point its DNS A record at the floating IP and replace the first-boot
# self-signed certificate with a CA-issued one (see the README).
# domain = "registry.example.com"

# harbor_version = "v2.14.4"
# flavor_name = "s1a.large"
# image_name = "Ubuntu-24.04"
# app_name = "harbor"
# volume_size = 100
# enable_trivy = true
# external_network = "PublicStatic"
# private_cidr = "10.50.0.0/24"
cloud-init/harbor.yaml.tftpl
#cloud-config
package_update: true
packages:
  - ca-certificates
  - curl
  - openssl
  - tar

write_files:
  # First-boot bootstrap. Generates a self-signed TLS certificate, the admin
  # password, and the database password on the instance: none of them is shipped
  # in this template. Downloads the Harbor online installer for the requested
  # version, writes harbor.yml, and runs the installer. The admin password is
  # written to /root/harbor-credentials (root-only); retrieve it over SSH.
  - path: /opt/harbor-bootstrap.sh
    permissions: "0755"
    content: |
      #!/usr/bin/env bash
      set -euo pipefail

      CRED_FILE=/root/harbor-credentials
      INSTALL_DIR=/opt/harbor-install
      CERT_DIR=/data/certs
      HARBOR_HOST="${domain != "" ? domain : floating_ip}"

      # Idempotent across reboots: if already installed, just bring the stack up.
      if [ -f "$CRED_FILE" ]; then
        cd "$INSTALL_DIR/harbor"
        docker compose up -d
        exit 0
      fi

      mkdir -p "$CERT_DIR"

      # First-boot self-signed certificate. Harbor is configured for HTTPS only,
      # so docker and OCI clients need TLS from the first push. This cert lets
      # them connect after trusting it; replace it with a CA-issued certificate
      # for anything beyond a private network or a trial (see the README).
      openssl req -x509 -newkey rsa:4096 -nodes \
        -keyout "$CERT_DIR/harbor.key" \
        -out "$CERT_DIR/harbor.crt" \
        -days 825 \
        -subj "/CN=$HARBOR_HOST" \
        -addext "subjectAltName=${domain != "" ? "DNS:${domain}" : "IP:${floating_ip}"}"
      chmod 600 "$CERT_DIR/harbor.key"

      # Generate the admin and database passwords on the instance. Neither is
      # shipped in the template. Restrict to an alphanumeric set so the values
      # are safe in YAML and connection strings.
      ADMIN_PASSWORD="$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24)"
      DB_PASSWORD="$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24)"

      # Download and unpack the Harbor online installer for the requested version.
      mkdir -p "$INSTALL_DIR"
      cd "$INSTALL_DIR"
      curl -fsSL -o harbor.tgz \
        "https://github.com/goharbor/harbor/releases/download/${harbor_version}/harbor-online-installer-${harbor_version}.tgz"
      tar -xzf harbor.tgz
      cd harbor

      cat > harbor.yml <<HARBOR_YML
      hostname: $HARBOR_HOST
      https:
        port: 443
        certificate: $CERT_DIR/harbor.crt
        private_key: $CERT_DIR/harbor.key
      harbor_admin_password: $ADMIN_PASSWORD
      database:
        password: $DB_PASSWORD
        max_idle_conns: 100
        max_open_conns: 900
        conn_max_lifetime: 5m
        conn_max_idle_time: 0
      data_volume: /data
%{ if enable_trivy }
      trivy:
        ignore_unfixed: false
        skip_update: false
        offline_scan: false
        security_check: vuln
        insecure: false
%{ endif }
      jobservice:
        max_job_workers: 10
        job_loggers:
          - STD_OUTPUT
          - FILE
        logger_sweeper_duration: 1
      notification:
        webhook_job_max_retry: 3
        webhook_job_http_client_timeout: 3
      log:
        level: info
        local:
          rotate_count: 50
          rotate_size: 200M
          location: /var/log/harbor
      _version: 2.14.0
      upload_purging:
        enabled: true
        age: 168h
        interval: 24h
        dryrun: false
      HARBOR_YML

      # Install Harbor. --with-trivy adds the vulnerability scanner service.
      ./install.sh%{ if enable_trivy } --with-trivy%{ endif }

      umask 077
      cat > "$CRED_FILE" <<CRED
      Harbor admin account (generated on first boot)
      url:      ${domain != "" ? "https://${domain}" : "https://${floating_ip}"}
      username: admin
      password: $ADMIN_PASSWORD

      Rotate this password after first login and delete this file.
      CRED
      chmod 600 "$CRED_FILE"

runcmd:
  - |
    set -e
    # The data volume attaches as /dev/sdb on this platform (not /dev/vdb).
    # Mount it at /data (Harbor's data_volume) before the installer runs, so image
    # layers, the registry database, and Trivy's DB live on the resizable volume.
    DEV=/dev/sdb
    for i in $(seq 1 30); do [ -b "$DEV" ] && break; sleep 5; done
    if ! blkid "$DEV" >/dev/null 2>&1; then mkfs.ext4 -F -L harbordata "$DEV"; fi
    mkdir -p /data
    mount "$DEV" /data
    grep -q "$DEV" /etc/fstab || echo "$DEV /data ext4 defaults,nofail 0 2" >> /etc/fstab
    # Install Docker Engine (provides docker + the compose plugin Harbor needs).
    curl -fsSL https://get.docker.com | sh
    systemctl enable --now docker
    /opt/harbor-bootstrap.sh
README.mdMarkdown
# Harbor registry

Single compute instance running [Harbor](https://goharbor.io), an open-source container registry, on infrastructure you control. Harbor stores and serves Docker and OCI images over HTTPS, with projects, role-based access, and (by default) Trivy vulnerability scanning. After apply, you `docker login` to the floating IP or your domain and push images for your container and Kubernetes pipelines to pull.


**Network class:** production — `external_network` defaults to `PublicStatic` for persisted floating IPs and multi-tier stacks; override with `PublicEphemeral` for ephemeral demos.

The instance provisions a private network, a floating IP, and a block volume mounted at `/data` (Harbor's `data_volume`) so image layers, the registry database, and Trivy's vulnerability database live on a resizable volume rather than the boot disk. cloud-init installs Docker, generates a self-signed TLS certificate and the admin password, downloads the Harbor installer, and brings the stack up, all on first boot.

## Where this fits

Container and Kubernetes pipelines need a push target. Only third-party registries are documented elsewhere; this is a self-hosted registry on infrastructure you own. It feeds the [Forgejo git + CI](/resources/iac-templates/forgejo-git-ci) and [Coolify host](/resources/iac-templates/coolify-host) tracks (push built images here) and the [Kubernetes cluster](/resources/iac-templates/k8s-cluster) track (pull from here).

For a much lighter target with no UI, scanning, or access control, the plain [Distribution `registry`](https://distribution.github.io/distribution/) image runs in a single container. Harbor is the right pick when you want projects, users, replication, and image scanning; the bare `registry` image is enough when you only need a private place to push and pull.

## Prerequisites

- OpenTofu >= 1.6.0 or Terraform >= 1.6.0
- Quake AI account with OpenStack credentials
- An existing SSH keypair in your project (the value of `key_name` must match that keypair)

## Resource baseline

Harbor's documented baseline is 2 vCPU / 4 GiB minimum and 4 vCPU / 8 GiB recommended, because the stack runs as roughly a dozen containers (registry, core, portal, database, job service, Redis, and the Trivy scanner). The default `s1a.large` flavor (8 shared vCPU, 8 GiB RAM) meets the recommended memory with headroom for image pushes and scans. To run on a 4 GiB flavor, set `enable_trivy = false` to drop the scanner. The boot disk is 40 GiB; image storage lives on the separate data volume (`volume_size`, default 100 GiB).

## Usage

1. Clone or copy this template directory
2. Copy `terraform.tfvars.example` to `terraform.tfvars` and set `key_name` (and `domain` if you have one)
3. Source your OpenStack credentials: `source openrc.sh`
4. Initialize: `tofu init`
5. Preview: `tofu plan`
6. Apply: `tofu apply`

cloud-init takes several minutes on first boot to install Docker, download the Harbor installer, pull the service images, and start the stack. Then open `registry_url` from the outputs.

## First-boot accounts and secrets

No credential ships with this template. On first boot the instance:

- Generates a self-signed TLS certificate (CN and SAN set to your `domain`, or the floating IP when no domain is set) and writes it under `/data/certs`.
- Generates the admin password and the database password with `openssl rand`. The admin password is written to `/root/harbor-credentials` (mode 600). Retrieve it over SSH: `ssh user@<floating_ip> sudo cat /root/harbor-credentials`, then sign in as `admin` and rotate it.

## How the instance is provisioned

cloud-init:

1. Mounts the data volume at `/data` (formatting it on first boot) and adds an `/etc/fstab` entry so it persists across reboots.
2. Installs Docker Engine with the official convenience script and enables the service.
3. Runs `/opt/harbor-bootstrap.sh`, which generates the certificate and passwords, writes `harbor.yml`, downloads the installer for `harbor_version`, and runs `./install.sh`.

The bootstrap script is idempotent: on reboot it brings the stack back up with `docker compose` without reinstalling or regenerating the password.

## Ports and access

| Port | Purpose | Open to |
| --- | --- | --- |
| 22 | Host SSH for administration and retrieving the admin password | `0.0.0.0/0` |
| 443 | Harbor portal, core API, and docker/OCI push and pull over HTTPS | `0.0.0.0/0` |

Harbor is configured for HTTPS only; there is no plaintext registry port.

## Logging in and pushing

Harbor serves a self-signed certificate on first boot, so a client has to trust it before it will connect. Either copy the certificate into the Docker daemon's per-registry trust directory:

```bash
# On the client, with HOST = your domain or the floating IP
sudo mkdir -p /etc/docker/certs.d/HOST
sudo scp user@HOST:/data/certs/harbor.crt /etc/docker/certs.d/HOST/ca.crt
docker login HOST          # username admin, password from /root/harbor-credentials
docker tag myapp:latest HOST/library/myapp:latest
docker push HOST/library/myapp:latest
```

`library` is Harbor's default public project; create your own projects from the portal.

## TLS for production

The first-boot certificate is self-signed, which is fine for a private network or a quick trial. For anything exposed to the internet:

- Set `domain`, point its DNS A record at `floating_ip`, and obtain a CA-issued certificate (for example with `certbot certonly`).
- Replace `/data/certs/harbor.crt` and `/data/certs/harbor.key` with the issued certificate and key, then re-run `./install.sh` in `/opt/harbor-install/harbor` to apply.

With a CA-issued certificate, clients trust the registry without the per-registry `certs.d` step above.

## Variables

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `key_name` | string | yes | n/a | SSH keypair name (must already exist in your project) |
| `flavor_name` | string | no | `s1a.large` | Instance size (8 GiB meets Harbor's recommended baseline) |
| `image_name` | string | no | `Ubuntu-24.04` | Operating system image |
| `app_name` | string | no | `harbor` | Display name prefix for resources |
| `harbor_version` | string | no | `v2.14.4` | Harbor release tag to install |
| `domain` | string | no | `""` | Public domain for the registry; empty uses the floating IP |
| `volume_size` | number | no | `100` | Block volume size in GiB, mounted at `/data` |
| `enable_trivy` | bool | no | `true` | Install the Trivy vulnerability scanner |
| `external_network` | string | no | `PublicStatic` | Persisted FIP / production default; override with `PublicEphemeral` for demos |
| `private_cidr` | string | no | `10.50.0.0/24` | CIDR for the private subnet |

## Outputs

| Name | Description |
| --- | --- |
| `floating_ip` | Public floating IP assigned to the instance |
| `private_ip` | Private IP address of the instance |
| `registry_url` | Harbor portal and registry endpoint (domain when set, otherwise the floating IP) |
| `instance_id` | Compute instance ID |

## Scope

This is a single-VM registry you operate, not a managed control plane. It is CPU-only and runs in one region. For high availability, run Harbor against external Postgres, Redis, and object storage and place multiple instances behind a load balancer; this template provisions one node with file-backed storage on the attached volume. For replication to or from another registry, configure a replication endpoint from the Harbor portal.

## Documentation

See also: [Forgejo git + CI template](/resources/iac-templates/forgejo-git-ci), [Kubernetes cluster template](/resources/iac-templates/k8s-cluster)
Resources, parameters, and variables
Provisions
Parameterized by
Variables
  • key_namerequired
  • flavor_name="s1a.large"
  • image_name="Ubuntu-24.04"
  • app_name="harbor"
  • harbor_version="v2.14.4"
  • domain=""
  • volume_size=100
  • enable_trivy=true
  • external_network="PublicStatic"
  • private_cidr="10.50.0.0/24"

Customize this pattern#

See also#

Usage Guidelines

The sample code, software libraries, command line tools, proofs of concept, templates, and other related technology on this page (including any of the foregoing that is provided by Quake AI personnel) is provided to you as Quake AI Content under the Quake AI Customer Agreement, or the relevant written agreement between you and Quake AI (whichever applies). Do not use this Quake AI Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the Quake AI Content (such as sample code) as appropriate for production grade use based on your specific quality control practices and standards. Deploying Quake AI Content may incur Quake AI charges for creating or using Quake AI chargeable resources, such as running Compute instances or storing data in Object Storage. Your use is also subject to the Acceptable Use Policy.

For the full policy, see Usage Guidelines.

Last validated: 29.06.2026

Was this page helpful?