In this post I will go over the installation of Docker Registry with Basic Authentication and over SSL/TLS (self-signed cert for demonstration purposes). I will also do this on Docker for Mac, which has some interesting things to note.

Let me start by saying that you can use the Docker Hub which is a freely available Hosted registry, but there may be instances where you want to host your own registry to control where your images are being stored, you can also opt to use the commercial Docker Trusted Registry, but I will go over the free open-source Registry.

Create project structure
Password file to support Basic Auth
Certificates to support TLS
Location to store the images
Create Docker Registry container
Using the Registry
Registry API

Create project structure

mac$ mkdir -p registry/{volumes/{auth,certs,data},docker-registry}
mac$ cd registry
mac$ tree
.
└── registry
    ├── docker-registry // WhereI will store the docker-compose.yml file
    └── volumes 
        ├── auth  // Volume for Basic Auth password file 
        ├── certs // Volume for TLS certs
        └── data  // Volumes for images storage

Password file to support Basic Auth

Let’s create a username and password

mac$ htpasswd -Bbn   > volumes/auth/htpasswd

mac$ cat volumes/auth/htpasswd
user1:$2y$05$xOuwA8oaflSZ5wpiJto2/OBOIMTSpZrH69wsaQOsnaKt07Vzu1sBW 

Certificates to support TLS

Create a root CA key, which will sign the actual certificates


mac$ openssl genrsa -out volumes/certs/rootCA.key 2048

Create root CA certificate which will need to be installed on the systems that will use the Registry

mac$ openssl req -x509 -new -nodes -key volumes/certs/rootCA.key -days 365 -out volumes/certs/rootCA.crt

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) [AU]:US
State or Province Name (full name) [Some-State]:NY
Locality Name (eg, city) []:NY
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Technologist
Organizational Unit Name (eg, section) []:Tech
Common Name (e.g. server FQDN or YOUR name) []:Technologist CA
Email Address []:johnATtechnologist

Now that you have a CA, you can sign certificates.

Create a key for the Registry system

mac$ openssl genrsa -out volumes/certs/host.key 2048

Create a Certificate Signing Request (CSR) for the CA to sign

mac$ openssl req -new -key volumes/certs/host.key -out volumes/certs/host.csr

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) [AU]:US
State or Province Name (full name) [Some-State]:NY
Locality Name (eg, city) []:NY
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Technologist
Organizational Unit Name (eg, section) []:Tech
Common Name (e.g. server FQDN or YOUR name) []:registry.example.com
Email Address []:johnATtechnologist

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

Finally have the root CA sign the CSR

mac$ openssl x509 -req -in volumes/certs/host.csr -CA volumes/certs/rootCA.crt -CAkey volumes/certs/rootCA.key -CAcreateserial -out volumes/certs/host.crt -days 365

Signature ok
subject=/C=US/ST=NY/L=NY/O=Technologist/OU=Tech/CN=registry.example.com/emailAddress=johnATtechnologist
Getting CA Private Key

Location to store the images

The tree structure should look like below, having the data folder be the image repository

mac $ tree
.
├── docker-registry
│   └── docker-compose.yml
└── volumes
    ├── auth
    │   └── htpasswd
    ├── certs
    │   ├── host.crt
    │   ├── host.csr
    │   ├── host.key
    │   ├── rootCA.crt
    │   ├── rootCA.key
    │   └── rootCA.srl
    └── data

Create Docker Registry container

Run your container using docker command

mac$ docker run -d -p 5000:5000 --restart=always --name registry \
  -v /Users/john/docker/blog/registry/volumes/auth:/auth \
  -e "REGISTRY_AUTH=htpasswd" \
  -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  -v /Users/john/docker/blog/registry/volumes/certs:/certs \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/host.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/host.key \
  registry:2

Or even better, use docker-compose:

mac$ cd docker-registry
mac$ cat docker-compose.yml
registry:
  restart: always
  image: registry:2
  ports:
    - 5000:5000
  environment:
    REGISTRY_HTTP_TLS_CERTIFICATE: /certs/host.crt
    REGISTRY_HTTP_TLS_KEY: /certs/host.key
    REGISTRY_AUTH: htpasswd
    REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
    REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
  volumes:
    - /Users/john/docker/blog/registry/volumes/data:/var/lib/registry
    - /Users/john/docker/blog/registry/volumes/certs:/certs
    - /Users/john/docker/blog/registry/volumes/auth:/auth 

