Skip to content

This is the Experimental version (Latest). It is under active development and may change. For the most reliable documentation, use the version selector in the top-right to switch to Stable, or click here to go to the Stable version's homepage.

Harbor Configurations

Last updated:

You will be store Container Images for Opstella here.

  1. Go to ${HARBOR_DOMAIN} and Login with Initial Admin Account.

  2. Go to Projects menu > + New Project

  3. Create opstella-platform Project

Create Harbor Projects for CI/CD Pipelines Container Image Mirrored/Proxy Caches Repository

Section titled “Create Harbor Projects for CI/CD Pipelines Container Image Mirrored/Proxy Caches Repository”

To ease on CI/CD Pipelines Runtime which is container-based. (GitLab Runner with Kubernetes Executor + Opstella Standard GitLab CI Pipelines)

Directly pull from source may exhaust your network bandwidth/utilisation and the availability to access the registry.

Mirrored/Proxy Caches on Harbor Projects are created to act as a domestic storage for container images that frequently used by CI/CD Pipelines without extensively access the source registry.

  1. Go to Administration menu > Registries

  2. Click + New Endpoint and populate these infomation.

    • Registry: Docker Hub
      • Provider: Docker Hub
      • Name: docker.io
      • Endpoint URL: https://hub.docker.com

    • Registry: gcr.io
      • Provider: Docker Registry
      • Name: gcr.io
      • Endpoint URL: https://gcr.io

    • Registry: quay.io
      • Provider: Docker Registry
      • Name: quay.io
      • Endpoint URL: https://quay.io

    • Registry: ghcr.io
      • Provider: Docker Registry
      • Name: ghcr.io
      • Endpoint URL: https://ghcr.io

  3. You should have registries recorded like this.

  4. Go to Projects menu > + New Project

  5. Create Proxy Cache Projects

    Create Each Project for each of registries that you previously create them.

    • Registry: Docker Hub

      • Project Name: mirror-docker.io
      • Tick Proxy Cache, choose docker.io-https://hub.docker.com
    • Registry: gcr.io

      • Project Name: mirror-gcr.io
      • Tick Proxy Cache, choose gcr.io-https://gcr.io
    • Registry: quay.io

      • Project Name: mirror-quay.io
      • Tick Proxy Cache, choose quay.io-https://quay.io
    • Registry: ghcr.io

      • Project Name: mirror-ghcr.io
      • Tick Proxy Cache, choose ghcr.io-https://ghcr.io
  6. Proxy Cache Projects should look like this.

Create Robot Account for Opstella Deployment

Section titled “Create Robot Account for Opstella Deployment”

This will be a credentials used to push/pull while installing Opstella Container Images.

  1. Go to Administration menu > Robot Accounts

  2. Create New Robot Account + New Robot Account

    Basic Information

    • Name: opstella-deploy
    • Description: Deploy Credentials for Opstella
    • Expiration time: Never

  3. Click Next to skip Assigning System Permissions

  4. Assign opstella-platform Project Permissions

    • Tick on opstella-platform Project

    • Assign Pull, Push Repository Permissions

  5. Keep the token SAFE as you will be using it while install Opstella in the later step

Create Robot Account for Opstella-managed GitLab Runner Access

Section titled “Create Robot Account for Opstella-managed GitLab Runner Access”

This will be a credentials used to pull Artifacts for Opstella-managed GitLab Runners.

  1. Go to Administration menu > Robot Accounts

  2. Create New Robot Account + New Robot Account

    Basic Information

    • Name: opstella-gitlab-runners
    • Description: Access for Opstella-managed GitLab Runners
    • Expiration time: Never

  3. Click Next to skip Assigning System Permissions

  4. Assign Projects Permissions

    • Tick on Projects
      • library
      • mirror-docker.io
      • mirror-gcr.io
      • mirror-quay.io
      • mirror-ghcr.io

    • Assign Pull Repository Permission with Reset All Project Permissions and Click Finish

  5. Keep the token SAFE as you will be using it while setup Opstella-managed GitLab Runners Deployment Template in the later step

