For the complete documentation index, see llms.txt. This page is also available as Markdown.

Create Docker registries using Terraform

Provision a Harness Artifact Registry virtual registry, an upstream proxy to Docker Hub, and link them as infrastructure as code with the Harness Terraform provider.

This tutorial walks you through creating a fully functional Docker registry in Harness Artifact Registry using Terraform, an open-source infrastructure-as-code tool that lets you define cloud resources in configuration files instead of clicking through a UI.

Instead of creating registries by hand in the Harness console, you write a few short configuration files and run a single command. The result is the same (a working Docker registry), but the setup is now version-controlled, repeatable, and auditable.

What you will build

  • A virtual registry, the URL your team uses to pull and push Docker images.

  • An upstream proxy, a caching layer that connects to Docker Hub, so the first time someone pulls a public image (like nginx or alpine), it is fetched from Docker Hub and cached in Harness. Every pull after that is served from the cache.

  • A link between them, so pulls from your virtual registry automatically resolve through Docker Hub when needed.

  Your team runs:
  docker pull <your-registry>/library/nginx:latest



  ┌─────────────────────────┐
  │  my-docker (VIRTUAL)    │  ← Your team points Docker here
  │                         │
  │  Checks local cache     │
  │  Cache miss? Ask proxy: │
  │         │               │
  │         ▼               │
  │  dockerhub-proxy        │  ← Fetches from Docker Hub
  │  (UPSTREAM)             │     and caches the result
  └─────────────────────────┘

Before you begin

Make sure you have the following:

  • A Harness account with Artifact Registry enabled. Sign up if you do not have one.

  • Terraform v1.5.0 or later installed on your machine. Go to Install Terraform to download it.

  • Docker installed on your machine (used to verify the registry at the end). Go to Install Docker to set it up.

  • A Harness API key: a Personal Access Token (PAT) or Service Account Token. Generate one in the Harness UI under My Profile > + API Key > + Token. This is how Terraform authenticates with Harness.

  • Your project identifiers, three values:

    • Account ID: visible in your Harness URL or under Account Settings > Overview.

    • Organization ID: the identifier of your Harness organization (for example, default).

    • Project ID: the identifier of the project where you want to create the registries.


Overview

This tutorial has three steps. Each step creates its own Terraform configuration in a separate directory:

Step
What it does
Directory

Step 1

Creates an empty virtual registry (the client-facing endpoint).

01-virtual-registry/

Step 2

Creates an upstream proxy connected to Docker Hub.

02-upstream-proxy/

Step 3

Links the proxy to the virtual registry so pulls resolve through Docker Hub.

03-link-virtual/

Why three separate steps? Each step manages a different resource with its own Terraform state. You can update the upstream proxy (for example, switching from anonymous to authenticated Docker Hub access) without touching the virtual registry, or attach more upstream proxies to the same virtual registry later.

Each directory contains these files:

File
Purpose

versions.tf

Declares the Terraform version and the Harness provider dependency.

providers.tf

Configures how Terraform authenticates with Harness.

variables.tf

Defines the input parameters (account ID, API key, registry name, and so on).

main.tf

The core resource definition, where the registry is actually created.

outputs.tf

Values exported after creation (registry URL, identifier, and so on) for use in later steps.

You do not need to memorize this. Each file is shown in full below.


Step 1: Create the virtual registry

The virtual registry is the entry point for your team's Docker operations. In this first step, you create it without any upstream proxy attached. The proxy connection is made in Step 3.

Create a directory called 01-virtual-registry and add the following five files.

versions.tf declares which provider to download. The Harness provider is a plugin that knows how to talk to the Harness API.

providers.tf configures the Harness provider with your credentials. The values come from the variables in the next file.

Artifact Registry is a Harness Next Gen feature. Always use platform_api_key (not the legacy api_key) when configuring the provider.

variables.tf defines the inputs Terraform needs. You provide the values when you run terraform apply.

Show variables.tf

main.tf is the core of Step 1; it defines the virtual registry resource.

What each attribute does:

Attribute
Meaning

identifier

A unique name for this registry within your project. Becomes part of the URL you use with docker pull.

space_ref

Tells Harness which project to create the registry in. Format: account_id/org_id/project_id.

package_type

The type of artifacts this registry holds. DOCKER for Docker images. Other options include HELM, MAVEN, NPM, PYPI, GO, NUGET, and CARGO.

config.type

VIRTUAL means this is a client-facing registry (as opposed to UPSTREAM, which is a proxy).

config.upstream_proxies

A list of upstream proxy identifiers to route through. Empty for now; populated in Step 3.

outputs.tf prints values after Terraform finishes; later steps can reference them.

The import_command_for_step3 output generates a command you need in Step 3. Terraform prints it for you; save it for later.

Now run Terraform. First, set your credentials as environment variables in your terminal.

Replace the placeholder values with your actual account ID, API key, and project scope.

Then initialize and apply:

  • terraform init downloads the Harness provider plugin.

  • terraform validate checks your configuration for syntax errors.

  • terraform apply shows you a plan of what will be created and asks for confirmation.

Type yes when prompted. You should see:

Save the import_command_for_step3 value. You need it in Step 3.

At this point, the virtual registry exists in Harness but cannot resolve external images yet because it has no upstream proxy. That comes next.


Step 2: Create the upstream proxy

The upstream proxy connects to Docker Hub and caches images locally in Harness. This is an independent resource that does not depend on the virtual registry you created in Step 1.

Create a directory called 02-upstream-proxy and add the following five files.

versions.tf is the same as Step 1:

