Setting Up a Private Docker Registry and Using it in NAT Network

You can find these commands in my Gitlab repo.

1. Creating workspace directory

registry_volume=/registry

rm -rf ${registry_volume}; \
mkdir -p ${registry_volume}/{auth,certs,repository,redis}

We will create auth,certs,repository,redis directories.

  • auth Authorization file for loging in to our registry. This file looks like /etc/shadow.
  • cert Certificate and key files of registry.

  • repository Private registry images.

  • redis Redis Cache files. We will mount Redis container's data directory here.

 

 

2. Creating a Certificate

openssl req \
     -newkey rsa:4096 \
     -x509 \
     -nodes \
     -sha256 \
     -keyout ${registry_volume}/certs/docker-registry.key \
     -out ${registry_volume}/certs/docker-registry.crt

Just answer the questions.

 

 

3. Creating a Network Interface in Docker

docker_network_name=docker_nat
docker_network_subnet="10.0.2.0/24"

test -n "$(docker network ls \
     | grep ${docker_network_name})" \
     || docker network create \
     --subnet=${docker_network_subnet} \
     ${docker_network_name}

This is not necessary, but this can make easier to use it in Kubernetes etc.

 

 

4. Standing Up Redis Cache Container

redis_address=10.0.2.63
redis_port=6379
redis_password=top_secret

docker run -d \
     --restart=always \
     --net ${docker_network_name} \
     --ip ${redis_address} \
     --name redis \
     -p ${redis_port}:6379 \
     -v ${registry_volume}/redis:/data \
     redis \
     --appendonly yes  \
     --requirepass ${redis_password}

After container stands up,

docker exec redis \
     redis-cli -h 127.0.0.1 -p ${redis_port} AUTH ${redis_password}

If you get 'OK' response, that means it's working.

 

 

5. Standing Up Private Registry Container

registry_domain=10.0.3.1 # NAT IP address of private registry
registry_port=5000
registry_name=localregistry
registry_address=10.0.2.22 # IP address of private registry container

echo -e "{\n\t\"insecure-registries\" : [ \"${registry_domain}:${registry_port}\" ]\n}" > /etc/docker/daemon.json

systemctl daemon-reload
systemctl restart docker
sleep 1

docker run -d \
     --restart=always \
     -p ${registry_port}:5000 \
     --name ${registry_name} \
     --net ${docker_network_name} \
     --ip ${registry_address} \
     -v ${registry_volume}/repository:/var/lib/registry \
     -v ${registry_volume}/auth:/auth \
     -v ${registry_volume}/certs:/certs \
     -e "REGISTRY_AUTH=htpasswd" \
     -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
     -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
     -e "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/docker-registry.crt" \
     -e "REGISTRY_HTTP_TLS_KEY=/certs/docker-registry.key" \
     -e "REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR=redis" \
     -e "REGISTRY_REDIS_ADDR=${redis_address}:${redis_port}" \
     -e "REGISTRY_REDIS_PASSWORD=${redis_password}" \
     -e "REGISTRY_REDIS_POOL_MAXIDLE=16" \
     -e "REGISTRY_REDIS_POOL_MAXACTIVE=64" \
     -e "REGISTRY_REDIS_POOL_IDLETIMEOUT=300s" \
     registry:2

 

 

Descriptions of parameters:

-p ${registry_port}:5000
    For assigning external port of our registry.

--name ${registry_name}
    For reaching to our registry container without ID.

--net ${docker_network_name}
    The name of the Docker network interface we just created.

--ip ${registry_address}
    IP address of private registry container.

-v ${registry_volume}/repository:/var/lib/registry
    Location of our private images.

-v ${registry_volume}/auth:/auth
    Location of credentials to login our private registry.

-v ${registry_volume}/certs:/certs
    Location of certificate and key files of our private registry.

 

 

Usage of environment variables (from docs.docker.com)

In a typical setup where you run your Registry from the official image, you can specify a configuration variable from the environment by passing -e arguments to your docker run stanza or from within a Dockerfile using the ENV instruction.

