How to setup Harbor registry using Ansible playbook

How to setup Harbor registry using Ansible playbook

harbor_installation_setup

Here in this article we will try to install and configure Harbor registry using Ansible playbook.

Test Environment

Fedora 41 server
Docker version 27.3.1
Docker Compose version v2.29.7

What is Harbor

Harbor is an open source registry that secures artifacts with policies and role-based access control, ensures images are scanned and free from vulnerabilities, and signs images as trusted. Harbor, a CNCF Graduated project, delivers compliance, performance, and interoperability to help you consistently and securely manage artifacts across cloud native compute platforms like Kubernetes and Docker.

Here is the project structure for harbor setup.

admin@fedser:learnansible$ tree harbour/
harbour/
├── inventory
│   ├── hosts
│   └── host_vars
│       └── linuxser.stack.com.yml
├── linux_setup_harbour.yml
├── README.md
└── roles
    ├── linux_download_harbour
    │   └── tasks
    │       └── main.yml
    ├── linux_expose_harbour
    │   ├── defaults
    │   │   └── main.yml
    │   └── tasks
    │       └── main.yml
    ├── linux_install_harbour
    │   ├── tasks
    │   │   └── main.yml
    │   └── templates
    │       └── harbor.yml.j2
    ├── linux_ping
    │   └── tasks
    │       └── main.yml
    ├── linux_start_harbour
    │   └── tasks
    │       └── main.yml
    └── linux_stop_harbour
        └── tasks
            └── main.yml

Also, here are the global variables set in the host_vars folder that we will be using in the ansible roles.

admin@fedser:harbour$ cat inventory/host_vars/linuxser.stack.com.yml 
---
home_dir: /home/admin/stack
artifact_dir: /home/admin/stack/artifacts
extract_dir: /home/admin/stack/extracts
harbour_repo: https://github.com/goharbor/harbor/releases/download/v2.12.0
harbour_package: "harbor-offline-installer-v2.12.0.tgz"

Here is the inventory file with the node “linuxser.stack.com” on which the harbor setup is going to be carried out.

admin@fedser:harbour$ cat inventory/hosts 
[harbour]
linuxser.stack.com

Procedure

Step1: Download Harbor package

As a first step we are going to download and extract the “Harbor offline installer” package. Here is the ansible role “linux_download_harbour” for the same.

admin@fedser:harbour$ cat roles/linux_download_harbour/tasks/main.yml 
---
- name: ensure home directory exists
  file:
    path: "{{ home_dir }}"
    state: directory
    mode: '0755'
    owner: admin
    group: admin

- name: ensure artifacts directory exists
  file:
    path: "{{ artifact_dir }}"
    state: directory
    mode: '0755'
    owner: admin
    group: admin

- name: download harbour_package
  get_url:
    url: "{{ harbour_repo }}/{{ harbour_package }}"
    dest: "{{ artifact_dir }}/{{ harbour_package }}"
    mode: '0440'
    owner: admin
    group: admin

- name: extract harbour_package
  unarchive:
    src: "{{ artifact_dir }}/{{ harbour_package }}"
    dest: "{{ home_dir }}"
    remote_src: yes

- name: ensure home directory permissions updated
  file:
    path: "{{ home_dir }}"
    state: directory
    mode: '0755'
    owner: admin
    group: admin
    recurse: true

Step2: Install harbor package

Once the harbor package has been download and extracted. Next we will need to install the harbor package. Here we are going to setup harbor to listen on https port.

For https setup we will need to generate server certificate key pair. We are using “sscg” package to generate a self signed certificate key pair along with the ca certificate.

Once the certificates are generated we are required to copy and make them available for harbor and docker as shown below. We need to convert linuxser.stack.com.crt to linuxser.stack.com.cert, for use by Docker. Please restart the docker service once necessary cert files have been copied.

Next we need to configure harbor using the “harbor.yml” file. When you extract the harbor installer package, you will get a “harbor.yml.tmpl” which we can update to configure harbor. In this file i am updating the hostname to my FQDN and the https section provide the location of server certificate and key file. Every other setting is kept the default. For clarity i have removed all the other default except for the ones that i have changed.

Now we are reading to install harbor with the updated configuration. Here we are installing harbor with trivy enabled so we can use it to scan images.

habor.yml template

