Skip to main content

How to implement a simple personal/private Linux container image registry for internal use

Here's a quick and easy way to create a basic internal registry to manage your container images.
Creating a simple container image registry
Image by Dutch Air from Pixabay

An image registry stores and versions container images for distribution to container runtime engines such as Docker, Podman, and others. Images are typically pulled and pushed using the respective commands of the runtime engine. Another benefit is that registries can centralize the distribution of images in an environment. Most runtimes come with default registries to pull and push from, but you might want to have your own personal/private registry where you can control what images are available for your runtimes.

This article outlines the steps needed to implement a private registry as a container and store images in /opt/registry for internal use. Podman is the container engine and htpasswd provides authentication.

Install Podman and httpd-tools

Install the podman package to run the registry. The httpd-tools package provides the htpasswd utility for authentication.

# yum install -y podman httpd-tools

Create folders for the registry

The registry will be stored in /opt/registry/ on the host system and the respective directories are mounted in the container running the registry.

# mkdir -p /opt/registry/{auth,certs,data}
  • The Auth subdirectory stores the htpasswd file used for authentication.
  • The Certs subdirectory stores certificates used by the registry for
  • authentication.
  • The Data directory stores the actual images stored in the registry.

Note - Creating the directories on removable storage makes the registry portable for disconnected/restricted networks.

Generate credentials for accessing the registry

Authentication is provided by a simple htpasswd file and also an SSL key pair.

htpasswd username and password

Use the htpasswd utility to generate a file containing the credentials for accessing the registry:

# htpasswd -bBc /opt/registry/auth/htpasswd registryuser registryuserpassword
  • b provides the password via command.
  • B stores the password using Bcrypt encryption.
  • c creates the file.
  • Username is registryuser.
  • Password is registryuserpassword.

A Bcrypt Htpasswd file named htpasswd will be created in the /opt/registry/auth/ directory.

TLS key pair

The registry is secured with TLS by using a key and certificate signed by a trusted authority (internal or external) or by a simple self-signed certificate. To use a self-signed certificate:

# openssl req -newkey rsa:4096 -nodes -sha256 -keyout /opt/registry/certs/domain.key -x509 -days 365 -out /opt/registry/certs/domain.crt
  • req tells OpenSSL to generate and process certificate requests.
  • -newkey tells OpenSSL to create a new private key and matching certificate request.
  • rsa:4096 tells OpenSSL to generate an RSA key with 4096 bits.
  • -nodes tells OpenSSL there is no password requirement for the private key. The private key will not be encrypted.
  • -sha256 tells OpenSSL to use the sha256 to sign the request.
  • -keyout tells OpenSSL the name and location to store new key.
  • -x509 tells OpenSSL to generate a self-signed certificate.
  • -days tells OpenSSL the number of days the key pair is valid for.
  • -out tells OpenSSL where to store the certificate.

Enter the respective options for your certificate. The CN= value is the hostname of your host. The host's hostname should be resolvable by DNS or the /etc/hosts file.

Note: If the registry is not secured using TLS, the insecure setting in the /etc/containers/registries.conf file may have to be configured for the registry.

The certificate will also have to be trusted by your hosts and clients:

# cp /opt/registry/certs/domain.crt /etc/pki/ca-trust/source/anchors/
# update-ca-trust
# trust list | grep -i "<hostname>"

Start the registry

The next step involves running the registry image with an exposed port (5000) and mounted volumes of the respective directories created earlier.

The command to start the registry:

# podman run --name myregistry \
-p 5000:5000 \
-v /opt/registry/data:/var/lib/registry:z \
-v /opt/registry/auth:/auth:z \
-e "REGISTRY_AUTH=htpasswd" \
-v /opt/registry/certs:/certs:z \
-e "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt" \
-e "REGISTRY_HTTP_TLS_KEY=/certs/domain.key" \
-d \

