Docker Private Registry - How To

Table of Contents

Setup private docker registry using official registry image.

Docker registry is repository of docker images. This image allow us to run a private registry in our own environment.


Docker Hub: registry1


Host Inside Container Usage
$MY_REG_DIR /var/lib/registry Registry persistent storage
$MY_REG_PORT $REGISTRY_HTTP_ADDR(default :::5000) Registry listening port
$MY_REG_CONF /etc/docker/registry/config.yml Registry configuration file
$MY_REG_TLS_DIR /certs Registry TLS certificate directory
(see TLS below) $REGISTRY_HTTP_TLS_CERTIFICATE Full path of registry tls certificate file
(see TLS below) $REGISTRY_HTTP_TLS_KEY Full path of registry tls key file

Note: This exercise only cover a very basic registry setup. More configuration options can be found in official documentation2.



We will use directory /var/lib/my_registry as persistent storage for our private registry.

mkdir /var/lib/my_registry

So $MY_REG_DIR=/var/lib/my_registry

Note: Never use /var/lib/docker, /var/lib/registry, etc as your local persistent storage. Those are mostly used by packages from package manger.


Registry uses port 5000 inside container. We will map it to port 5001 in the host for this example.

So $MY_REG_PORT=5001, you can use other port as long as it does not conflict with other services running.


$MY_REG_CONF is used to map a customized registry configuration file into the container. Such customization is out of scope of this exercise. We will not define it in this exercise.


Note: TLS is optional for this exercise.

For our private registry to use tls, we have to map a certificate directory to /cert inside the container.


We will use directory /var/lib/my_registry_tls as our certificate directory.

mkdir /var/lib/my_registry_tls

So $MY_REG_TLS_DIR=/var/lib/my_registry_tls

Put the certificate and key file inside.

├── certificate.pem
└── key.pem
Cert And Key Filename

Though we mapped a directory containing certificate and key files into the container, we still need to let the registry know the name of the respective files. That is where the container environmental variable comes in.


$REGISTRY_HTTP_TLS_CERTIFICATE define the full path of the tls certificate file INSIDE the container.

To make our compose file more portable in later section. We will define a new variable $MY_REG_TLS_CRT for the certificate filename.



As the previous one, $REGISTRY_HTTP_TLS_KEY define the full path of the tls key file INSIDE the container.

We will use $MY_REG_TLS_KEY for key filename.


Preparation Summary

Variable Value
$MY_REG_DIR /var/lib/my_registry
$MY_REG_CONF <n/a>
$MY_REG_TLS_DIR /var/lib/my_registry_tls
$MY_REG_TLS_CRT certificate.pem
$MY_REG_TLS_KEY key.pem


Following are running commands using variables prepared above:


docker run \
-d \
--rm \
-p ${MY_REG_PORT}:5000 \
-v ${MY_REG_DIR}:/var/lib/registry \
-v ${MY_REG_CONF}:/etc/docker/registry/config.yml \
--name my_registry \

With TLS

docker run \
-d \
--rm \
-p ${MY_REG_PORT}:5000 \
-v ${MY_REG_DIR}:/var/lib/registry \
-v ${MY_REG_CONF}:/etc/docker/registry/config.yml \
-v ${MY_REG_TLS_DIR}:/certs \
--name my_registry \
docker run option Usage
-d Run as daemon
–rm Automatically remove the container when it exits
-v <source path in host>:<target path in container> Map a path(file/dir) from host to a path in container
-p <host port>:<container port> Map a port from host to a port in container
-e VAR_NAME=VALUE Set environment variables inside the container
–name Set name of container created


Let plug in all the values manually, skipping -d, tls and custom configuration:

docker run --rm -p 5001:5000 -v /var/lib/my_registry:/var/lib/registry registry


time="2019-09-01T21:15:07.526325662Z" level=warning msg="No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable." go.version=go1.11.2 service=registry version=v2.7.1
time="2019-09-01T21:15:07.526391891Z" level=info msg="Starting upload purge in 55m0s" go.version=go1.11.2 service=registry version=v2.7.1
time="2019-09-01T21:15:07.526445081Z" level=info msg="redis not configured" go.version=go1.11.2 service=registry version=v2.7.1
time="2019-09-01T21:15:07.549500041Z" level=info msg="using inmemory blob descriptor cache" go.version=go1.11.2 service=registry version=v2.7.1
time="2019-09-01T21:15:07.549764469Z" level=info msg="listening on [::]:5000" go.version=go1.11.2 service=registry version=v2.7.1

There is a warning about REGISTRY_HTTP_SECRET if multiple registries running behind load balancer. That can be ignore for this exercise.

Keep this running and continue to next section.

Push Image

While the private registry is running, we will try pushing an image into it.

Pull from official registry

Pull a fresh Alpine Linux image from official registry:

docker pull alpine


Using default tag: latest
latest: Pulling from library/alpine
9d48c3bd43c5: Pull complete
Digest: sha256:72c42ed48c3a2db31b7dafe17d275b634664a708d901ec9fd57b1529280f01fb
Status: Downloaded newer image for alpine:latest
Tag To Private Registry

Tag the image to use private registry:

docker tag alpine:latest localhost:5001/alpine:latest
Push To Private Registry
docker push localhost:5001/alpine:latest


The push refers to repository [localhost:5001/alpine]
03901b4a2ea8: Pushed
latest: digest: sha256:acd3ca9941a85e8ed16515bfc5328e4e2f8c128caa72959a58a127b7801ee01f size: 528

Stop the registry with ctrl-c.


We will use docker compose to make running private registry more streamline and manageable.

Create directory compose_registry and create the following files inside it:



MY_REG_TAG: For version other than latest, check registry tags page3.


version: '3'
    image: registry:${MY_REG_TAG}
      - "${MY_REG_PORT}:5000"
      - ${MY_REG_DIR}:/var/lib/registry
      #- ${MY_REG_TLS_DIR}=/var/lib/my_registry_tls
    restart: always


cd compose_registry
docker-compose up -d
docker-compose command/Option Usage
up create and start container
-d daemon/run in background

When docker-compose4 is executed, it automatically look for two default files in current directory: docker-compose-yml5 and .env6.

It will use variables in .env as environment variables. The .env file must be present in the current working folder.

Currently there is no command line option to specify an alternative .env file.

Variables already set in shell and command line will override .env.

It will create containers(s) base on docker-compose.yml.

-f can specify one or more compose file, other than the default.


Creating network "registry-compose_default" with the default driver
Creating registry-compose_registry_1 ... done


Check status with ps

cd compose_registry
docker-compose up -d


           Name                          Command               State           Ports
registry-compose_registry_1   / /etc/docker ...   Up>5000/tcp


cd compose_registry
docker-compose stop


Stopping registry-compose_registry_1 ... done

Now our private registry is ready!

John Siu
Minimize the Effort, Maximize the Effect!
comments powered by Disqus