admin@fedser:harbour$ cat roles/linux_install_harbour/templates/harbor.yml.j2 
# Configuration file of Harbor

# The IP address or hostname to access admin UI and registry service.
# DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
hostname: {{ ansible_host }}

# http related config
http:
  # port for http, default is 80. If https enabled, this port will redirect to https port
  port: 80

# https related config
https:
  # https port for harbor, default is 443
  port: 443
  # The path of cert and key files for nginx
  certificate: {{ home_dir }}/certs/{{ ansible_host }}.crt
  private_key: {{ home_dir }}/certs/{{ ansible_host }}.key
  # enable strong ssl ciphers (default: false)
  # strong_ssl_ciphers: false
...

linux_install_harbour role

admin@fedser:harbour$ cat roles/linux_install_harbour/tasks/main.yml 
---
- name: ensure sscg present
  dnf:
    name: sscg
    state: present 

- name: ensure certs directory exists
  file:
    path: "{{ home_dir }}/certs"
    state: directory
    mode: '0700'
    owner: admin
    group: admin
    recurse: true

- name: check if cert file exists
  stat:
    path: "{{ home_dir }}/certs/{{ ansible_host }}.crt"
  register: result

- name: generate self signed certificate
  shell: "cd {{ home_dir }}/certs; sscg --cert-file={{ ansible_host }}.crt --cert-key-file={{ ansible_host }}.key --ca-file=ca.crt --ca-key-file=ca.key"
  when: not result.stat.exists

- name: convert crt file to cert
  shell: "openssl x509 -inform PEM -in {{ home_dir }}/certs/{{ ansible_host }}.crt -out {{ home_dir }}/certs/{{ ansible_host }}.cert"

- name: ensure certs directory permissions updated
  file:
    path: "{{ home_dir }}/certs"
    state: directory
    mode: '0700'
    owner: admin
    group: admin
    recurse: yes

- name: ensure certs directory exists in docker
  file:
    path: "/etc/docker/certs.d/{{ ansible_host }}"
    state: directory
    mode: '0700'

- name: copy server cert to docker certs directory
  copy:
    src: "{{ home_dir }}/certs/{{ ansible_host }}.cert"
    dest: "/etc/docker/certs.d/{{ ansible_host }}"
    remote_src: yes

- name: copy server key to docker certs directory
  copy:
    src: "{{ home_dir }}/certs/{{ ansible_host }}.key"
    dest: "/etc/docker/certs.d/{{ ansible_host }}"
    remote_src: yes

- name: copy ca cert to docker certs directory
  copy:
    src: "{{ home_dir }}/certs/ca.crt"
    dest: "/etc/docker/certs.d/{{ ansible_host }}"
    remote_src: yes

- name: "ensure docker service restarted"
  service:
    name: docker
    state: restarted

- name: configure harbor.yml
  template:
    src: "harbor.yml.j2"
    dest: "{{ home_dir }}/harbor/harbor.yml"
    owner: admin
    group: admin
    mode: '0755'

- name: execute harbour installation script
  shell: "{{ home_dir }}/harbor/install.sh --with-trivy"

Step3: Expose harbor service ports

Here we are going to expose the following ports at the linux firewall level.

PortProtocolDescription
443HTTPSHarbor portal and core API accept HTTPS requests on this port. You can change this port in the configuration file.
4443HTTPSConnections to the Docker Content Trust service for Harbor. You can change this port in the configuration file.
80HTTPHarbor portal and core API accept HTTP requests on this port. You can change this port in the configuration file.

linux_expose_harbour role

admin@fedser:harbour$ cat roles/linux_expose_harbour/defaults/main.yml 
---
harbor_http_port: "80"
harbor_https_port: "443"
harbor_trustservice_port: "4443"

linux_expose_harbour role

admin@fedser:harbour$ cat roles/linux_expose_harbour/tasks/main.yml 
---
- name: expose http port
  firewalld:
    port: "{{harbor_http_port}}/tcp"
    permanent: true
    immediate: true
    state: enabled

- name: expose https port
  firewalld:
    port: "{{harbor_https_port}}/tcp"
    permanent: true
    immediate: true
    state: enabled

- name: expose trustservice port
  firewalld:
    port: "{{harbor_trustservice_port}}/tcp"
    permanent: true
    immediate: true
    state: enabled