The details of the options are:

  • --name myregistry names the container myregistry.
  • -p 5000:5000 exposes port 5000 in the container as port 5000 on the host.
  • -v /opt/registry/data:/var/lib/registry:z mounts /opt/registry/data on the host as /var/lib/registry in the container with the correct SELinux context.
  • -v /opt/registry/auth:/auth:z mounts /opt/registry/auth on the host as /auth in the container with the correct SELinux context.
  • -v opt/registry/certs:/certs:z mounts /opt/registry/certs on the hosts as /certs in the container with the correct SELinux context.
  • -e "REGISTRY_AUTH=htpasswd" uses an bcrypt encrypted htpasswd file for authentication. File location set by container's REGISTRY_AUTH_HTPASSWD_PATH environment variable.
  • -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" specifies the realm to use for htpasswd.
  • -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd uses the bcrypt-encrypted /auth/htpasswd file in the container.
  • -e "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt" sets path to certificate file.
  • -e "REGISTRY_HTTP_TLS_KEY=/certs/domain.key" sets path to private key.
  • -e REGISTRY_COMPATIBILITY_SCHEMA1_ENABLED=true provides backward compatibility for schema1 manifests.
  • -d is a registry application that allows for the storage and distribution of images.

Note: If a firewall is running on the hosts, the exposed port (5000) will need to be permitted.

# firewall-cmd --add-port=5000/tcp --zone=internal --permanent
# firewall-cmd --add-port=5000/tcp --zone=public --permanent
# firewall-cmd --reload

Verify access to registry

The curl command can be used to access the registry:

# curl https://hostname:5000/v2/_catalog

The certificate can be verified using:

# openssl s_client -connect <servername>:5000 -servername <servername>

Be sure to trust the certificate from earlier or use curl's -k switch to ignore certificate verification.

Work with the registry

The registry can be accessed and interacted with just like any other registry such as,,, and/or

Log in to the registry

Use the podman login command to log into the registry:

# podman login <hostname>:5000
Enter Username:xxxxxxxx
Enter Password:yyyyyyyy
Login Succeeded!

Your credentials will be Base64 encoded into /run/user/0/containers/auth.json by default.

When working with OpenShift and using an alternate/mirror registry for disconnected/air-gapped environments, the contents of this file can be added to your pull secret file. You can also add your pull secret file as a command-line option to podman login and your credentials will be added to the file upon login.

# podman login <hostname>:5000 --authfile <pull secret file>
Enter Username:xxxxxxxx
Enter Password:yyyyyyyy
Login Succeeded!

Log out of the registry

Use podman logout to log out of the registry:

# podman logout <hostname>:5000
Removed login credentials for

Credentials will be removed from /run/user/0/containers/auth.json or from any file you specify via the --authfile parameter.

Push/pull images to the registry

To pull images from the registry, prepend the registry location to the repository/image name.

Example: podman pull <Registry Hostname>:<Registry Port>/<Repository>/<Image Name>

To push to the registry, use podman tag to first tag the image and the registry location, and then push the image.

Example: podman tag <image id|<repo name/image name>> registry:5000/<repo>/<image>

# podman pull
Trying to pull
Getting image source signatures
Storing signatures

# podman login <registry hostname>:<port>
Enter Username:xxxxxx
Enter Password:yyyyyyy
Login Suceeded!

# podman tag <registry

# podman push <registry hostname>:<port>/ubi8/ubi:latest
Getting image source signatures
Copying blob 226bfaae015f done
Storing signatures

You can verify the images has been uploaded to the registry via the curl command:

# curl -u <username>:password https://<registry host>:<port>/v2/_catalog

With the private registry implemented, you can edit /etc/containers/registries.conf to include the private registry in the list of supported registries.


Start registry:

# podman run --name myregistry -p 5000:5000 -v
/opt/registry/data:/var/lib/registry:z -v /opt/registry/auth:/auth:z -e
REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd -v /opt/registry/certs:/certs:z -e

Stop registry:

# podman stop myregistry

Remove registry:

# podman rm myregistry

Delete registry image:

# podman rmi registry:latest

Remove certificate:

# rm /etc/pki/ca-trust/source/anchors/domain.crt
# update-ca-trust
# trust list | grep "<hostname>"


Another option is to use Quay as a private registry, which offers many more features.


With this personal/private registry in place, you can provide a central point of distribution for your in-house images that can be stored and maintained. A private registry can also come in handy in disconnected and air-gapped environments. I hope you find this information useful for your situation.

[ Getting started with containers? Check out this free course. Deploying containerized applications: A technical overview. ]

Topics:   Containers   Podman  
Author’s photo

Stephen Wilson

Stephen Wilson is a Senior Storage Consultant with Red Hat, Inc. He has over 20 years of experience in information systems management. His professional interests include system administration, cybersecurity, cloud technologies, and virtualization. More about me

Try Red Hat Enterprise Linux

Download it at no charge from the Red Hat Developer program.