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.
The commands shown in this article should be entered from a root user shell.
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
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
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.
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.
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
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
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.
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/
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 commandupdate-ca-trust force-enable
. Then retry theupdate-ca-trust
extract command.
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 theREGISTRYHOST
andREGISTRYIPADDR
variables, or else be able to look up the registry host’s hostname by using DNS.
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.
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
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 thecurl
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: []
}
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