Files
flaskpaste/documentation/harbor-registry.md

9.8 KiB

Harbor Registry Deployment

Overview

This guide covers building and pushing FlaskPaste container images to a Harbor registry, and deploying from Harbor to Kubernetes.

Harbor is an open-source container registry with security features including vulnerability scanning, image signing, and role-based access control.


Prerequisites

  • Harbor registry deployed and accessible
  • Container tools: podman, docker, or ctr (containerd)
  • Network access to Harbor (direct or via SSH tunnel)

Registry Configuration

Harbor Details

Internal:    192.168.122.154:30443 (K8s cluster access)
External:    harbor.mymx.me (CI/CD, remote access)
Project:     library (public)
Repository:  library/flaskpaste

Authentication

Username: <harbor-username>
Password: <harbor-password>

For CI/CD automation, configure Gitea Actions secrets:

  • HARBOR_USER - Harbor username (e.g., ansible automation account)
  • HARBOR_PASS - Harbor password

Building Images

Local Build

# Build with Podman
podman build -t flaskpaste:latest .

# Build with Docker
docker build -t flaskpaste:latest .

# Verify the image
podman images | grep flaskpaste

Multi-architecture Build

# Build for multiple architectures
podman manifest create flaskpaste:latest
podman build --platform linux/amd64 -t flaskpaste:amd64 .
podman build --platform linux/arm64 -t flaskpaste:arm64 .
podman manifest add flaskpaste:latest flaskpaste:amd64
podman manifest add flaskpaste:latest flaskpaste:arm64

Pushing to Harbor

Direct Push (Network Access)

# Login to Harbor (skip TLS verify for self-signed certs)
podman login 192.168.122.154:30443 \
  -u "$HARBOR_USER" -p "$HARBOR_PASS" \
  --tls-verify=false

# Tag for Harbor
podman tag localhost/flaskpaste:latest \
  192.168.122.154:30443/library/flaskpaste:latest

# Push
podman push 192.168.122.154:30443/library/flaskpaste:latest \
  --tls-verify=false

Push via SSH Tunnel

When Harbor is on an internal network:

# Set up SSH port forward
ssh -f -N -L 30443:192.168.122.154:30443 jumphost

# Login and push via localhost
podman login localhost:30443 -u "$HARBOR_USER" -p "$HARBOR_PASS" --tls-verify=false
podman tag localhost/flaskpaste:latest localhost:30443/library/flaskpaste:latest
podman push localhost:30443/library/flaskpaste:latest --tls-verify=false

Push via Image Transfer

When direct push isn't possible:

# Save image to tar
podman save localhost/flaskpaste:latest -o flaskpaste.tar

# Copy to machine with Harbor access
scp flaskpaste.tar user@k8s-master:/tmp/

# On k8s-master: Import and push via containerd
ssh user@k8s-master '
  sudo ctr -n k8s.io images import /tmp/flaskpaste.tar
  sudo ctr -n k8s.io images tag \
    localhost/flaskpaste:latest \
    192.168.122.154:30443/library/flaskpaste:latest
  sudo ctr -n k8s.io images push --skip-verify \
    --user <harbor-username>:<harbor-password> \
    192.168.122.154:30443/library/flaskpaste:latest
'

Pulling from Harbor

Podman/Docker

podman pull 192.168.122.154:30443/library/flaskpaste:latest \
  --tls-verify=false

Containerd (Kubernetes nodes)

sudo ctr -n k8s.io images pull --skip-verify \
  --user <harbor-username>:<harbor-password> \
  192.168.122.154:30443/library/flaskpaste:latest

Kubernetes Deployment

Configure Registry Trust

For nodes to pull from Harbor with self-signed certificates:

containerd (/etc/containerd/config.toml):

