Deploying Harbor Satellite with SPIFFE/SPIRE: A Practical Guide

This is a practical walkthrough to deploy Harbor Satellite with SPIFFE/SPIRE and validate offline image pulls.

What you will achieve

  • Run Central Harbor as source registry
  • Start Ground Control and Edge Satellite
  • Sync nginx:alpine to Satellite cache
  • Prove local image pulls still work when central services are down

Step 1: Set up Central Harbor Registry

# Download Harbor
wget https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-offline-installer-v2.10.0.tgz
tar xzvf harbor-offline-installer-v2.10.0.tgz
cd harbor

# Configure for your machine
nano harbor.yml
# Change: hostname = YOUR_IP_ADDRESS
# For local testing only, disable HTTPS block if needed

# Install
sudo ./install.sh
# Expected: Harbor services start successfully

Step 2: Start Ground Control

cd deploy/quickstart/spiffe/join-token/external/gc
HARBOR_URL=http://<YOUR_HARBOR_IP>:80 ./setup.sh
# Expected: Ground Control is running and connected to Harbor

Step 3: Start the Edge Satellite

cd ../sat
./setup.sh
# Expected: Satellite starts and authenticates using SPIFFE/SPIRE

Step 4: Verify everything is running

# Ground Control health
curl -k https://localhost:9080/health
# Expected: ok

# Edge Satellite registry endpoint
curl -i http://localhost:5050/v2/
# Expected: HTTP/1.1 200 OK

Step 5: Tell Satellite to cache images

# 1) Login and get auth token
TOKEN=$(curl -sk -X POST "https://localhost:9080/login" \
  -d '{"username":"admin","password":"<HARBOR_PASSWORD>"}' | \
  grep -o '"token":"[^"]*"' | cut -d'"' -f4)

# 2) Get nginx:alpine digest from Harbor
DIGEST=$(curl -sk -u "admin:<HARBOR_PASSWORD>" \
  "http://<YOUR_HARBOR_IP>/api/v2.0/projects/library/repositories/nginx/artifacts?q=tags%3Dalpine&page_size=1" | \
  grep -m1 '"digest":' | cut -d'"' -f4)
# 3) Create edge group and sync policy
curl -sk -X POST "https://localhost:9080/api/groups/sync" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${TOKEN}" \
  -d "{
    \"group\": \"edge-group\",
    \"registry\": \"http://<YOUR_HARBOR_IP>:80\",
    \"artifacts\": [{\"repository\": \"library/nginx\", \"tag\": [\"alpine\"], \"type\": \"image\", \"digest\": \"${DIGEST}\"}]
  }"

# 4) Assign Satellite to group
curl -sk -X POST "https://localhost:9080/api/groups/satellite" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${TOKEN}" \
  -d '{"satellite":"edge-01","group":"edge-group"}'
# Expected: after ~30-45s, image is cached on Satellite
# Verify cache catalog
curl -s http://localhost:5050/v2/_catalog
# Expected: {"repositories":["library/nginx"]}

Testing without internet (air-gap validation)

Step 1: Clear local cache

docker rmi localhost:5050/library/nginx:alpine
# If image does not exist locally, Docker prints an error and continues

Step 2: Stop central services

docker stop harbor ground-control
# Expected: central side is unavailable

Step 3: Pull from local offline cache

docker pull localhost:5050/library/nginx:alpine
# Expected: pull still succeeds from local Satellite cache