Docker Registry with Authentication and SSL/TLS
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"]}