How to a Setup Private Docker Registry on Rocky Linux 8
If you are working for an organization and want to keep your docker images in-house for quick deployment, then hosting a private Docker repository is perfect. Having a private docker registry allows you to own your images distribution pipeline and have tighter control over image storage and distribution. You can integrate your registry with your CI/CD system improving your workflow.
This tutorial will teach you how to set up and use a private Docker registry on a Rocky Linux 8 based server using Amazon S3 as a storage location.
Prerequisites
- Two Linux servers with Rocky Linux 8. One server will act as the registry host, while the other one will be used as a client to send requests and receive images from the host.
- A registered domain name pointing to the host server. We will be using
registry.example.com
for our tutorial. - A non-root user with sudo privileges on both machines.
Step 1 - Configure Firewall
The first step is to configure the firewall. Rocky Linux uses Firewalld Firewall. Check the firewall's status.
$ sudo firewall-cmd --state running
The firewall works with different zones, and the public zone is the default one that we will use. List all the services and ports active on the firewall.
$ sudo firewall-cmd --permanent --list-services
It should show the following output.
cockpit dhcpv6-client ssh
Allow HTTP and HTTPS ports.
$ sudo firewall-cmd --permanent --add-service=http $ sudo firewall-cmd --permanent --add-service=https
Recheck the status of the firewall.
$ sudo firewall-cmd --permanent --list-services
You should see a similar output.
cockpit dhcpv6-client http https ssh
Reload the firewall to enable the changes.
$ sudo firewall-cmd --reload
Step 2 - Install Docker and Docker Compose
This step is required on both the server and the client machines.
Install the official Docker repository.
$ sudo dnf install yum-utils $ sudo yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo
Install Docker.
$ sudo dnf install docker-ce docker-ce-cli containerd.io
Enable and run the Docker daemon.
$ sudo systemctl enable docker --now
Add your system user to the Docker group to avoid using sudo
to run Docker commands.
$ sudo usermod -aG docker $(whoami)
Login again to your server after logging out to enable the change.
Download and install the latest stable release of Docker Compose.
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
Apply executable permissions to the downloaded binary file.
$ sudo chmod +x /usr/local/bin/docker-compose
Install the Docker-compose Bash Completion script.
$ sudo curl \ -L https://raw.githubusercontent.com/docker/compose/1.29.2/contrib/completion/bash/docker-compose \ -o /etc/bash_completion.d/docker-compose
Reload your profile settings to make the bash-completion work.
$ source ~/.bashrc
Step 3 - Configure Docker Registry
Create user directories
Create a directory for the registry configuration.
$ mkdir ~/docker-registry
Switch to the docker-registry
directory.
$ cd ~/docker-registry
Create a directory to store the HTTP authentication password, Nginx configuration files, and the SSL certificates.
$ mkdir auth
Create another directory to store Nginx logs.
$ mkdir logs
Create Amazon S3 Bucket
You can store the registry data and the images on your server or use a cloud hosting service. For our tutorial, we will be using the Amazon S3 cloud service.
The next step is to set up the configuration file with a few important settings. These settings can also be defined in the docker-compose.yml
file, but having a separate file is much better.
Create a bucket with the following settings.
- ACL should be disabled.
- Public access to the bucket should be disabled.
- Bucket versioning should be disabled.
- Enable Bucket encryption using Amazon S3 managed keys. (SSE-S3)
- Object lock should be disabled.
Create an IAM user with the following policy.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:ListBucket", "s3:GetBucketLocation", "s3:ListBucketMultipartUploads" ], "Resource": "arn:aws:s3:::S3_BUCKET_NAME" }, { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:ListMultipartUploadParts", "s3:AbortMultipartUpload" ], "Resource": "arn:aws:s3:::S3_BUCKET_NAME/*" } ] }
Replace the S3_BUCKET_NAME
with the name of your S3 bucket.
Note down the secret key, secret value, and the bucket region of your bucket to be used later.
Create Docker Compose File
Create the docker-compose.yml
file and open it for editing.
$ nano docker-compose.yml
Paste the following code in it.
version: '3.3' services: registry: image: registry:2 restart: always environment: - REGISTRY_STORAGE=s3 - REGISTRY_STORAGE_S3_REGION=us-west-2 - REGISTRY_STORAGE_S3_BUCKET=hf-docker-registry - REGISTRY_STORAGE_S3_ENCRYPT=true - REGISTRY_STORAGE_S3_CHUNKSIZE=5242880 - REGISTRY_STORAGE_S3_SECURE=true - REGISTRY_STORAGE_S3_ACCESSKEY=AKIA3FIG4NVFCJ6STMUA - REGISTRY_STORAGE_S3_SECRETKEY=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 - REGISTRY_STORAGE_S3_V4AUTH=true - REGISTRY_STORAGE_S3_ROOTDIRECTORY=/image-registry - REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR=inmemory - REGISTRY_HEALTH_STORAGEDRIVER_ENABLED=false nginx: image: "nginx:alpine" ports: - 443:443 links: - registry:registry volumes: - ./auth:/etc/nginx/conf.d - ./auth/nginx.conf:/etc/nginx/nginx.conf:ro - ./logs:/var/log/nginx - /etc/letsencrypt:/etc/letsencrypt
Save the file by pressing Ctrl + X and entering Y when prompted.
Let us go through what we have set up in our compose file.
-
The first step is to grab the latest image of version 2 of the Docker registry from the hub. We are not using the latest tag because it may cause problems in the case of a major version upgrade. Setting it to 2 allows you to grab all 2.x updates while preventing being auto-upgraded to the next major version, which can introduce breaking changes.
-
The registry container is set to restart always in the case of a failure or an unexpected shutdown.
-
We have set various environment variables for Amazon S3 storage. Let us go through them quickly.
- REGISTRY_STORAGE sets the type of storage. We have selected s3 since we are using Amazon S3.
- REGISTRY_STORAGE_S3_REGION sets the region of your S3 bucket.
- REGISTRY_STORAGE_S3_BUCKET sets the name of your S3 bucket.
- REGISTRY_STORAGE_S3_ENCRYPT - set it to true if you have enabled Bucket encryption.
- REGISTRY_STORAGE_S3_CHUNKSIZE sets the size of upload chunks. It should be larger than 5MB (5 * 1024 * 1024).
- REGISTRY_STORAGE_S3_SECURE - set it to true if you are going to use HTTPS.
- REGISTRY_STORAGE_S3_ACCESSKEY and REGISTRY_STORAGE_S3_SECRETKEY - User credentials you grabbed after creating your IAM user.
- REGISTRY_STORAGE_S3_V4AUTH - set it to true if you use v4 of AWS authentication. If you are getting errors relating to S3 login, set it to false.
- REGISTRY_STORAGE_S3_ROOTDIRECTORY - sets the root directory in your bucket under which your registry data will be stored.
- REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR - sets the location for the Cache. In our case, we are storing it in memory. You can also set it to use Redis.
- REGISTRY_HEALTH_STORAGEDRIVER_ENABLED - Set it to false to disable the Registry's storage health check service. There is a bug with the Registry which can cause issues if you don't set it to false.
-
Docker registry communicates via port 5000, which is what we have exposed in our server to the docker.
-
./auth:/etc/nginx/conf.d
mapping ensures that all Nginx's settings are available in the container. -
./auth/nginx.conf:/etc/nginx/nginx.conf:ro
maps the Nginx settings file from the system to one in the container in read-only mode. -
./logs:/var/log/nginx
allows access to the Nginx's logs on the system by mapping to the Nginx logs directory in the container. -
Docker registry's settings are stored in the
/etc/docker/registry/config.yml
file in the container, and we have mapped it to theconfig.yml
file in the current directory, which we will create in the next step.
Set up Authentication
To set up the HTTP authentication, you need to install the httpd-tools
package.
$ sudo dnf install httpd-tools
Create the password file in the ~/docker-registry/auth
directory.
$ htpasswd -Bc ~/docker-registry/auth/nginx.htpasswd user1 New password: Re-type new password: Adding password for user user1
The -c
flag instructs the command to create a new file, and the -B
flag is to use the bcrypt algorithm supported by Docker. Replace user1
with a username of your choice.
If you want to add more users, run the command again, but without the -c
flag.
$ htpasswd -B ~/docker-registry/auth/registry.password user2
Now, the file will be mapped to the registry container for authentication.
Step 4 - Install SSL
To install an SSL certificate using Let's Encrypt, we need to download the Certbot tool, which is available from the Epel repository.
Install EPEL repository and Certbot.
$ sudo dnf install epel-release $ sudo dnf install certbot
Generate an SSL certificate.
$ sudo certbot certonly --standalone --agree-tos --no-eff-email --staple-ocsp --preferred-challenges http -m [email protected] -d registry.example.com
The above command will download a certificate to the /etc/letsencrypt/live/registry.example.com
directory on your server.
Generate a Diffie-Hellman group certificate.
$ sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
Test the renewal of the certificate.
$ sudo certbot renew --dry-run
If the dry run succeeds, it means your certificates will be automatically renewed.
Copy the Dhparam file to the container
Copy the Diffie-Hellman group certificate to the ~/docker-registry/auth
directory, which will be mapped to the container.
$ sudo cp /etc/ssl/certs/dhparam.pem ~/docker-registry/auth
Step 5 - Configure Nginx
The next step involves configuring the Nginx server as a front-end proxy for the Docker registry server. Docker registry comes with an in-built server operating at port 5000. We will put it behind Nginx.
Create and open the file ~/docker-registry/auth/nginx.conf
for editing.
$ sudo nano ~/docker-registry/auth/nginx.conf
Paste the following code in it.
events { worker_connections 1024; } http { upstream docker-registry { server registry:5000; } ## Set a variable to help us decide if we need to add the ## 'Docker-Distribution-Api-Version' header. ## The registry always sets this header. ## In the case of nginx performing auth, the header is unset ## since nginx is auth-ing before proxying. map $upstream_http_docker_distribution_api_version $docker_distribution_api_version { '' 'registry/2.0'; } server { listen 443 ssl http2; server_name registry.example.com; # SSL ssl_certificate /etc/letsencrypt/live/registry.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/registry.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/registry.example.com/chain.pem; access_log /var/log/nginx/registry.access.log; error_log /var/log/nginx/registry.error.log; # Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers on; ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1; ssl_session_cache shared:SSL:10m; ssl_dhparam /etc/nginx.d/conf.d/dhparam.pem; resolver 8.8.8.8; # disable any limits to avoid HTTP 413 for large image uploads client_max_body_size 0; # required to avoid HTTP 411: see Issue #1486 (https://github.com/moby/moby/issues/1486) chunked_transfer_encoding on; location /v2/ { # Do not allow connections from docker 1.5 and earlier # docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) { return 404; } # To add basic authentication to v2 use auth_basic setting. auth_basic "Registry realm"; auth_basic_user_file /etc/nginx/conf.d/nginx.htpasswd; ## If $docker_distribution_api_version is empty, the header is not added. ## See the map directive above where this variable is defined. add_header 'Docker-Distribution-Api-Version' $docker_distribution_api_version always; proxy_pass http://docker-registry; proxy_set_header Host $http_host; # required for docker client's sake proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 900; } } }
Save the file by pressing Ctrl + X and entering Y when prompted once finished.
Configure SELinux to allow network connections for the Private Docker Registry.
$ sudo setsebool -P httpd_can_network_connect on
Step 6 - Launch Docker Registry
Switch to the Docker Registry's directory.
$ cd ~/docker-registry
Launch the docker container.
$ docker-compose up -d
Check the status of the containers.
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 88d6addc1687 nginx:alpine "/docker-entrypoint.…" 5 minutes ago Up 5 minutes 80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp docker-registry_nginx_1 2b112edc1c72 registry:2 "/entrypoint.sh /etc…" 5 minutes ago Up 5 minutes 5000/tcp docker-registry_registry_1
Log in to the Docker registry.
$ docker login -u=testuser -p=testpassword https://registry.example.com
You can also open the URL https://registry.example.com/v2/
in your browser, and it will ask for a username and password. You should see an empty page with {} on it.
You can check the URL on the terminal using curl
.
$ curl -u testuser -X GET https://registry.nspeaks.xyz/v2/ Enter host password for user 'testuser': {}
Download the latest Ubuntu docker image.
$ docker pull ubuntu:latest
Tag this image for the private registry.
$ docker tag ubuntu:latest registry.example.com/ubuntu2004
Push the image to the registry.
$ docker push registry.example.com/ubuntu2004
Test whether the push has been successful.
$ curl -u testuser -X GET https://registry.nspeaks.xyz/v2/_catalog Enter host password for user 'testuser': {"repositories":["ubuntu2004"]}
Enter your Nginx authentication password when prompted, and you will see the list of repositories available via the registry.
Check the list of Docker images currently available for use.
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE registry 2 d3241e050fc9 5 days ago 24.2MB nginx alpine 53722defe627 5 days ago 23.4MB httpd 2 118b6abfbf55 5 days ago 144MB ubuntu latest ff0fea8310f3 2 weeks ago 72.8MB registry.nspeaks.xyz/ubuntu2004 latest ff0fea8310f3 2 weeks ago 72.8MB
Step 7 - Access and Use Docker registry from the Client Machine
Login to your client-server. In step 1, we installed Docker on the client machine.
Login to the private Docker registry from the client machine.
$ docker login -u=testuser -p=testpassword https://registry.example.com
Pull the Ubuntu image from the registry.
$ docker pull registry.example.com/ubuntu2004
List all the images on your client machine.
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE registry.nspeaks.xyz/ubuntu2004 latest ff0fea8310f3 2 weeks ago 72.8MB
Create and launch a container using the downloaded image.
$ docker run -it registry.example.com/ubuntu2004 /bin/bash
You will be logged in to the Shell inside the Ubuntu container.
root@a2da49fdbea9:
Run the following command to check the Linux version.
root@a2da49fdbea9$ cat /etc/os-release NAME="Ubuntu" VERSION="20.04.4 LTS (Focal Fossa)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 20.04.4 LTS" VERSION_ID="20.04" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=focal UBUNTU_CODENAME=focal
Now, you can start using your Docker registry from your client machines.
Conclusion
This concludes our tutorial on setting up a private Docker registry on a Rocky Linux 8 based server that uses Amazon S3 as storage. If you have any questions, post them in the comments below.