What is a Container Registry?
The containers are created from the images. These images need to be stored and used from a scalable and secure repository. Like the other repositories, the container registry also must provide a fast way to pull and push images with correct policies and credentials.
The most popular container registry hosting the images is Docker Hub. The Docker and Kubernetes are usually using the images via this service. There are many public images freely available, but you don’t have full control over the registry, and day by day, you will meet with some restrictions. Moreover, when it comes to using private images, security is a concern, and it may also be expensive.
Therefore, hosting your private registry can be useful in many situations. Private registries offer many different storage and authentication options and can be customized according to your personal needs.
Why Use a Private Container Registry?
Here are some basic reasons for using your personal private registry instead of a public registry like DockerHub.
- You have full control over the storage location of the private registry.
- You can set up policies as you wish
- Wide options to secure and share your images
- Special configuration options for logging, authentication, load balancing, etc.
There are some images like DockerHub’s registry and CNCF’s Harbor to configure and use as a private registry. In this study, we will go on with DockerHub’s registry image.
AWS ec2-instance Security Settings for Container Registry
We will deploy the registry to an AWS ec2-instance for the showcase. It will host the image repository. That’s why it is important to emphasize the security group permissions.
HTTP TCP 80 IP: 0.0.0.0/0 - HTTP TCP 443, IP: 0.0.0.0/0 - SSH TCP 22 IP: 0.0.0.0/0 - Custom TCP 5000 IP: 0.0.0.0/0 - Custom TCP 5000 IP: ::/0 -

Create a Simple Registry
Let’s create a folder to start our journey at the home directory;
mkdir -p docker-hub/data chmod 777 -R docker-hub/data
The
data
folder will store the
registry data
.
We will use
docker-compose
structure to create and manage the registry.
To create a simple registry, we can use a basic
registry
image from Docker Hub. The
docker-compose.yaml
file content is as follows:
version: '3' services: docker-registry: image: registry:2 restart : always container_name: docker-registry ports: - "5000:5000" environment: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data volumes: - ./data:/data
The directory structure is as follows:
docker-hub ├── data └── docker-compose.yaml
Now, run the
docker-compose
under
docker-hub
directory.
docker-compose up -d
Pushing an Image to the Registry
The registry is ready to store the images. To do that, we need to create an image that is labeled correctly. The correct convention for labeling is as follows:
<ip>:<port-number>/<image-name>:<tag> <dns-name>:<port-number>/<image-name>:<tag>
For example, we can pull an image from the docker hub, or create an image from scratch and tag it for our private registry.
docker pull alpine docker tag alpine localhost:5000/my-alpine
To push the image, we need to login into our private registry if it requires the credentials.
docker login localhost:5000
Now we can push the image using the
push
command:
docker push localhost:5000/my-alpine
Pulling an Image from the Registry
The pulling of images from the private registry is very straightforward. Again, to pull the image, we need to login into our private registry if it requires credentials.
docker login localhost:5000
Now we can pull the image using the
pull
command:
docker push localhost:5000/my-alpine
Adding Web Service to the Private Registry
It is a good idea to monitor or see the images via a web browser, as in Docker Hub. To realize this, we can use
konradkleine/docker-registry-frontend
image. There is enough configuration information on the related documentation page. The
docker-compose.yaml
file is updated to capture this feature.
version: '3' services: docker-registry: image: registry:2 restart : always container_name: docker-registry ports: - "5000:5000" environment: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data volumes: - ./data:/data docker-registry-ui: image: konradkleine/docker-registry-frontend:v2 restart : always container_name: docker-registry-ui ports: - 80:80 environment: ENV_DOCKER_REGISTRY_HOST: ip-172-31-82-125.ec2.internal ENV_DOCKER_REGISTRY_PORT: 5000
For the
ENV_DOCKER_REGISTRY_HOST
, we could use a service name, but as we intend to use this registry as a service, we need a secure SSL connection and a
reachable ip
or
dns name
. That is why we used
ip-172–31–82–125.ec2.internal
, which is reachable from the AWS network internally. A DNS name can also be used instead of this static IP. You can also set the storage location with the help of an environment variable,
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY
.
We have added the
my-nginx
and
httpd
images to the registry. The web browser screenshots are presented.