[plugins."io.containerd.grpc.v1.cri".registry.configs]
  [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.122.154:30443"]
    [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.122.154:30443".tls]
      insecure_skip_verify = true
    [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.122.154:30443".auth]
      username = "<harbor-username>"
      password = "<harbor-password>"

Restart containerd after changes:

sudo systemctl restart containerd

Create Image Pull Secret

kubectl create secret docker-registry harbor-creds \
  --docker-server=192.168.122.154:30443 \
  --docker-username=<harbor-username> \
  --docker-password=<harbor-password> \
  --docker-email=<your-email>

Deployment Manifest

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flaskpaste
  labels:
    app: flaskpaste
spec:
  replicas: 2
  selector:
    matchLabels:
      app: flaskpaste
  template:
    metadata:
      labels:
        app: flaskpaste
    spec:
      imagePullSecrets:
        - name: harbor-creds
      containers:
        - name: flaskpaste
          image: harbor.mymx.me/library/flaskpaste:latest
          ports:
            - containerPort: 5000
          env:
            - name: FLASK_ENV
              value: "production"
            - name: FLASKPASTE_DB
              value: "/app/data/pastes.db"
          volumeMounts:
            - name: data
              mountPath: /app/data
          livenessProbe:
            httpGet:
              path: /health
              port: 5000
            initialDelaySeconds: 10
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /health
              port: 5000
            initialDelaySeconds: 5
            periodSeconds: 10
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "256Mi"
              cpu: "500m"
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: flaskpaste-data
---
apiVersion: v1
kind: Service
metadata:
  name: flaskpaste
spec:
  selector:
    app: flaskpaste
  ports:
    - port: 80
      targetPort: 5000
  type: ClusterIP
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: flaskpaste-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

Deploy

kubectl apply -f flaskpaste-k8s.yaml

# Verify deployment
kubectl get pods -l app=flaskpaste
kubectl logs -l app=flaskpaste

CI/CD Integration

Gitea Actions Example

name: Build and Push to Harbor

on:
  push:
    branches: [main]
    tags: ['v*']

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        run: |
          git clone --depth 1 --branch "${GITHUB_REF_NAME}" \
            "https://oauth2:${{ github.token }}@${GITHUB_SERVER_URL#https://}/${GITHUB_REPOSITORY}.git" .

      - name: Build image
        run: |
          podman build -t flaskpaste:${{ github.sha }} .
          podman tag flaskpaste:${{ github.sha }} flaskpaste:latest

      - name: Push to Harbor
        env:
          HARBOR_USER: ${{ secrets.HARBOR_USER }}
          HARBOR_PASS: ${{ secrets.HARBOR_PASS }}
        run: |
          podman login 192.168.122.154:30443 \
            -u "$HARBOR_USER" -p "$HARBOR_PASS" --tls-verify=false

          podman tag flaskpaste:${{ github.sha }} \
            192.168.122.154:30443/library/flaskpaste:${{ github.sha }}
          podman tag flaskpaste:latest \
            192.168.122.154:30443/library/flaskpaste:latest

          podman push 192.168.122.154:30443/library/flaskpaste:${{ github.sha }} \
            --tls-verify=false
          podman push 192.168.122.154:30443/library/flaskpaste:latest \
            --tls-verify=false

Harbor API

Check Image Exists

curl -k -s -u <harbor-username>:<harbor-password> \
  "https://192.168.122.154:30443/api/v2.0/projects/library/repositories/flaskpaste/artifacts" \
  | jq '.[] | {digest: .digest, tags: [.tags[].name], size: .size}'

List Tags

curl -k -s -u <harbor-username>:<harbor-password> \
  "https://192.168.122.154:30443/api/v2.0/projects/library/repositories/flaskpaste/artifacts" \
  | jq -r '.[].tags[].name'

Delete Old Tags

# Delete specific tag
curl -k -X DELETE -u <harbor-username>:<harbor-password> \
  "https://192.168.122.154:30443/api/v2.0/projects/library/repositories/flaskpaste/artifacts/v1.0.0"

Troubleshooting

Connection Refused

# Check Harbor is running
kubectl get pods -n harbor

# Check service is exposed
kubectl get svc -n harbor

# Test connectivity
curl -k https://192.168.122.154:30443/api/v2.0/health

Authentication Failed

# Verify credentials via API
curl -k -u <harbor-username>:<harbor-password> \
  https://192.168.122.154:30443/api/v2.0/users/current

# Check if project exists
curl -k -u <harbor-username>:<harbor-password> \
  https://192.168.122.154:30443/api/v2.0/projects

TLS Certificate Errors

# For podman/docker: use --tls-verify=false
# For ctr: use --skip-verify
# For curl: use -k

# To properly trust the cert, export Harbor's CA:
kubectl get secret -n harbor harbor-nginx \
  -o jsonpath='{.data.ca\.crt}' | base64 -d > harbor-ca.crt
sudo cp harbor-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

Image Pull Failures in Kubernetes

# Check secret exists
kubectl get secret harbor-creds -o yaml

# Verify pod can reach Harbor
kubectl run test --rm -it --image=alpine -- \
  wget -qO- --no-check-certificate https://192.168.122.154:30443/api/v2.0/health

# Check events
kubectl describe pod <pod-name> | grep -A10 Events

Version Tagging Strategy

latest          - Most recent build from main branch
v1.2.3          - Semantic version releases
sha-abc123      - Git commit SHA (for traceability)
main-20260117   - Branch + date (for nightly builds)

Example workflow:

VERSION="1.0.0"
COMMIT=$(git rev-parse --short HEAD)

podman build -t flaskpaste .
podman tag flaskpaste 192.168.122.154:30443/library/flaskpaste:v${VERSION}
podman tag flaskpaste 192.168.122.154:30443/library/flaskpaste:sha-${COMMIT}
podman tag flaskpaste 192.168.122.154:30443/library/flaskpaste:latest

for tag in v${VERSION} sha-${COMMIT} latest; do
  podman push 192.168.122.154:30443/library/flaskpaste:${tag} --tls-verify=false
done