A healthy registry endpoint only proves Harbor can reach the source — it does not prove a proxy cache can serve an image, and nothing on the screen proves a Robot Account got the permissions you intended. Check both against the API:

Terminal window
H="https://${HARBOR_DOMAIN}"
source $BASE_WORKING_DIR/shell-values/tools/harbor-robots.vars.sh
R="-u ${HARBOR_ROBOT_GITLAB_RUNNERS_NAME}:${HARBOR_ROBOT_GITLAB_RUNNERS_SECRET}"
D="-u ${HARBOR_ROBOT_OPSTELLA_DEPLOY_NAME}:${HARBOR_ROBOT_OPSTELLA_DEPLOY_SECRET}"
# 1. every proxy cache really pulls through — expect 200
curl -s -o /dev/null -w '%{http_code}\n' $R $H/v2/mirror-docker.io/library/alpine/manifests/3.20
curl -s -o /dev/null -w '%{http_code}\n' $R $H/v2/mirror-quay.io/prometheus/busybox/manifests/latest
curl -s -o /dev/null -w '%{http_code}\n' $R $H/v2/mirror-ghcr.io/cloudnative-pg/postgresql/manifests/15
curl -s -o /dev/null -w '%{http_code}\n' $R $H/v2/mirror-gcr.io/distroless/static/manifests/latest
# 2. the deploy Robot can talk to the registry — expect 200
curl -s -o /dev/null -w '%{http_code}\n' $D $H/v2/
# 3. least privilege holds: the deploy Robot must NOT reach a mirror — expect 401
curl -s -o /dev/null -w '%{http_code}\n' $D $H/v2/mirror-docker.io/library/alpine/manifests/3.20

Alternative: configure with the Harbor REST API

Section titled “Alternative: configure with the Harbor REST API”

Every step above can be done with the API instead of the UI, which is the only option when the Harbor Ingress is not reachable yet, on an air-gapped site, or when the configuration has to be reproducible.

Terminal window
source $BASE_WORKING_DIR/shell-values/tools/harbor.vars.sh
H="https://${HARBOR_DOMAIN}"
A="-u ${HARBOR_ADMIN_USERNAME}:${HARBOR_ADMIN_PASSWORD}"
J="-H Content-Type:application/json"
# 1. registry endpoints (ping first — Harbor itself must reach the source)
curl -s $A $J -X POST $H/api/v2.0/registries/ping \
-d '{"type":"docker-hub","url":"https://hub.docker.com"}' -o /dev/null -w '%{http_code}\n'
curl -s $A $J -X POST $H/api/v2.0/registries \
-d '{"name":"docker.io","type":"docker-hub","url":"https://hub.docker.com"}'
for r in gcr.io quay.io ghcr.io; do
curl -s $A $J -X POST $H/api/v2.0/registries \
-d "{\"name\":\"$r\",\"type\":\"docker-registry\",\"url\":\"https://$r\"}"
done
curl -s $A $H/api/v2.0/registries # note the id of each endpoint for the next step
# 2. projects — the plain one, then one proxy cache per endpoint id
curl -s $A $J -X POST $H/api/v2.0/projects \
-d '{"project_name":"opstella-platform","public":false}'
curl -s $A $J -X POST $H/api/v2.0/projects \
-d '{"project_name":"mirror-docker.io","public":false,"registry_id":1}'
# repeat for mirror-gcr.io / mirror-quay.io / mirror-ghcr.io with their own registry_id
# 3. robot accounts ("duration": -1 means never expires)
curl -s $A $J -X POST $H/api/v2.0/robots -d '{
"name":"opstella-deploy","level":"system","duration":-1,
"permissions":[{"kind":"project","namespace":"opstella-platform","access":[
{"resource":"repository","action":"pull"},{"resource":"repository","action":"push"}]}]}'
curl -s $A $J -X POST $H/api/v2.0/robots -d '{
"name":"opstella-gitlab-runners","level":"system","duration":-1,
"permissions":[
{"kind":"project","namespace":"library","access":[{"resource":"repository","action":"pull"}]},
{"kind":"project","namespace":"mirror-docker.io","access":[{"resource":"repository","action":"pull"}]},
{"kind":"project","namespace":"mirror-gcr.io","access":[{"resource":"repository","action":"pull"}]},
{"kind":"project","namespace":"mirror-quay.io","access":[{"resource":"repository","action":"pull"}]},
{"kind":"project","namespace":"mirror-ghcr.io","access":[{"resource":"repository","action":"pull"}]}]}'

