Skip to content

Self-hosted GitLab container registry

Introduction

When we expand homelab infrastructure we often get to the point when custom solutions are necessary. A common example is creating custom Docker images to run containers. To develop images on one machine and then deploy them to others container registry is needed. In this post I will show how to run self-hosted container registry using GitLab.

OS used: Debian 12
Software used: GitLab Community Edition 17.11.1

Source

Initial requirements

For GitLab container registry you need working GitLab instance. To create GitLab instance you can use instruction from post Installing GitLab as a self-hosted git repository.

Configure container registry

To run container registry under the same domain as GitLab instance perform following actions.

In /etc/gitlab/gitlab.rb set registry URL with port and path to TLC certificate:

$ sudo vim /etc/gitlab/gitlab.rb
/etc/gitlab/gitlab.rb
(...)
registry_external_url '<https://your_domain_name:5050>'
(...)
registry_nginx['ssl_certificate'] = "/etc/letsencrypt/live/<your_domain_name>/fullchain.pem"
registry_nginx['ssl_certificate_key'] = "/etc/letsencrypt/live/<your_domain_name>/privkey.pem"
(...)

In lines registry_nginx['ssl_..] you need to enter path to Let's Encrypt TLS certificate and private key obtained using certbot for your GitLab instance.

Reconfigure GitLab:

$ sudo gitlab-ctl reconfigure

Container registry is now active for all projects.

Firewall configuration

Container registry uses port 5050. Open that port on firewall.

Using iptables

Use following command to add rule to iptables:

$ sudo iptables -A INPUT -p tcp --dport 5050 -j ACCEPT && \
  sudo iptables -L -v && \
  sudo netfilter-persistent save

Using nftables

Add following line to your input chain in nftables config file:

$ sudo vim /etc/nftables.conf
/etc/nftables.conf
table inet filter {
        chain input {
            (...)
            tcp dport { 5050 } ct state new accept
            (...)
}

Reload rules after changing /etc/nftables.conf with command:

$ sudo systemctl restart nftables.service

In order to see firewall ruleset and counters statistics use command:

$ sudo nft list ruleset

Path to stored images

When using the GitLab container registry, images are stored in the following path: /var/opt/gitlab/gitlab-rails/shared/registry.

Test of container registry

Create Personal Access Token

First create GitLab Personal Access Token for your user. You will use this token when logging in to the container registry with Docker. In GitLab:

  • Click your avatar (left up corner) > Preferences > Access tokens > click Add new token:
  • set Token name
  • set Expiration date (If you do not enter an expiry date, the expiry date is automatically set to 365 days later than the current date.)
  • check read_registry
  • check write_registry
  • Click Create personal access token

Authenticate to container registry using Docker

Use created Personal Access Token to authenticate to the container registry using command docker login. Use read command to prevent the token from being saved in the terminal history:

$ read -p "Enter token: " token && \
  echo "$token" | docker login <https://your_domain_name.com:5050> -u <username> --password-stdin

Create Docker image

Create Dockerfile in a folder for example /docker-test-image/:

$ mkdir ~/docker-test-image && \
  vim ~/docker-test-image/Dockerfile
~/docker-test-image/Dockerfile
FROM alpine:latest
LABEL description="Test image"

# Install some packages:
RUN apk update --no-cache && \
    apk add bash curl

# Add user:
RUN adduser testuser --disabled-password

USER testuser
WORKDIR /home/testuser

# Run echo command:
ENTRYPOINT [ "echo", "Hello it's alpine-test" ]

Important

Your container images must follow this naming convention: <registry server>/<namespace>/<project>[/<optional path>]

GitLab has two types of namespaces (you will see this name in URL when you are inside repository on GitLab web page):

  • User: Your personal namespace is based on your username.
  • Group: A group or subgroup namespace is based on the group or subgroup name.

Examples:

  • your_domain_name.com/user_name/project_name:tag
  • your_domain_name.com/user_name/project_name/image:tag

Build an image with a tag that contains registry name (remember about adding port 5050 in the registry path):

$ container_name=alpine-test:0.1 && \
  registry_name=your_domain_name.com:5050/user_name/project_name && \
  docker build -t ${registry_name}/${container_name} \
               -f ~/docker-test-image/Dockerfile .

. - dot at the end of above command is important

Check if the image was built:

$ docker images

Push Docker image to container registry

Push Docker image to the container registry:

$ docker push your_domain_name.com:5050/user_name/project_name/alpine-test:0.1

Run container using custom image

Delete image from your host:

$ docker image prune -a && \
  docker images

Run a new container using your custom image from the container registry. First it will pull image from the registry and then run the container using specified image:

$ docker run -it --rm --name alpine-test your_domain_name.com/user_name/project_name/alpine-test:0.1

If the container was pulled and run successfully you will see a message "Hello it's alpine-test" at the end of the command.

If you have access denied:

Delete image from container registry

You can delete image from the container registry using GitLab page:

  • your repository > Deploy > Container registry > Clicking the trash can icon you will delete the image

Deleting the image doesn't immediately free up storage, it's only marked for deletion. To delete unreferenced container images and recover storage space garbage collection must be run.

To run garbage collection you must log in to your GitLab machine and run command:

$ sudo gitlab-ctl registry-garbage-collect -m
  • -m - remove untagged images

Warning

Command sudo gitlab-ctl registry-garbage-collect shuts down the container registry prior to the garbage collection and only starts it again after garbage collection completes.
For more information read this part of GitLab documentation.

Use cron for periodic garbage collection:

$ sudo vim /etc/cron.d/registry-garbage-collect
/etc/cron.d/registry-garbage-collect
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# Run every Sunday at 05:00 in the morning
0 5 * * 0 root gitlab-ctl registry-garbage-collect -m