- name: restart firewalld service
  service:
    name: firewalld
    state: restarted

Step4: Start Harbor service

We need to ensure that the docker service is up and running. Once the docker serivce is started we can start our harbor using the docker compose command as shown below.

linux_start_harbour role

admin@fedser:harbour$ cat roles/linux_start_harbour/tasks/main.yml 
---
- name: "ensure docker service restarted"
  service:
    name: docker
    state: started

- name: ensure harbor service is started
  shell: "cd {{ home_dir }}/harbor; docker compose up -d"

Step5: Stop Harbor service

Here the role to stop docker and harbor service as shown below.

admin@fedser:harbour$ cat roles/linux_stop_harbour/tasks/main.yml 
---
- name: "ensure docker service stopped"
  service:
    name: docker
    state: stopped

- name: ensure harbor service is stopped
  shell: "cd {{ home_dir }}/harbor; docker compose down -v"

Step6: Executing the Playbook

Here is the complete playbook.

admin@fedser:harbour$ cat linux_setup_harbour.yml 
---
- hosts: "harbour"
  serial: 1
  become: true
  become_user: root
  roles:
  - { role: "linux_ping", tags: "linux_ping" }
  - { role: "linux_download_harbour", tags: "linux_download_harbour" }
  - { role: "linux_install_harbour", tags: "linux_install_harbour" }
  - { role: "linux_expose_harbour", tags: "linux_expose_harbour" }
  - { role: "linux_stop_harbour", tags: "linux_stop_harbour" }
  - { role: "linux_start_harbour", tags: "linux_start_harbour" }

Here are the instructions for executing each role individually.

admin@fedser:harbour$ cat README.md 
# Instructions for execution

ansible-playbook linux_setup_harbour.yml -i inventory/hosts --tags "linux_ping" -v
ansible-playbook linux_setup_harbour.yml -i inventory/hosts --tags "linux_download_harbour" -v 
ansible-playbook linux_setup_harbour.yml -i inventory/hosts --tags "linux_install_harbour" -v
ansible-playbook linux_setup_harbour.yml -i inventory/hosts --tags "linux_expose_harbour" -v
ansible-playbook linux_setup_harbour.yml -i inventory/hosts --tags "linux_stop_harbour" -v
ansible-playbook linux_setup_harbour.yml -i inventory/hosts --tags "linux_start_harbour" -v

Let us comment out “linux_stop_harbour” role execution in “linux_setup_harbour.yml” and execute the playbook.

admin@fedser:harbour$ ansible-playbook linux_setup_harbour.yml -i inventory/hosts

PLAY [harbour] *************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************
ok: [linuxser.stack.com]

TASK [linux_ping : ansible ping pong validation] ***************************************************************************************
ok: [linuxser.stack.com]

TASK [linux_download_harbour : ensure package home directory exists] *******************************************************************
ok: [linuxser.stack.com]

TASK [linux_download_harbour : ensure artifacts directory exists] **********************************************************************
ok: [linuxser.stack.com]

TASK [linux_download_harbour : ensure extracts directory exists] ***********************************************************************
ok: [linuxser.stack.com]

TASK [linux_download_harbour : download harbour_package] *******************************************************************************
ok: [linuxser.stack.com]

TASK [linux_download_harbour : extract harbour_package] ********************************************************************************
changed: [linuxser.stack.com]

TASK [linux_download_harbour : ensure home directory permissions updated] **************************************************************
changed: [linuxser.stack.com]

TASK [linux_install_harbour : ensure sscg present] *************************************************************************************
ok: [linuxser.stack.com]

TASK [linux_install_harbour : ensure certs directory exists] ***************************************************************************
changed: [linuxser.stack.com]

TASK [linux_install_harbour : check if cert file exists] *******************************************************************************
ok: [linuxser.stack.com]

TASK [linux_install_harbour : generate self signed certificate] ************************************************************************
skipping: [linuxser.stack.com]

TASK [linux_install_harbour : convert crt file to cert] ********************************************************************************
changed: [linuxser.stack.com]

TASK [linux_install_harbour : ensure certs directory permissions updated] **************************************************************
ok: [linuxser.stack.com]

TASK [linux_install_harbour : ensure certs directory exists in docker] *****************************************************************
ok: [linuxser.stack.com]

TASK [linux_install_harbour : copy server cert to docker certs directory] **************************************************************
ok: [linuxser.stack.com]