The POST /api/v2.0/robots response is the only place the secret appears — store it right away, as described above.

Push Opstella Container Images to Private Registry (Air-gapped Installation)

Section titled “Push Opstella Container Images to Private Registry (Air-gapped Installation)”

Before proceeding, ensure you have your Google Service Account Key (JSON) on the Bastion Host.

Define the required environment variables:

Terminal window
# Ensure HARBOR_DOMAIN, OPSTELLA_SOURCE_REGISTRY, OPSTELLA_VERSION and
# OPSTELLA_UI_IMAGE_TAG are loaded from variables
export SOURCE_REGISTRY="${OPSTELLA_SOURCE_REGISTRY}"
export DEST_REGISTRY="${HARBOR_DOMAIN}/opstella-platform"
export VERSION="${OPSTELLA_VERSION}"
# Opstella UI is built per customer and carries its own tag
export UI_TAG="${OPSTELLA_UI_IMAGE_TAG}"
  1. Login to Registries

    1.1 Login to Google Artifact Registry (Source) Use the JSON key file you downloaded earlier.

    Terminal window
    # Replace path/to/key.json with your actual file path
    cat path/to/key.json | docker login -u _json_key --password-stdin https://asia-southeast1-docker.pkg.dev

    1.2 Login to Local Harbor Registry (Destination) Use your previously created Robot Account.

    Terminal window
    ## For security reasons turn off bash history momentarily
    set +o history
    # Ensure REGISTRY_USERNAME and REGISTRY_PASSWORD are loaded from variables
    docker login $HARBOR_DOMAIN \
    --username "$REGISTRY_USERNAME" \
    --password "$REGISTRY_PASSWORD"
    set -o history
  2. Pull, Tag, and Push Images

    We will pull images from the Google Cloud source, re-tag them for your local Harbor, and push them.

    2.1 Core Services (UI, Backend, Utilities)

    Terminal window
    # Opstella UI carries a per-customer tag, the rest follow VERSION
    docker pull "${SOURCE_REGISTRY}/ui:${UI_TAG}"
    docker tag "${SOURCE_REGISTRY}/ui:${UI_TAG}" "${DEST_REGISTRY}/ui:${UI_TAG}"
    docker push "${DEST_REGISTRY}/ui:${UI_TAG}"
    # List of core services
    CORE_SERVICES=("core" "clear-session")
    for SERVICE in "${CORE_SERVICES[@]}"; do
    echo "Processing ${SERVICE}..."
    # Pull
    docker pull "${SOURCE_REGISTRY}/${SERVICE}:${VERSION}"
    # Tag
    docker tag "${SOURCE_REGISTRY}/${SERVICE}:${VERSION}" "${DEST_REGISTRY}/${SERVICE}:${VERSION}"
    # Push
    docker push "${DEST_REGISTRY}/${SERVICE}:${VERSION}"
    done

    2.2 Opstella Workers

    Push the specific worker images required for your installation.

    #!/bin/bash
    # Define enabled instruments
    export OPSTELLA_ENABLED_INSTRUMENTS=(keycloak kubernetes argocd-nonprod argocd-prod defectdojo gitlab headlamp-nonprod headlamp-prod harbor sonarqube vault grafana loki tempo)
    for KEY in "${OPSTELLA_ENABLED_INSTRUMENTS[@]}"
    do
    # Normalize image names (remove suffixes like -prod/-nonprod)
    if echo "$KEY" | grep -q "^argocd"; then
    export IMAGE_NAME="argocd"
    elif echo "$KEY" | grep -q "^headlamp"; then
    export IMAGE_NAME="headlamp"
    elif echo "$KEY" | grep -q "^kubernetes"; then
    export IMAGE_NAME="kubernetes"
    else
    export IMAGE_NAME="$KEY"
    fi
    SOURCE_IMAGE="${SOURCE_REGISTRY}/worker-${IMAGE_NAME}:${VERSION}"
    DEST_IMAGE="${DEST_REGISTRY}/worker-${IMAGE_NAME}:${VERSION}"
    echo "Processing Worker: ${IMAGE_NAME}..."
    # Pull
    docker pull "$SOURCE_IMAGE"
    # Tag
    docker tag "$SOURCE_IMAGE" "$DEST_IMAGE"
    # Push
    docker push "$DEST_IMAGE"
    done

