---
title: "Harbor Registry Setup Guide"
description: "Step-by-step guide to deploying Harbor as a self-hosted Docker registry on a Proxmox LXC. Covers installation, NPM reverse proxy config, Gitea Actions integration, vulnerability scanning, backup strategy, and troubleshooting."
type: guide
domain: server-configs
tags: [harbor, docker-registry, proxmox, lxc, gitea-actions, vulnerability-scanning, docker]
---

# Harbor Docker Registry Setup Guide

Complete guide to setting up Harbor on a Proxmox LXC for self-hosted Docker registry.

## Prerequisites

- Proxmox LXC with Ubuntu 22.04
- 2 CPU cores, 4GB RAM, 50GB disk
- Docker and docker-compose installed
- Domain name (e.g., registry.manticorum.com)

## Quick Setup

### 1. Create LXC Container

```bash
# On Proxmox host
pct create 227 local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst \
  --hostname harbor \
  --cores 2 \
  --memory 4096 \
  --swap 512 \
  --net0 name=eth0,bridge=vmbr0,ip=10.10.0.227/24,gw=10.10.0.1 \
  --rootfs local-lvm:50 \
  --unprivileged 1 \
  --features nesting=1 \
  --onboot 1 \
  --start 1
```

### 2. Install Docker

```bash
ssh root@10.10.0.227

apt update && apt install -y curl
curl -fsSL https://get.docker.com | sh
systemctl enable docker
```

### 3. Download Harbor

```bash
cd /opt
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
```

### 4. Configure Harbor

```bash
cp harbor.yml.tmpl harbor.yml

# Edit harbor.yml
nano harbor.yml
```

**Key settings to change:**
```yaml
hostname: registry.manticorum.com  # Your domain

# HTTPS (configure after NPM setup, start with HTTP for now)
# https:
#   port: 443
#   certificate: /path/to/cert
#   private_key: /path/to/key

# Or disable HTTPS initially
# Comment out entire https section

harbor_admin_password: YourSecurePassword123

database:
  password: YourDBPassword123

data_volume: /mnt/harbor-data
```

### 5. Install Harbor

```bash
./install.sh
```

### 6. Access Harbor

Open: `http://10.10.0.227` (or `http://registry.manticorum.com` if DNS configured)

**Default login:**
- Username: `admin`
- Password: `YourSecurePassword123` (what you set)

### 7. Configure NPM Reverse Proxy

In Nginx Proxy Manager (10.10.0.16):

**Proxy Host:**
- Domain: `registry.manticorum.com`
- Scheme: `http`
- Forward Hostname: `10.10.0.227`
- Forward Port: `80`
- Websockets: ✅ Enabled
- Block Common Exploits: ✅ Enabled
- SSL: Let's Encrypt

**Custom Nginx Configuration:**
```nginx
# Increase timeouts for large image uploads
proxy_read_timeout 900;
proxy_send_timeout 900;
client_max_body_size 0;  # No upload limit

# Required for Docker registry
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
```

## Using Your Registry

### 1. Login from Dev Machine

```bash
docker login registry.manticorum.com
# Username: admin
# Password: YourSecurePassword123
```

### 2. Tag and Push Image

```bash
# Tag existing image
docker tag manticorum67/paper-dynasty:latest registry.manticorum.com/paper-dynasty/bot:latest

# Push to your registry
docker push registry.manticorum.com/paper-dynasty/bot:latest
```

### 3. Pull from Production

```bash
# On sba-bots
docker login registry.manticorum.com
docker pull registry.manticorum.com/paper-dynasty/bot:latest
```

### 4. Update docker-compose

```yaml
services:
  paper-dynasty:
    # Old: image: manticorum67/paper-dynasty:latest
    # New:
    image: registry.manticorum.com/paper-dynasty/bot:latest
```

## Integrating with Gitea Actions

Update your workflow to push to both registries:

```yaml
- name: Login to Docker Hub
  uses: docker/login-action@v3
  with:
    username: ${{ secrets.DOCKERHUB_USERNAME }}
    password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Login to Harbor
  uses: docker/login-action@v3
  with:
    registry: registry.manticorum.com
    username: ${{ secrets.HARBOR_USERNAME }}
    password: ${{ secrets.HARBOR_PASSWORD }}

- name: Build and push
  uses: docker/build-push-action@v5
  with:
    context: .
    push: ${{ github.ref == 'refs/heads/main' }}
    tags: |
      manticorum67/paper-dynasty:latest
      manticorum67/paper-dynasty:v${{ steps.meta.outputs.version }}
      registry.manticorum.com/paper-dynasty/bot:latest
      registry.manticorum.com/paper-dynasty/bot:v${{ steps.meta.outputs.version }}
```