Start up container based on docker-compose.yml

mac$ docker-compose up -d
Creating dockerregistry_registry_1

See it running

mac$ docker-compose ps
          Name                         Command               State           Ports
-------------------------------------------------------------------------------------------
dockerregistry_registry_1   /entrypoint.sh /etc/docker ...   Up      0.0.0.0:5000->5000/tcp

At this point the Docker Registry is running

Using the Registry

Download an image from Docker Hub (or you can test with your own images built from Dockerfile)

mac$ docker pull ubuntu

Tag the image to point to your new Registry

mac$ docker tag ubuntu registry.example.com:5000/john/ubuntu

Push image to your Registry (see failure due to ‘unknow CA authority’)

mac$ docker push registry.example.com:5000/john/ubuntu
The push refers to a repository [registry.example.com:5000/john/ubuntu]
Get https://registry.example.com:5000/v1/_ping: x509: certificate signed by unknown authority

At this point, you need to add the root CA cert to your trusted certificates.
On the machine that will pull or push to the registry, you will need to install the rootCA.crt CA certificate created earlier:

docker-engine$ mkdir /etc/docker/certs.d/registry.example.com:5000/ 
docker-engine$ cp rootCA.crt /etc/docker/certs.d/registry.example.com:5000/rootCA.crt

On the Mac, you are probably using Docker for Mac, which actually runs a small hypervisor xhyve that virtualizes the docker engine. That means we need to place the rootCA.crt CA certificate inside the xhyve VM running the Docker Engine. There is no simple way of doing it and I am using the following method (which must be done everytime you restart the Docker service).

mac$ docker run -v /Users/john/docker/blog/registry/volumes/certs:/macdata -v /etc/docker:/hostdata ubuntu bash -c "mkdir -p /hostdata/certs.d/registry.example.com:5000/;cat /macdata/rootCA.crt > /hostdata/certs.d/registry.example.com:5000/ca.crt"

Now let’s try to Push the image to the Registry again

linux$ docker push registry.example.com:5000/john/ubuntu
The push refers to a repository [registry.example.com:5000/john/ubuntu]
4f9d527ff23f: Preparing
cc8df16ebbaf: Preparing
c3c1fd75be70: Preparing
5c42a1311e7e: Preparing
c8305edc6321: Preparing
unauthorized: authentication required

Good, we are getting somewhere, TLS is working, and it now asks for Authentication
Authenticate to the Docker Registry

linux$ docker login registry.example.com:5000
Username (testuser): user1
Password:
Login Succeeded

Now let’s successfully push our image

linux$ docker push registry.example.com:5000/john/ubuntu
The push refers to a repository [registry.example.com:5000/john/ubuntu]
4f9d527ff23f: Pushed
cc8df16ebbaf: Pushed
c3c1fd75be70: Pushed
5c42a1311e7e: Pushed
c8305edc6321: Pushed
latest: digest: sha256:35dca5fc91c4890f787a266a057ad76b98ff3af43c75aa7e10962d06f06fd6e5 size: 1357

At this point you can share your Docker image, or incorporate into a development or integration workflow by pulling the image
From any Docker engine system:

linux$ docker login registry.example.com:5000
Username: user1
Password:
Login Succeeded
linux$ docker pull registry.example.com:5000/john/ubuntu
Using default tag: latest
latest: Pulling from john/ubuntu
Digest: sha256:35dca5fc91c4890f787a266a057ad76b98ff3af43c75aa7e10962d06f06fd6e5
Status: Downloaded newer image for registry.example.com:5000/john/ubuntu:latest

Registry API

You can use the Docker Registry API https://docs.docker.com/registry/spec/api/ to interact with your Registry.

For example to see a catalog of your images:

$ curl --user user1:password1 --cacert rootCA.crt -X GET https://registry.example.com:5000/v2/_catalog

{"repositories":["john/ubuntu"]}