Same result as the pull/tag/push loops above, without a container runtime and without writing layers to local disk.

Terminal window
# Login to both sides (nothing is echoed)
gcloud auth print-access-token \
| crane auth login ${ARTIFACT_REGISTRY_DOMAIN} -u oauth2accesstoken --password-stdin
# ...or with the JSON key, matching the docker login above:
# crane auth login ${ARTIFACT_REGISTRY_DOMAIN} -u _json_key --password-stdin < path/to/key.json
source $BASE_WORKING_DIR/shell-values/tools/harbor-robots.vars.sh
printf '%s' "$HARBOR_ROBOT_OPSTELLA_DEPLOY_SECRET" \
| crane auth login ${HARBOR_DOMAIN} -u "$HARBOR_ROBOT_OPSTELLA_DEPLOY_NAME" --password-stdin
# Copy every image, keeping the UI on its own tag
crane copy "${SOURCE_REGISTRY}/ui:${OPSTELLA_UI_IMAGE_TAG}" "${DEST_REGISTRY}/ui:${OPSTELLA_UI_IMAGE_TAG}"
for IMAGE in core clear-session; do
crane copy "${SOURCE_REGISTRY}/${IMAGE}:${VERSION}" "${DEST_REGISTRY}/${IMAGE}:${VERSION}"
done
for KEY in "${OPSTELLA_ENABLED_INSTRUMENTS[@]}"; do
IMAGE_NAME=$(echo "$KEY" | sed -E 's/-(non)?prod$//')
crane copy "${SOURCE_REGISTRY}/worker-${IMAGE_NAME}:${VERSION}" \
"${DEST_REGISTRY}/worker-${IMAGE_NAME}:${VERSION}"
done
Terminal window
# 1. same content on both sides
crane digest "${SOURCE_REGISTRY}/core:${VERSION}"
crane digest "${DEST_REGISTRY}/core:${VERSION}" # must be identical
# 2. the Robot Account that Kubernetes will use can read it
source $BASE_WORKING_DIR/shell-values/tools/harbor-robots.vars.sh
curl -s -o /dev/null -w '%{http_code}\n' \
-u "${HARBOR_ROBOT_OPSTELLA_DEPLOY_NAME}:${HARBOR_ROBOT_OPSTELLA_DEPLOY_SECRET}" \
"https://${HARBOR_DOMAIN}/v2/opstella-platform/core/manifests/${VERSION}" # 200
# 3. a node can actually pull it — this is the check that catches node-side DNS
kubectl create secret docker-registry harbor-pull-test \
--docker-server="${HARBOR_DOMAIN}" \
--docker-username="${HARBOR_ROBOT_OPSTELLA_DEPLOY_NAME}" \
--docker-password="${HARBOR_ROBOT_OPSTELLA_DEPLOY_SECRET}"
kubectl run harbor-pull-test --restart=Never \
--image="${HARBOR_DOMAIN}/opstella-platform/core:${VERSION}" \
--overrides='{"spec":{"imagePullSecrets":[{"name":"harbor-pull-test"}]}}' \
--command -- sleep 30
kubectl get pod harbor-pull-test -w # must leave ContainerCreating, not ImagePullBackOff
kubectl delete pod harbor-pull-test; kubectl delete secret harbor-pull-test

Steps 1 and 2 pass from the Bastion Host even when no node can pull the image. Do not skip step 3.

Finished?

Use the below navigation to proceed