providers.tf is the same as Step 1:

variables.tf adds variables for Docker Hub authentication. By default the proxy uses anonymous access (no Docker Hub account needed). You can optionally enable authenticated access for higher rate limits.

Show variables.tf

The validation blocks are guardrails. If you enable authenticated mode but forget to provide a username or secret, Terraform stops and tells you what is missing instead of creating a broken proxy.

main.tf defines the upstream proxy resource:

What is the dynamic "auth" block? This is a Terraform pattern for conditional configuration. If anonymous mode is on, the auth block is skipped entirely; if it is off, the block includes the Docker Hub credentials. The same configuration file works for both anonymous and authenticated setups. You just flip a variable.

outputs.tf exposes the proxy identifier and URL for later steps:

Set the same environment variables from Step 1 (TF_VAR_harness_account_id, TF_VAR_harness_platform_api_key, TF_VAR_space_ref), then:

For anonymous Docker Hub access (the default), no additional variables are needed. This is fine for pulling public images like nginx, alpine, or redis.

For authenticated Docker Hub access (higher rate limits, private image support), set these additional variables before running terraform apply:

HOW TO SET UP DOCKER HUB CREDENTIALS IN HARNESS

  1. In Docker Hub: go to Account Settings > Security > New Access Token. A read-only token is sufficient.

  2. In Harness: go to your project, then Project Settings > Secrets > + New Secret > Text. Paste the Docker Hub token as the value. Note the secret identifier, which is the value for dockerhub_secret_identifier.

Type yes when prompted. You should see:


Now connect the two pieces. This step updates the virtual registry from Step 1 to route requests through the upstream proxy from Step 2.

Why is this a separate step? The virtual registry already exists (you created it in Step 1). Step 3 needs to take ownership of that existing resource. In Terraform, this is done with terraform import, a command that tells Terraform "this resource already exists in Harness, start managing it from here."

Create a directory called 03-link-virtual and add the following six files.

versions.tf is the same as previous steps:

providers.tf is the same as previous steps:

data.tf reads the output from Step 2's Terraform state, so this step knows the upstream proxy identifier without you having to type it again.

The path ../02-upstream-proxy/terraform.tfstate assumes you kept the directory names from this tutorial. If your layout is different, adjust the path to point to wherever Step 2's state file is located.

variables.tf has the same shape as before; virtual_registry_identifier must match Step 1.

Show variables.tf

main.tf redefines the virtual registry, now with the upstream proxy attached:

The only change from Step 1 is the upstream_proxies line. Instead of an empty list, it now references the proxy identifier from Step 2.

outputs.tf also surfaces the proxies that ended up wired in:

This step requires one extra command compared to the previous steps. Since the virtual registry already exists (created in Step 1), you need to import it so Terraform updates it instead of trying to create a duplicate.

  1. Make sure Step 2 is complete. Its state file must exist:

  2. Set the same environment variables, then initialize:

  3. Import the existing virtual registry. Use the command that Step 1 printed in its output:

    Replace the values with your actual account ID, org, project, and registry identifier.

    If you still have Step 1's terminal open, you can copy the exact command:

    You should see:

  4. Now apply the update:

  5. Type yes. You should see:

    0 added, 1 changed means Terraform recognized the existing registry and updated it to include the upstream proxy.


Verify the setup

All three steps are complete. Verify the end-to-end flow by pulling a Docker image through your new registry.

  1. Log in to the Harness registry with your Docker client:

    You should see Login Succeeded.

  2. Pull an image through the virtual registry:

    PULL PATH FORMAT

    The pull path is pkg.harness.io/<account_id>/<registry_identifier>/<image>:<tag>. It does not include the org or project segments, even though they appear in space_ref. This matches the virtual_registry_url printed in the Terraform outputs. If you also want to copy the exact command Harness recommends, open the registry in the UI and select Setup Client.

    On the first pull, the request flows through the chain: virtual registry, then upstream proxy, then Docker Hub. The image is cached in Harness. Subsequent pulls are served directly from the cache.

  3. Verify in the Harness UI: navigate to Artifact Registry in your project. You should see:

    • my-docker listed as a Virtual registry with dockerhub-proxy shown as its upstream.

    • dockerhub-proxy listed as an Upstream registry connected to Docker Hub.

    • The alpine image visible under the virtual registry's cached artifacts.

If you see all three, your Terraform-provisioned registry is fully operational.


Troubleshooting

`undefined response type` error during `terraform apply` for a Harness Artifact Registry resource

The registry may have been created despite the error. Check the Harness UI; if the registry exists, run `terraform import harness_platform_har_registry.NAME 'account_id/org_id/project_id/registry_identifier'` to adopt it, then `terraform init -upgrade` to pull the latest provider and retry.

`terraform import` for a Harness Artifact Registry fails with 'not found'

The import path must be exactly `account_id/org_id/project_id/registry_identifier` with no leading or trailing slashes and no `https://` prefix. Re-run with the corrected path.

Authentication errors from the Harness Terraform provider when managing Artifact Registry

Use a Next Gen API key with `platform_api_key` (not the legacy `api_key`), confirm the token has permission to manage registries in the target project, and verify `space_ref` matches `account_id/org_id/project_id` exactly.


Clean up

To remove everything, destroy the resources in reverse order:

Type yes at each prompt.


Next steps

You now have a Terraform-managed Docker registry with a Docker Hub upstream proxy. From here, attach more upstream proxies to the same virtual registry, or replicate this pattern for other package types such as Helm, Maven, npm, or PyPI.

Last updated

Was this helpful?