Using OpenSSL and Docker CLI to Create and Deploy a Privately Hosted Docker Container Image Registry
Creating a Private Docker Registry
This article describes how you can use OpenSSL and Docker CLI to create and deploy a privately hosted Docker container image registry.
For certain use cases, for example, container deployment on an air-gapped system, it can be useful to create a private Docker registry.
You can do this by using the following instructions for Ubuntu 22.04 or Red Hat Enterprise Linux (RHEL) 9.
Entering Commands
The commands shown in this article should be entered from a root user shell.
Prerequisites
Docker is installed on the system that will host your Docker private registry and also installed on any system that will access the registry.
On an Ubuntu system:
apt -y install docker.io
On a RHEL system:
dnf config-manager \
--add-repo=https://download.docker.com/linux/centos/docker-ce.repo
dnf -y install docker-ce && systemctl enable --now docker.service
Populating Hostname and IP Address Variables
To make these instructions more generic and to decrease the likelihood of user input errors, populate three shell variables with the IP address, hostname, and port number that will reference your private Docker registry. Replace the values in the example below with values particular to your system.
REGISTRYIPADDR=192.168.221.20
REGISTRYHOST=my.private-registry.lan
REGISTRYPORT=5001
The hostname and IP address that you set the REGISTRYHOST and REGISTRYIPADDR variables to should match a hostname and IP address pair that is set in the system's /etc/hosts file. Any system that needs to access your private Docker registry should also have a matching hostname and IP address pair for the registry host in its /etc/hosts file, or be able to lookup the hostname by using a DNS service.
If the hostname and IP address are not already in your /etc/hosts file, you can add them by entering the following command:
echo $REGISTRYIPADDR $REGISTRYHOST >> /etc/hosts
Preparing a Certificate and Key pair for Your Private Registry
To use the default HTTPS access method with a private Docker registry that you will set up, you will need to have a certificate and its corresponding private key.
Creating a Self-Signed Certificate
Create the certificate and private key by using the following commands. The OpenSSL command below uses the Ed25519 algorithm to generate a private key and certificate pair. Your operations team can choose a different cryptographic algorithm depending on any requirements it might have.
Creating an OpenSSL Configuration File
You can create an OpenSSL configuration file to supply parameter values for generating the certificate and private key. Replace the example values below with values particular to your environment. There are many more parameters and values that you can specify in the configuration file. The parameter values below are just examples.
cat << EOF > ./$REGISTRYHOST.cnf
[req]
distinguished_name = req_distinguished_name
prompt = no
x509_extensions = v3_ca
[ req_distinguished_name ]
countryName = US
stateOrProvinceName = Michigan
localityName = Detroit
organizationName = Private Company, Ltd
commonName = $REGISTRYHOST
[ v3_ca ]
subjectAltName = DNS:$REGISTRYHOST,IP:$REGISTRYIPADDR
basicConstraints = CA:true
EOF
Generating a Certificate and Private Key Pair
After creating an OpenSSL configuration file, you can use it to generate a certificate and private key pair that you will use to authenticate with your private Docker registry.
mkdir -p /certs && \
openssl req -newkey ed25519 \
-x509 \
-nodes \
-days 365 \
-config ./$REGISTRYHOST.cnf \
-keyout /certs/$REGISTRYHOST.key \
-out /certs/$REGISTRYHOST.crt
Copying the Certificate to Your Docker Certificates Directory
Create a directory within the Docker configuration folder to put the certificate into, by entering the following commands:
mkdir -p /etc/docker/certs.d/$REGISTRYHOST:$REGISTRYPORT && \
cp /certs/$REGISTRYHOST.crt /etc/docker/certs.d/$REGISTRYHOST:$REGISTRYPORT/
For recent versions of Docker, such as would be found in Ubuntu 22.04 and RHEL 9 packages, you can add a new certificate to your Docker instance without needing to restart the Docker service.
Copying the Signing Certificate to Your Local Certificates Folder
The certificate that you generated previously is self-signed. For a system to trust the certificate, the certificate needs to be placed in your system's trusted certificates directory. On an Ubuntu system, you will also need to edit the CA certificates configuration and add an entry for the location of the added certificate.
On an Ubuntu system:
mkdir -p /usr/share/ca-certificates/$REGISTRYHOST && \
cp /certs/$REGISTRYHOST.crt /usr/share/ca-certificates/$REGISTRYHOST/
echo $REGISTRYHOST/$REGISTRYHOST.crt >> /etc/ca-certificates.conf
On a RHEL system:
trust anchor --store /certs/$REGISTRYHOST.crt
The above command should automatically place the certificate where it needs to be. However, if you have issues running the command, you can manually copy the certificate to the correct directory for system-wide trust, by entering the following command.
cp /certs/$REGISTRYHOST.crt /etc/pki/ca-trust/source/anchors/
Updating Your Certificates
After copying the certificate to your system's trusted certificates folder, enter the following command to update your system's trusted certificates.
On an Ubuntu system:
update-ca-certificates
On a RHEL system:
If you have manually copied the certificate to the system-wide trust directory, enter the following command to update the system's trusted certificates. If you used the trust anchor command earlier, you do not need to enter the command below.
update-ca-trust extract
📝 NOTE: If you get an error message related to the dynamic CA configuration feature being in a disabled state, after entering the update-ca-trust extract command on a RHEL system, enable the feature first by entering the command update-ca-trust force-enable. Then retry the update-ca-trust extract command.
Placing the Certificate on Other Systems
If other systems will be accessing your private Docker registry, you will need to repeat the copying and updating instructions on those systems.
📝 IMPORTANT: As mentioned previously, any system that will be accessing your private Docker registry will need to have a hostname and IP address entry in its /etc/hosts file that matches the values supplied earlier in the REGISTRYHOST and REGISTRYIPADDR variables, or else be able to look up the registry host's hostname by using DNS.
Starting Your Private Docker Registry
With your signing certificate in place, you can start your private Docker registry by referencing the corresponding private key, by entering the following command:
docker run -d \
-p $REGISTRYPORT:$REGISTRYPORT \
--restart=always \
--name private-registry \
-v /certs:/certs \
-e REGISTRY_HTTP_ADDR=0.0.0.0:$REGISTRYPORT \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/$REGISTRYHOST.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/$REGISTRYHOST.key \
registry:2
You can verify that your private Docker registry container started by entering a docker container ls command. Output from the command should be similar to this:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c7121bbe7dcf registry:2 /entrypoint.sh /etc… 2 minutes ago Up 2 minutes 5000/tcp, 0.0.0.0:5001->5001/tcp private-registry
📝 NOTE: If you see anything other than an Up status for your container, you will need to refer to the container's log, by entering the command docker logs <container_id>, to troubleshoot why it is not up and running.
Stopping and Removing Your Private Docker Registry
By using the --restart=always argument when starting your private Docker registry container, the container will persist across system reboots, so long as the Docker service is enabled.
If you need to, you can stop and remove the container by entering the following command. Replace private-registry with the name of your container, if it is different.
docker container stop private-registry && docker container rm private-registry
Verifying Access to Your Private Docker Registry
After starting your private Docker registry container, you can verify that you can access it using OpenSSL.
echo | openssl s_client -connect $REGISTRYHOST:$REGISTRYPORT -brief
Output from the command should be similar to this:
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_128_GCM_SHA256
Peer certificate: C = US, ST = Michigan, L = Detroit, O = Private Company, Ltd, CN = my.private-registry.lan
Hash used: UNDEF
Signature type: Ed25519
Verification: OK
Server Temp Key: X25519, 253 bits
DONE
After verifying that you can connect successfully to your registry host, without a warning about a self-signed certificate, you can verify that you can access your registry by using the curl command to parse the registry's contents.
curl https://$REGISTRYHOST:$REGISTRYPORT/v2/_catalog | jq
📝 IMPORTANT: Install the CLI JSON processor package, jq, if it is not already installed, before running the curl command above.
The curl command should run without any errors or warnings and show the contents of your registry (empty at first):
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 20 100 20 0 0 317 0 --:--:-- --:--:-- --:--:-- 317
{
repositories: []
}
Securing Your Private Docker Registry
If you intend to use your private Docker registry within an air-gapped deployment, your network setup for this might be secure enough to forego configuring your registry further. However, it is possible to further restrict access to your registry by requiring authentication. Instructions for setting this up can be found in the Docker documentation: Restricting access.
Created by MAT, 2023-07-06
Reviewed by MKP, 2023-07-25