Adding a Certificate to the Registry
For a secure connection and remote client access, you need certificates. We will show to configure a secure connection with SSL certificates. If you already acquired a
.cert
and
.key
from your Certification Authority, then you just need the copy them to under
certs
directory. Go to
docker-hub
and then,
mkdir certs chmod 777 -R certs
Now copy the
.crt
and
.key
file to
certs
directory.
If you don’t have them, here is a way of creating the Certification Authorization Key. For this purpose, we need the
openssl
package. If
openssl
not present, install it.
yum -y install openssl
Let’s create
certification
keys for docker-registry. Command prompt must be at the same level as
certs
folder. Run the command as follows;
openssl req \ -newkey rsa:4096 -nodes -sha256 -keyout ./certs/domain.key \ -x509 -days 3650 -out ./certs/domain.crt
When creating keys,
Common Name (eg, your name or your server’s hostname)
part is critical. It must be the
hostname/ip
of the
docker-registry
host. When AWS considered,
private ip/hostname
of
docker-registry
host can be used. The private IP of an ec2 instance is like
ip-172–31–82–125.ec2.internal
. You can go with defaults (just press
enter
) or enter the appropriate values for the rest.
[ec2-user@ip-172-31-82-125 docker-hub]$ openssl req \ > -newkey rsa:4096 -nodes -sha256 -keyout ./certs/domain.key \ > -x509 -days 3650 -out ./certs/domain.crt Generating a 4096 bit RSA private key ...............................................................................++ .......................................................................................................................................................++ writing new private key to './certs/domain.key' ----- You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [XX]: State or Province Name (full name) []: Locality Name (eg, city) [Default City]: Organization Name (eg, company) [Default Company Ltd]: Organizational Unit Name (eg, section) []: Common Name (eg, your name or your server's hostname) []:ip-172-31-82-125.ec2.internal Email Address []:
Adding a Certificate to the Registry with the Help of
openssl.conf
File
You can also use a file to set the
openssl
configuration settings as an alternative. For this, prepare an
openssl.conf
file for your needs. We will use the following settings for the parameters:
[ req ] distinguished_name = req_distinguished_name x509_extensions = req_ext default_md = sha256 prompt = no encrypt_key = no [ req_distinguished_name ] countryName = "US" localityName = "Bristow" organizationName = "Clarusway" organizationalUnitName = "Clarusway" commonName = "ip-172-31-82-125.ec2.internal" emailAddress = "test@example.com" [ req_ext ] subjectAltName = @alt_names [alt_names] DNS = "ip-172-31-82-125.ec2.internal"
Run the command as follows;
openssl req \ -x509 -newkey rsa:4096 -days 3650 -config openssl.conf \ -keyout certs/domain.key -out certs/domain.crt
The command output should look like this.
openssl req \ > -x509 -newkey rsa:4096 -days 3650 -config openssl.conf \ > -keyout certs/domain.key -out certs/domain.crt Generating a 4096 bit RSA private key ...........................................++ ...............++ writing new private key to 'certs/domain.key' -----
The folder structure should be like this.
docker-hub ├── certs │ ├── domain.crt │ └── domain.key ├── data └── docker-compose.yaml
Now, it is time to update the
docker-compose.yaml
file to include the certificates.
version: '3' services: docker-registry: image: registry:2 restart : always container_name: docker-registry ports: - "5000:5000" environment: REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt REGISTRY_HTTP_TLS_KEY: /certs/domain.key REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data volumes: - ./certs:/certs - ./data:/data docker-registry-ui: image: konradkleine/docker-registry-frontend:v2 container_name: docker-registry-ui ports: - 443:443 environment: ENV_DOCKER_REGISTRY_HOST: ip-172-31-82-125.ec2.internal ENV_DOCKER_REGISTRY_PORT: 5000 ENV_USE_SSL: "yes" ENV_DOCKER_REGISTRY_USE_SSL: 1 volumes: - ./certs/domain.crt:/etc/apache2/server.crt:ro - ./certs/domain.key:/etc/apache2/server.key:ro
This time, for the web browser link, we have to use
https
connection. In our case,
https://ec2-35-172-118-174.compute-1.amazonaws.com/
will be used. As we get a certificate for a private IP address (
ip-172–31–82–125.ec2.internal
), we can ignore the security warning and go on.

Client Machine Settings to Use the Registry
The
domain.crt
must be copied to the clients. The
path
should be
/etc/docker/certs.d/<private-docker-hub-ip or dns-name>:<port-no>/
. In our case, copy the file to the clients and then at clients move the file to the correct path with
root
privileges (with
sudo
or
root
user):
[root@docker-client1 ~]# mkdir -p /etc/docker/certs.d/ip-172-31-82-125.ec2.internal:5000/ [root@docker-client1 ~]# cp -rf ./domain.crt /etc/docker/certs.d/ip-172-31-82-125.ec2.internal:5000/
The
domain.crt
can also be copied to
/root/domain.crt /etc/docker/certs.d/ip-172–31–82–125.ec2.internal:5000/
directory at registry (private-docker-hub) node.
After this setting, we can
pull
and
push
images at the clients with a secure connection.
Creating the Authentification File
In the registry, for authentication, we will use an
username
and a
password
. To create the password, we will need
htpasswd
package to be installed.
If this package is not installed, for ubuntu-based Linux OS;
sudo apt install apache2-utils -y
For fedora-centos-based Linux OS;
sudo yum install httpd-tools -y
After verification of the presence of
htpassw
package, we can create a folder to go on our journey. Let’s create the folders to hold the password file;
mkdir -p docker-hub/auth
This is the folder to store our
password file
. Next, the password for a selected user can be created. Let’s create a
username
and
password
.
cd docker-hub/auth htpasswd -Bc registry.password clarusway
The last parameter is the name of the user; in this case
clarusway
. After executing the command, you will be prompted to enter your password. In this study,
clarusway
is selected for both
username
and
password
.
Final
docker-compose
files with Certification Authorization Key and
username
and
password
authentication;
version: '3' services: docker-registry: image: registry:2 restart : always container_name: docker-registry ports: - "5000:5000" environment: REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt REGISTRY_HTTP_TLS_KEY: /certs/domain.key REGISTRY_AUTH: htpasswd REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.password REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data volumes: - ./certs:/certs - ./auth:/auth - ./data:/data docker-registry-ui: image: konradkleine/docker-registry-frontend:v2 restart : always container_name: docker-registry-ui ports: - 443:443 environment: ENV_DOCKER_REGISTRY_HOST: ip-172-31-82-125.ec2.internal ENV_DOCKER_REGISTRY_PORT: 5000 ENV_USE_SSL: "yes" ENV_DOCKER_REGISTRY_USE_SSL: 1 volumes: - ./certs/domain.crt:/etc/apache2/server.crt:ro - ./certs/domain.key:/etc/apache2/server.key:ro
The folder structure should be like this;
docker-hub ├── auth │ └── registry.password ├── certs │ ├── domain.crt │ └── domain.key ├── data └── docker-compose.yaml
Now, run the
docker-compose
under
docker-hub
directory. Let’s check the web interface. When the default page loaded, we see the Welcome Screen. After hitting the
Browse repositories
button, a login attempt is observed.


Sample Usage At Clients
The client should log in and use the reachable IP(DNS)&port number of this registry to
pull
and
push
the images.
After authentication setting activation, we have to log in to
ip-172–31–82–125.ec2.internal:5000
to
pull
and
push
the images at the clients.
docker login ip-172-31-82-125.ec2.internal:5000
docker pull ip-172-31-82-125.ec2.internal:5000/my-alpine
docker tag <image>:tag ip-172-31-82-125.ec2.internal:5000/<new-name>:tag
docker push ip-172-31-82-125.ec2.internal:5000/<new-name>:tag
A sample image pull showcase is as follows:
[ec2-user@ip-172-31-45-163 ~]$ docker login ip-172-31-82-125.ec2.internal:5000 Username: clarusway Password: WARNING! Your password will be stored unencrypted in /home/ec2-user/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded [ec2-user@ip-172-31-45-163 ~]$ docker pull ip-172-31-82-125.ec2.internal:5000/my-alpine:latest latest: Pulling from my-alpine Digest: sha256:074d3636ebda6dd446d0d00304c4454f468237fdacf08fb0eeac90bdbfa1bac7 Status: Downloaded newer image for ip-172-31-82-125.ec2.internal:5000/my-alpine:latest ip-172-31-82-125.ec2.internal:5000/my-alpine:latest [ec2-user@ip-172-31-45-163 ~]$
I hope this study addresses the issues of hosting and use a private container registry. You can freely and securely use the containers in popular tools like Docker, contained, Mesos Containerizer, CoreOS rkt, and LXC Linux Containers. I have explained how to use private registries with Kubernetes in the “How to use images from a private container registry for Kubernetes: AWS ECR, Hosted Private Container Registry”.
References
https://gabrieltanner.org/blog/docker-registry
https://www.learnitguide.net/2018/07/create-your-own-private-docker-registry.html
https://www.youtube.com/watch?v=SEpR35HZ_hQ
https://hub.docker.com/r/konradkleine/docker-registry-frontend
https://github.com/justmeandopensource/docker/tree/master/docker-compose-files/docker-registry