by Georg Ledermann

Switching from Docker Hub to GitLab Container Registry

Continuous Delivery

Photo by Pankaj Patel on Unsplash

Photo by Pankaj Patel on Unsplash

An automated build process is essential for software development. After every single change in the source code – no matter how small – the software is completely assembled and tested automatically. In my case, the result is usually a Docker image.

So far I’ve used Docker Hub to manage my Docker images. This was quite comfortable but had some disadvantages:

  • It took up to 30 minutes between the commit and the built image. This is because I used Docker Hub not only as a registry (to store the images) but also to build the images: A build was initiated by a webhook at the GitHub repo. Because the CPU resources provided by Docker Hub are limited, the process of building images was very slow.
  • Docker images are quite large and are stored in the USA. The frequent transfer of large files between Germany and the USA causes a lot of traffic.
  • More and more often the building failed because of an Internal server error at Docker Hub. This is very bad when it comes to production deployment.
  • The service is not free, Docker charges a small fee for hosting of private images.

After I recently said goodbye to DockerCloud, I don’t want to use Docker Hub for private repositories anymore. Since I’ve been using GitLab CE for several years for Continuous Integration, it is about time to use the GitLab Container Registry for storing Docker images.

1. Setup GitLab with Container Registry

The GitLab Container Registry was introduced in 2016 with GitLab 8.8. It just needs to be enabled.

I’m using the official Docker image for GitLab CE to run GitLab on my own server behind nginx-proxy with the letsencrypt-nginx-proxy-companion. To install GitLab with the enabled registry, I use the following Docker Compose file (reduced to the relevant parts):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: '3.4'
services:
  web:
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'https://gitlab.example.com'
        registry_external_url 'http://registry.gitlab.example.com'
        nginx['listen_port'] = 80
        nginx['listen_https'] = false
        nginx['proxy_set_headers'] = { 'X-Forwarded-Proto' => 'https', 'X-Forwarded-Ssl' => 'on' }
      VIRTUAL_HOST: gitlab.example.com,registry.gitlab.example.com
      VIRTUAL_PORT: 80
      LETSENCRYPT_EMAIL: info@example.com
      LETSENCRYPT_HOST: gitlab.example.com,registry.gitlab.example.com
      CERT_NAME: gitlab.example.com
    image: 'gitlab/gitlab-ce:latest'
    ports:
      - '22'
      - '80'

Now Gitlab is available with the browser at https://gitlab.example.com, the registry API is available at https://registry.gitlab.example.com. After enabling the registry for a given project, we can push an image to our own registry like this:

1
2
3
4
~ $ docker login registry.gitlab.example.com -u myusername -p mypassword
~ $ cd myrepo
~ $ docker build -t registry.gitlab.example.com/name/repo:latest .
~ $ docker push registry.gitlab.example.com/name/repo:latest

Nice. Next step is automation.

2. Setting up a GitLab Runner

For the GitLab Runner I’m using a small virtual machine at Hetzner. For about five bucks per month, you get the CX21, a virtual Linux host with 2 vCPU, 4GB RAM and 40GB SSD - enough for GitLab Runner. Installing GitLab Runner is quite simple, I’ll skip this here.

3. Configure project

For each project to be tested with GitLab CI, a file .gitlab-ci.yml is required in the root of the repo. It also covers the pushing of the image. The following minimalistic one creates the image, performs the tests and pushes the image to the registry.

1
2
3
4
5
6
7
8
test:
  before_script:
    - echo $CI_JOB_TOKEN | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY

  script:
    - docker build -t $CI_REGISTRY_IMAGE:latest .
    - docker run --rm $CI_REGISTRY_IMAGE:latest bundle exec rake test
    - docker push $CI_REGISTRY_IMAGE:latest

Read on for details about the available CI/CD variables or the configuration of the jobs.

The result

Overall, my build process has improved in several ways:

  • Now it takes less than 10 minutes from commit to the finished Docker image
  • The images are transferred within the Hetzner data center only (fast and without traffic costs)
  • There are no costs for an external registry service