To override a configuration option, create an environment variable named REGISTRYvariable where variable is the name of the configuration option and the (underscore) represents indention levels. For example, you can configure the rootdirectory of the filesystem storage backend:

storage:
  filesystem:
    rootdirectory: /var/lib/registry

To override this value, set an environment variable like this:

REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/somewhere

This variable overrides the /var/lib/registry value to the /somewhere directory.

I suggest to look at Docker Documents for detailed information.

 

 

6. Creating Credentials to Login Private Registry

registry_username=user
registry_password=top_secret

docker run --rm \
     --entrypoint htpasswd \
     httpd:2 -Bbn \
     ${registry_username} \
     ${registry_password} \
     > ${registry_volume}/auth/htpasswd; \
chmod 0600 -R ${registry_volume}/auth/htpasswd; \
cat ${registry_volume}/auth/htpasswd

Here we create the username and the password of our private registry.

 

 

7. To Login Private Registry

rm ~/.docker/config.json &>/dev/null; \
docker login \
    -u ${registry_username} \
    -p ${registry_password} \
    https://${registry_domain}:${registry_port}

 

 

8. To Pull, Tag and Push to Private Registry an Image

Pulling an image

docker pull alpine && docker images alpine:latest

Tagging the image

docker tag \
    docker.io/alpine:latest \
    ${registry_domain}:${registry_port}/alpine:local; \
docker images ${registry_domain}:${registry_port}/alpine:local

Pushing the tagged image

docker push ${registry_domain}:${registry_port}/alpine:local; \
docker images ${registry_domain}:${registry_port}/*

 

 

You can see files of image in ${registry_volume}/repository/docker/registry/v2/repositories

ls -1tr ${registry_volume}/repository/docker/registry/v2/repositories

or you can look into the running container

docker exec ${registry_name} \
     sh -c "ls -1tr /var/lib/registry/docker/registry/v2/repositories"

 

 

9. Looking into Redis Cache

We should login to Redis Cache

docker exec -ti redis \
     /usr/local/bin/redis-cli \
     -h 127.0.0.1 \
     -p ${redis_port} \
     -a ${redis_password}

And list keys

keys *

 

 

Now our Private Registry is running and we can login and push our images. What about other NAT clients? You will probably get some security and certificate related errors when you try to login to this registry from other machines.

 

 

The shortest way to solve this problem is to set all other clients to this machine can be used as an "Insecure Registry".

You can run these commands on each machine individually

echo -e "{\n\t\"insecure-registries\" : [ \"${registry_domain}:${registry_port}\" ]\n}" > /etc/docker/daemon.json

systemctl daemon-reload && systemctl restart docker && sleep 1

 

 

Or you can run it all at once with a "For Loop"

NAT_list=( \
    10.0.3.100 \
    10.0.3.101 \
    10.0.3.102 \
    10.0.3.103 \
    10.0.3.104 \
    )

echo -e "{\n\t\"insecure-registries\" : [ \"${registry_domain}:${registry_port}\" ]\n}" >> /etc/docker/daemon.json
systemctl daemon-reload && systemctl restart docker && sleep 1

for i in ${NAT_list[@]}
do
    scp /etc/docker/daemon.json $(echo root@${i}):/etc/docker/daemon.json
    ssh -T $(echo root@${i}) 'systemctl daemon-reload; systemctl restart docker && echo OK.'
done

 

 

10. You Can Try Pulling "alpine:local" Image From Another NAT Client

Connecting to a client from NAT_list

ssh $(echo root@${NAT_list[-1]})

 

 

To Login private registry

docker login \
    -u user \
    -p top_secret \
    https://10.0.3.1:5000 # Edit here as your configuration

 

 

Pulling "alpine:local" image

docker pull 10.0.3.1:5000/alpine:local

 

 

If you want to list images in private registry

curl -s \
    --insecure \
    -X GET \
    -u user:top_secret \
    "https://10.0.3.1:5000/v2/_catalog" \
    | jq -r '.repositories'

 

 

If you want to list tags of an image in private registry

curl -s \
    --insecure \
    -X GET \
    -u user:top_secret \
    "https://10.0.3.1:5000/v2/alpine/tags/list" \
    | jq -r '.tags'