TASK [linux_install_harbour : copy server key to docker certs directory] ***************************************************************
ok: [linuxser.stack.com]

TASK [linux_install_harbour : copy ca cert to docker certs directory] ******************************************************************
ok: [linuxser.stack.com]

TASK [linux_install_harbour : ensure docker service restarted] *************************************************************************
changed: [linuxser.stack.com]

TASK [linux_install_harbour : configure harbor.yml] ************************************************************************************
ok: [linuxser.stack.com]

TASK [linux_install_harbour : execute harbour installation script] *********************************************************************
changed: [linuxser.stack.com]

TASK [linux_expose_harbour : expose http port] *****************************************************************************************
ok: [linuxser.stack.com]

TASK [linux_expose_harbour : expose https port] ****************************************************************************************
ok: [linuxser.stack.com]

TASK [linux_expose_harbour : expose trustservice port] *********************************************************************************
ok: [linuxser.stack.com]

TASK [linux_expose_harbour : restart firewalld service] ********************************************************************************
changed: [linuxser.stack.com]

TASK [linux_start_harbour : ensure docker service restarted] ***************************************************************************
ok: [linuxser.stack.com]

TASK [linux_start_harbour : ensure harbor service is started] **************************************************************************
changed: [linuxser.stack.com]

PLAY RECAP *****************************************************************************************************************************
linuxser.stack.com         : ok=26   changed=8    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

Step7: Validate Harbor service

Once the playbook execution is completed. The installation will instantiate multiple docker container to provide the harbor service as shown below on the harbor host.

admin@linuxser:~$ docker ps
CONTAINER ID   IMAGE                                   COMMAND                  CREATED          STATUS                    PORTS                                                                                NAMES
4adeb4bf60af   goharbor/harbor-jobservice:v2.12.0      "/harbor/entrypoint.…"   56 seconds ago   Up 54 seconds (healthy)                                                                                        harbor-jobservice
a2a13a37a650   goharbor/nginx-photon:v2.12.0           "nginx -g 'daemon of…"   56 seconds ago   Up 54 seconds (healthy)   0.0.0.0:80->8080/tcp, [::]:80->8080/tcp, 0.0.0.0:443->8443/tcp, [::]:443->8443/tcp   nginx
36a9ebb4c0f7   goharbor/trivy-adapter-photon:v2.12.0   "/home/scanner/entry…"   56 seconds ago   Up 55 seconds (healthy)                                                                                        trivy-adapter
7e95714f5770   goharbor/harbor-core:v2.12.0            "/harbor/entrypoint.…"   56 seconds ago   Up 55 seconds (healthy)                                                                                        harbor-core
371b84a71497   goharbor/redis-photon:v2.12.0           "redis-server /etc/r…"   56 seconds ago   Up 55 seconds (healthy)                                                                                        redis
286825e1b892   goharbor/harbor-portal:v2.12.0          "nginx -g 'daemon of…"   56 seconds ago   Up 55 seconds (healthy)                                                                                        harbor-portal
850fc847fad1   goharbor/harbor-db:v2.12.0              "/docker-entrypoint.…"   56 seconds ago   Up 55 seconds (healthy)                                                                                        harbor-db
c3bc7a29cdef   goharbor/registry-photon:v2.12.0        "/home/harbor/entryp…"   56 seconds ago   Up 55 seconds (healthy)                                                                                        registry
80ac07979004   goharbor/harbor-registryctl:v2.12.0     "/home/harbor/start.…"   56 seconds ago   Up 55 seconds (healthy)                                                                                        registryctl
862d9f3914ba   goharbor/harbor-log:v2.12.0             "/bin/sh -c /usr/loc…"   56 seconds ago   Up 55 seconds (healthy)   127.0.0.1:1514->10514/tcp

Now let us try to login to the registry using the docker utility as shown below.

admin@linuxser:~/stack/harbor$ docker login linuxser.stack.com
Username: admin
Password: 
WARNING! Your password will be stored unencrypted in /home/admin/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credential-stores

Login Succeeded

You can also launch the harbor portal using the following https url.

URL - https://linuxser.stack.com/
username - admin
password - Harbor12345

NOTE: We are using the default credentials which needs to be updated or changed in production environment.

Hope you enjoyed reading this article. Thank you..