## Harbor Features

### Create Projects

1. Login to Harbor UI
2. Click **New Project**
3. Name: `paper-dynasty`
4. Access Level: Private or Public

### Enable Vulnerability Scanning

1. Go to **Administration** → **Interrogation Services**
2. Enable **Trivy** scanner
3. Set scan on push: ✅ Enabled

Now images are auto-scanned for CVEs!

### Set Up Replication

Replicate between Harbor and Docker Hub:

1. **Administration** → **Replications**
2. **New Replication Rule**
   - Name: `sync-to-dockerhub`
   - Source: Local
   - Destination: Docker Hub (add endpoint first)
   - Trigger: Event Based

### Garbage Collection

Free up disk space from deleted images:

1. **Administration** → **Garbage Collection**
2. Schedule: Daily at 2 AM
3. Dry run first to see what would be deleted

## Backup Strategy

### What to Backup

1. **Harbor database** (PostgreSQL)
2. **Image storage** (`/mnt/harbor-data`)
3. **Configuration** (`/opt/harbor/harbor.yml`)

### Backup Script

```bash
#!/bin/bash
BACKUP_DIR="/mnt/backups/harbor"
DATE=$(date +%Y%m%d)

# Stop Harbor
cd /opt/harbor
docker-compose down

# Backup database
docker exec harbor-db pg_dumpall -U postgres > $BACKUP_DIR/harbor-db-$DATE.sql

# Backup data (incremental)
rsync -av /mnt/harbor-data/ $BACKUP_DIR/harbor-data/

# Backup config
cp /opt/harbor/harbor.yml $BACKUP_DIR/harbor-config-$DATE.yml

# Start Harbor
docker-compose up -d

# Keep last 7 days
find $BACKUP_DIR -name "harbor-db-*.sql" -mtime +7 -delete
```

## Monitoring

### Check Harbor Status

```bash
cd /opt/harbor
docker-compose ps

# View logs
docker-compose logs -f
```

### Disk Usage

```bash
du -sh /mnt/harbor-data

# By project
du -sh /mnt/harbor-data/docker/registry/v2/repositories/*
```

### API Health Check

```bash
curl -k https://registry.manticorum.com/api/v2.0/health
```

## Troubleshooting

### "401 Unauthorized" on push

**Problem:** Docker login not working

**Solution:**
```bash
# Clear old credentials
rm ~/.docker/config.json

# Login again
docker login registry.manticorum.com
```

### "413 Request Entity Too Large"

**Problem:** Nginx upload limit

**Solution:** Add to NPM custom config:
```nginx
client_max_body_size 0;
```

### Disk space full

**Problem:** Old images filling disk

**Solution:**
```bash
# Run garbage collection
cd /opt/harbor
docker-compose exec core /harbor/garbage-collection.sh

# Or via UI: Administration → Garbage Collection → Run Now
```

### Can't pull from registry

**Problem:** Firewall or network issue

**Solution:**
```bash
# Test connection
telnet 10.10.0.227 80

# Check Harbor logs
docker-compose logs registry
```

## Advanced: High Availability

For production-critical registries, set up HA:

1. Multiple Harbor instances
2. Shared storage (NFS, S3, Minio)
3. Load balancer in front
4. Database replication

## Cost Analysis

**LXC Resources:**
- CPU: 2 cores = $0 (spare capacity)
- RAM: 4GB = $0 (spare capacity)
- Disk: 50GB = $0 (local storage)
- Bandwidth: Internal = $0

**Total ongoing cost: $0/month**

**Docker Hub Pro alternative: $5/month**

**Time investment:**
- Setup: 2-3 hours
- Maintenance: 30 min/month
- Break-even: 3 months of learning value

## Next Steps

1. ✅ Set up Harbor on LXC 227
2. ✅ Configure NPM reverse proxy
3. ✅ Test push/pull from dev machine
4. ✅ Update one project to use Harbor
5. ✅ Set up Gitea Actions to push to both registries
6. ✅ Configure vulnerability scanning
7. ✅ Set up automated backups

---

**Created:** 2026-02-04
**For:** Manticorum Home Lab
**Reference:** Paper Dynasty as first use case
