03.Harbor 설치가이드
Harbor v2 설치 및 운영 가이드
- 대상: 단일 Linux 서버에서 Docker Compose 기반으로 Harbor를 설치하는 경우
- 범위: 사설 CA 기반 HTTPS, Trivy 온라인·오프라인 DB 관리, 신뢰 CA 배포, Podman 구성
- 설치 전 Harbor Releases에서 최신 안정 버전과 릴리스 노트를 확인한다.
개요 및 사전 준비
- Harbor는 OCI/Docker 이미지, Helm OCI Chart, SBOM 등 클라우드 네이티브 아티팩트를 저장·배포하는 컨테이너 레지스트리다.
- 프로젝트별 권한, Robot Account, Trivy 취약점 스캔, 이미지 복제, Retention Policy 등의 기능을 제공한다.
- 이 문서는 Docker Compose 기반 단일 노드 설치를 다룬다. 고가용성 구성이 필요하면 Docker Compose 대신 Harbor 공식 Helm Chart로 Kubernetes에 설치한다.
이전 Harbor 1.x 문서와 차이점
| 기존 구성 | 최신 권장 구성 |
|---|---|
| harbor.cfg | harbor.yml |
| Clair | Trivy |
| --with-clair | --with-trivy |
| ChartMuseum | Helm OCI Registry |
| --with-chartmuseum | 사용하지 않음 |
| Docker Compose v1 바이너리 | Docker Compose v2 (docker compose) |
| Docker iptables: false | 설정하지 않음 |
Info
Docker의 "iptables": false 설정은 Docker가 컨테이너 NAT 및 포트 포워딩 규칙을 생성하지 못하게 할 수 있으므로 일반적인 Harbor 환경에서는 사용하지 않는다.
권장 사양
| 항목 | 최소 사양 | 권장 사양 |
|---|---|---|
| CPU | 2 vCPU | 4 vCPU 이상 |
| Memory | 4 GB | 8 GB 이상 |
| Disk | 40 GB | 160 GB 이상 |
| OS | Docker 지원 Linux | Rocky Linux/RHEL/Ubuntu 최신 LTS |
| Docker Engine | 20.10.10-ce 이상 | 최신 안정 버전 |
| Docker Compose | v1.18 이상 | Compose v2 plugin |
-
이미지, PostgreSQL, Redis, 로그, Trivy DB가 누적되므로 운영 환경에서는 OS 루트 파티션과 분리한 데이터 볼륨을 사용한다.
-
Docker와 Compose를 확인한다.
$> docker version $> docker compose version
포트 및 방화벽
| 포트 | 프로토콜 | 용도 |
|---|---|---|
| 80 | HTTP | HTTP 리다이렉션 또는 테스트 |
| 443 | HTTPS | Portal, API, Docker/OCI Registry |
| 9090 | HTTP | Prometheus 메트릭 사용 시 |
설치 진행하기
- 도구 및 디렉터리 준비
$> sudo dnf install -y curl tar openssl ca-certificates # Ubuntu/Debian # sudo apt-get update # sudo apt-get install -y curl tar openssl ca-certificates $> sudo mkdir -p /opt/harbor $> sudo mkdir -p /data/harbor $> sudo mkdir -p /data/cert $> sudo chmod 700 /data/cert
사설 CA 인증서 생성 및 trust-ca 등록 (필요시)
- 운영 환경에서는 HTTPS를 사용해야 한다. 이 절에서는 10년 유효기간의 사설 Root CA를 만들고, 해당 CA로 Harbor 서버 인증서를 발급한다.
Info
사설 CA를 신뢰하지 않는 Docker 또는 Kubernetes 노드에서는 x509: certificate signed by unknown authority 오류가 발생한다. Harbor 서버뿐 아니라 이미지를 Push/Pull하는 모든 노드에 Root CA를 배포해야 한다.
- 환경 변수 설정을 위해 아래 값은 실제 환경에 맞게 변경한다. HARBOR_FQDN은 DNS에서 Harbor IP로 해석되어야 한다.
$> export HARBOR_FQDN="harbor.example.internal" $> export HARBOR_IP="10.10.10.20" $> export CERT_DIR="/data/cert" # 10년 $> export CA_DAYS=3650 $> export SERVER_DAYS=3650 $> sudo mkdir -p "${CERT_DIR}"
Info
공인 CA 인증서는 브라우저 및 CA 정책상 짧은 유효기간이 요구될 수 있다. 여기의 10년 인증서는 사설 PKI 및 내부 신뢰 환경을 전제로 한다.
Info
Root CA의 개인 키인 harbor-root-ca.key는 Harbor 서버에 둘 필요가 없다. 별도의 PKI 서버에서 인증서를 발급했다면 최종 서버 인증서와 서버 개인 키만 /data/cert에 전달한다.
-
10년 Root CA 생성
- Root CA 개인 키는 유출 시 전체 신뢰 체인이 무효화되므로, 가능하면 Harbor 서버와 분리된 보안 저장소 또는 PKI 서버에서 보관한다.
$> cd "${CERT_DIR}" $> sudo openssl genrsa -out harbor-root-ca.key 4096 $> sudo chmod 600 harbor-root-ca.key $> sudo openssl req -x509 -new -nodes \ -key harbor-root-ca.key \ -sha256 \ -days "${CA_DAYS}" \ -out harbor-root-ca.crt \ -subj "/C=KR/ST=Seoul/L=Seoul/O=Example/OU=Platform/CN=Example Harbor Root CA"
- Root CA 개인 키는 유출 시 전체 신뢰 체인이 무효화되므로, 가능하면 Harbor 서버와 분리된 보안 저장소 또는 PKI 서버에서 보관한다.
-
생성 결과를 확인한다.
$> openssl x509 -in harbor-root-ca.crt -noout -subject -issuer -dates -
SAN 포함 Harbor 서버 인증서 생성
- 현대 TLS 클라이언트는 CN만으로 호스트를 검증하지 않고 SAN(Subject Alternative Name) 을 확인한다. Harbor FQDN과 IP를 SAN에 포함한다.
$> cd "${CERT_DIR}" $> sudo openssl genrsa -out "${HARBOR_FQDN}.key" 4096 $> sudo chmod 600 "${HARBOR_FQDN}.key" sudo tee "${HARBOR_FQDN}.cnf" > /dev/null <<EOF [req] distinguished_name = req_distinguished_name req_extensions = v3_req prompt = no [req_distinguished_name] C = KR ST = Seoul L = Seoul O = Example OU = Platform CN = ${HARBOR_FQDN} [v3_req] keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth subjectAltName = @alt_names [alt_names] DNS.1 = ${HARBOR_FQDN} IP.1 = ${HARBOR_IP} EOF $> sudo openssl req -new \ -key "${HARBOR_FQDN}.key" \ -out "${HARBOR_FQDN}.csr" \ -config "${HARBOR_FQDN}.cnf" $> sudo openssl x509 -req \ -in "${HARBOR_FQDN}.csr" \ -CA harbor-root-ca.crt \ -CAkey harbor-root-ca.key \ -CAcreateserial \ -out "${HARBOR_FQDN}.crt" \ -days "${SERVER_DAYS}" \ -sha256 \ -extensions v3_req \ -extfile "${HARBOR_FQDN}.cnf"
- 현대 TLS 클라이언트는 CN만으로 호스트를 검증하지 않고 SAN(Subject Alternative Name) 을 확인한다. Harbor FQDN과 IP를 SAN에 포함한다.
-
인증서의 SAN, 서명 체인, 유효기간을 확인한다.
$> openssl x509 -in "${HARBOR_FQDN}.crt" -noout -text | \ grep -A1 -E "Subject:|Subject Alternative Name" $> openssl verify \ -CAfile harbor-root-ca.crt \ "${HARBOR_FQDN}.crt" -
Harbor 서버 인증서 배치
$> sudo chmod 644 "/data/cert/${HARBOR_FQDN}.crt" $> sudo chmod 600 "/data/cert/${HARBOR_FQDN}.key" -
Rocky Linux/RHEL trust-ca 등록
-
Harbor 서버와 모든 Docker/Podman 클라이언트 노드에 적용한다.
$> sudo cp /data/cert/harbor-root-ca.crt \ /etc/pki/ca-trust/source/anchors/harbor-root-ca.crt $> sudo update-ca-trust extract -
등록 여부를 확인한다.
$> trust list | grep -i -A2 "Example Harbor Root CA" - Ubuntu/Debian trust-ca 등록
$> sudo cp /data/cert/harbor-root-ca.crt \ /usr/local/share/ca-certificates/harbor-root-ca.crt $> sudo update-ca-certificates
-
-
Docker는 시스템 trust store 외에 Registry FQDN별 인증서 경로를 사용하기 때문에 Registry 전용 CA 등록이 필요하다
$> export HARBOR_FQDN="harbor.example.internal" $> sudo mkdir -p "/etc/docker/certs.d/${HARBOR_FQDN}" $> sudo cp /data/cert/harbor-root-ca.crt \ "/etc/docker/certs.d/${HARBOR_FQDN}/ca.crt" $> sudo systemctl restart docker -
Harbor 주소에 포트가 포함되면 디렉터리에도 포트를 넣는다.
/etc/docker/certs.d/harbor.example.internal:8443/ca.crt
Docker 로그인으로 검증한다.
$> docker login "${HARBOR_FQDN}"
- Kubernetes 런타임으로 containerd를 사용하는 경우 containerd Kubernetes 노드 인증서 등록절차가 필요하다
$> export HARBOR_FQDN="harbor.example.internal" $> sudo mkdir -p "/etc/containerd/certs.d/${HARBOR_FQDN}" $> sudo cp /data/cert/harbor-root-ca.crt \ "/etc/containerd/certs.d/${HARBOR_FQDN}/ca.crt" $> sudo tee "/etc/containerd/certs.d/${HARBOR_FQDN}/hosts.toml" > /dev/null <<EOF server = "https://${HARBOR_FQDN}" [host."https://${HARBOR_FQDN}"] capabilities = ["pull", "resolve", "push"] ca = "/etc/containerd/certs.d/${HARBOR_FQDN}/ca.crt" EOF $> sudo systemctl restart containerd
Harbor 설치와 기본 운영
-
Harbor Installer 다운로드 수행
$> export HARBOR_VERSION="v2.14.0" $> cd /opt/harbor $> sudo curl -fLO \ "https://github.com/goharbor/harbor/releases/download/${HARBOR_VERSION}/harbor-online-installer-${HARBOR_VERSION}.tgz" $> sudo tar xzvf "harbor-online-installer-${HARBOR_VERSION}.tgz" $> cd harbor- 폐쇄망 환경에서는 인터넷 연결 구간에서 아래 파일을 내려받아 반입한다.
$> sudo tar xzvf "harbor-offline-installer-${HARBOR_VERSION}.tgz" $> cd harbor
- 폐쇄망 환경에서는 인터넷 연결 구간에서 아래 파일을 내려받아 반입한다.
-
harbor.yml 설정
$> cd /opt/harbor/harbor $> sudo cp harbor.yml.tmpl harbor.yml $> sudo vi harbor.yml -
HTTPS 및 Trivy를 포함한 설정 예시다.
hostname: harbor.example.internal https: port: 443 certificate: /data/cert/harbor.example.internal.crt private_key: /data/cert/harbor.example.internal.key harbor_admin_password: "CHANGE_THIS_ADMIN_PASSWORD" database: password: "CHANGE_THIS_DATABASE_PASSWORD" max_idle_conns: 50 max_open_conns: 100 data_volume: /data/harbor trivy: ignore_unfixed: false security_check: vuln,config,secret skip_update: false insecure: false jobservice: max_job_workers: 10 notification: webhook_job_max_retry: 10 log: level: info local: rotate_count: 50 rotate_size: 200M location: /var/log/harbor metric: enabled: true port: 9090 path: /metrics
- hostname에는 외부에서 접근 가능한 FQDN 또는 IP를 지정한다. localhost, 127.0.0.1, 0.0.0.0은 사용할 수 없다. harbor_admin_password는 최초 설치 시에만 적용된다.
-
Trivy 포함 설치
$> cd /opt/harbor/harbor $> sudo ./install.sh --with-trivy -
설치 상태를 확인한다.
$> docker compose ps $> docker compose logs -f --tail=100 $> docker compose logs -f --tail=100 trivy-adapter -
브라우저에서 접속한다.
https://harbor.example.internal -
초기 로그인 계정은 다음과 같다.
ID: admin Password: harbor.yml의 harbor_admin_password 값
이미지 Push/Pull 테스트
-
먼저 Harbor Portal에서 Projects → + New Project로 이동해 platform 프로젝트를 생성한다.
$> export HARBOR_FQDN="harbor.example.internal" $> docker login "${HARBOR_FQDN}" $> docker tag nginx:1.27 \ "${HARBOR_FQDN}/platform/nginx:1.27" $> docker push "${HARBOR_FQDN}/platform/nginx:1.27" $> docker pull "${HARBOR_FQDN}/platform/nginx:1.27" -
Helm OCI Chart 사용 (ChartMuseum 방식 대신 Helm OCI Registry 방식을 사용한다.)
$> helm registry login harbor.example.internal $> helm package mychart $> helm push mychart-0.1.0.tgz oci://harbor.example.internal/platform $> helm pull oci://harbor.example.internal/platform/mychart --version 0.1.0 -
프로젝트 권한
역할 주요 권한 Limited Guest 이미지 Pull 가능, 프로젝트 정보 접근 제한 Guest 읽기 및 Pull Developer Push 및 Pull Maintainer Developer 권한 및 일부 프로젝트 운영 권한 Project Admin 멤버, 정책, Webhook 등 프로젝트 관리
- CI/CD에는 개인 계정이나 admin 대신 Robot Account를 생성하고 최소 권한을 부여한다.
Trivy 온라인·오프라인 DB 관리
-
Trivy 스캔의 정확도는 취약점 DB 최신성에 의존한다.
데이터 용도 필요 여부 Vulnerability DB OS 및 패키지 CVE 탐지 필수 Java DB JAR 기반 Java 패키지 식별 Java 스캔 시 권장 Checks Bundle IaC 및 설정 오류 검사 config 검사 시 필요 -
온라인 환경 DB 다운로드시 harbor.yml에서 다음을 설정한다.
trivy: skip_update: false security_check: vuln,config,secret Trivy Adapter는 필요한 DB를 자동으로 내려받는다. 프록시 환경이라면 다음을 추가한다. yaml proxy: http_proxy: http://proxy.example.internal:3128 https_proxy: http://proxy.example.internal:3128 no_proxy: 127.0.0.1,localhost,core,registry,harbor.example.internal 방화벽 또는 프록시에서는 다음 통신을 검토한다. text mirror.gcr.io ghcr.io pkg-containers.githubusercontent.com github.com github-releases.githubusercontent.com -
Trivy DB 다운로드 상태는 로그로 확인한다.
$> cd /opt/harbor/harbor $> docker compose logs --tail=300 trivy-adapter | egrep -i "download|update|db|error"
오프라인 환경에서 DB관리
-
인터넷이 되는 중계 서버에서 Trivy CLI로 DB만 미리 내려받을 수 있다.
$> mkdir -p ~/trivy-cache $> trivy image \ --cache-dir ~/trivy-cache \ --download-db-only -
일반적인 핵심 파일 경로는 다음과 같다.
~/trivy-cache/db/trivy.db ~/trivy-cache/db/metadata.json -
Java 아티팩트 스캔이 필요하면 Java DB도 받는다.
$> trivy image \ --cache-dir ~/trivy-cache \ --download-java-db-only -
반입 전에 실제 파일 경로를 확인한다.
$> find ~/trivy-cache -maxdepth 3 -type f
오프라인 환경 DB 다운로드 및 반입
-
폐쇄망 Harbor에서는 자동 다운로드를 비활성화하고, 인터넷 연결 구간에서 취약점 DB를 내려받아 반입한다.
-
인터넷 연결 구간 작업
$> export TRIVY_EXPORT_DIR="$HOME/trivy-offline" $> rm -rf "${TRIVY_EXPORT_DIR}" $> mkdir -p "${TRIVY_EXPORT_DIR}/db" $> trivy image \ --cache-dir "${TRIVY_EXPORT_DIR}" \ --download-db-only # Java JAR 스캔이 필요할 때만 수행 $> trivy image \ --cache-dir "${TRIVY_EXPORT_DIR}" \ --download-java-db-only $> tar czvf "trivy-db-$(date +%F).tar.gz" \ -C "${TRIVY_EXPORT_DIR}" . $> sha256sum "trivy-db-$(date +%F).tar.gz" \ > "trivy-db-$(date +%F).tar.gz.sha256"
폐쇄망 Harbor 서버 작업
-
반입한 tarball의 무결성을 먼저 검증한다.
$> sha256sum -c trivy-db-YYYY-MM-DD.tar.gz.sha256 -
Trivy DB를 저장할 디렉터리를 준비한다.
$> sudo mkdir -p /data/trivy/db $> sudo tar xzvf trivy-db-YYYY-MM-DD.tar.gz -C /data/trivy -
trivy.db와 metadata.json의 실제 경로를 확인한다.
$> sudo find /data/trivy -type f \( -name trivy.db -o -name metadata.json \) -
Harbor Adapter가 읽을 경로로 파일을 배치한다.
$> sudo find /data/trivy -name trivy.db -exec cp -f {} /data/trivy/db/trivy.db \; $> sudo find /data/trivy -name metadata.json -exec cp -f {} /data/trivy/db/metadata.json \;
오프라인 Harbor 설정
-
harbor.yml에서 자동 업데이트를 끈다.
trivy: skip_update: true security_check: vuln,config,secret -
설정 파일을 다시 생성한다.
$> cd /opt/harbor/harbor $> sudo docker compose down -v $> sudo ./prepare --with-trivy -
생성된 docker-compose.yml에서 trivy-adapter 서비스에 read-only 볼륨을 추가한다.
services: trivy-adapter: volumes: - /data/trivy/db:/home/scanner/.cache/trivy/db:ro -
Harbor를 시작한다.
$> sudo docker compose up -d $> sudo docker compose logs -f --tail=200 trivy-adapter
Info
./prepare를 실행하거나 Harbor를 업그레이드하면 docker-compose.yml이 다시 생성될 수 있다. 따라서 오프라인 Trivy DB mount 설정은 Ansible, ```shell Script, GitOps 등 구성 관리 절차에 포함한다.
정기 Trivy 업데이트
-
온라인 환경
- 온라인 환경은 skip_update: false를 유지한다. DB 갱신 실패가 의심되면 Trivy Adapter를 재시작하고 로그를 확인한다.
$> cd /opt/harbor/harbor $> sudo docker compose restart trivy-adapter $> sudo docker compose logs -f --tail=200 trivy-adapter
- 온라인 환경은 skip_update: false를 유지한다. DB 갱신 실패가 의심되면 Trivy Adapter를 재시작하고 로그를 확인한다.
-
새 DB 기준으로 기존 이미지를 다시 평가하려면 Harbor Portal에서 전체 재스캔 일정을 설정한다
Administration → Interrogation Services → Vulnerability → Schedule to scan all작업 권장 주기 Trivy DB 업데이트 자동 또는 매일 전체 이미지 재스캔 매일 또는 매주 Trivy Adapter 로그 점검 매일 취약점 정책 검토 월 1회 또는 릴리스 시 -
오프라인 환경
-
폐쇄망에서는 DB 반입과 이미지 재스캔을 별도로 관리한다.
작업 권장 주기 설명 인터넷 구간 DB 다운로드 매일 신규 CVE 반영 반입 파일 SHA-256 검증 매 반입 무결성 검증 폐쇄망 DB 교체 매일 또는 주 1회 보안 정책에 맞춤 전체 이미지 재스캔 매일 또는 주 1회 새 DB 기반 결과 갱신
-
-
오프라인 DB 갱신 예시다.
$> cd /opt/harbor/harbor
# 1. 반입 파일 검증
$> sha256sum -c trivy-db-YYYY-MM-DD.tar.gz.sha256
# 2. 기존 DB 백업
$> sudo mv /data/trivy/db \
"/data/trivy/db.bak.$(date +%F-%H%M%S)"
$> sudo mkdir -p /data/trivy/db
# 3. 새 DB 압축 해제
$> sudo mkdir -p /data/trivy/import
$> sudo tar xzvf trivy-db-YYYY-MM-DD.tar.gz \
-C /data/trivy/import
# 4. 필수 DB 파일 배치
$> sudo find /data/trivy/import -name trivy.db \
-exec cp -f {} /data/trivy/db/trivy.db \;
$> sudo find /data/trivy/import -name metadata.json \
-exec cp -f {} /data/trivy/db/metadata.json \;
$> sudo rm -rf /data/trivy/import
# 5. 새 DB를 읽도록 Trivy Adapter 재기동
$> sudo docker compose restart trivy-adapter
$> sudo docker compose logs --tail=200 trivy-adapter
Harbor 설치 전 호환성 테스트
-
Harbor 설치 전에 Podman socket과 Compose 연결을 검증한다.
$> export DOCKER_HOST="unix:///run/podman/podman.sock" $> docker version $> docker compose version $> docker compose -f - up -d <<'EOF' services: test: image: docker.io/library/alpine:3.20 command: ["sh", "-c", "sleep 60"] EOF $> docker compose -f - ps $> docker compose -f - down
- 위 테스트가 실패하면 Harbor 설치를 진행하지 않는다. Podman socket 경로, Docker CLI, Compose provider, DOCKER_HOST, SELinux AVC 로그를 먼저 점검한다.