(Last Updated On: August 4, 2018)

If you’re running Docker micro-services in your Infrastructure, you may be interested in building an internal Private Docker Registry to host Docker images. This could be for security reasons or to have faster builds. This article will show you the steps you need to Setup Docker Private Registry on Ubuntu 18.04 and Ubuntu 16.04.

The assumption here is that you have running Docker Engine hosts, Kubernetes cluster or DC/OS cluster. If you don’t have docker or Kubernetes clusters, here are the guides to get you started:

How to install Docker CE on Ubuntu / Debian / Fedora / Arch / CentOS

How to setup 3 node Kubernetes Cluster on Ubuntu 18.04 with Weave Net CNI

How to Deploy and Use Dokku on Ubuntu 18.04

How to launch Kubernetes cluster using Rancher

Install Docker UI manager – Portainer

We also have an article on how to Install and Configure Docker Registry on CentOS 7, check it out if you’re interested in setting up Docker Registry on CentOS 7.

How to Setup Docker Private Registry on Ubuntu 18.04 / Ubuntu 16.04

Let’s start to build Private Registry for Docker images. First, install Docker Engine on the host to act as a registry.

Update the apt package index:

sudo apt-get update

Install packages to allow apt to use a repository over HTTPS:

sudo apt-get install \
 apt-transport-https \
 ca-certificates \
 curl \
 software-properties-common

Add Docker’s official GPG key:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

Add official Docker stable repository:

sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable"

Install docker-ce pakage:

sudo apt-get update
sudo apt-get install docker-ce

If you would like to use Docker as a non-root user, you should now consider adding your user to the “docker” group:

sudo usermod -aG docker your-user

Run the command below to see a version of docker installed.

$ docker --version
Docker version 18.06.0-ce, build 0ffa825

Check status, it should be in a running state:

 $ systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2018-08-04 07:53:38 UTC; 5s ago
     Docs: https://docs.docker.com
 Main PID: 3978 (dockerd)
    Tasks: 18
   CGroup: /system.slice/docker.service
           ├─3978 /usr/bin/dockerd -H fd://
           └─3997 docker-containerd --config /var/run/docker/containerd/containerd.toml

Aug 04 07:53:38 registry.computingforgeeks.com dockerd[3978]: time="2018-08-04T07:53:38.148462907Z" level=info msg="ClientConn switching balancer to \
Aug 04 07:53:38 registry.computingforgeeks.com dockerd[3978]: time="2018-08-04T07:53:38.148746959Z" level=info msg="pickfirstBalancer: HandleSubConnSt
Aug 04 07:53:38 registry.computingforgeeks.com dockerd[3978]: time="2018-08-04T07:53:38.149468418Z" level=info msg="pickfirstBalancer: HandleSubConnSt
Aug 04 07:53:38 registry.computingforgeeks.com dockerd[3978]: time="2018-08-04T07:53:38.149705817Z" level=info msg="Loading containers: start."
Aug 04 07:53:38 registry.computingforgeeks.com dockerd[3978]: time="2018-08-04T07:53:38.298147504Z" level=info msg="Default bridge (docker0) is assign
Aug 04 07:53:38 registry.computingforgeeks.com dockerd[3978]: time="2018-08-04T07:53:38.334297216Z" level=info msg="Loading containers: done."
Aug 04 07:53:38 registry.computingforgeeks.com dockerd[3978]: time="2018-08-04T07:53:38.364074712Z" level=info msg="Docker daemon" commit=0ffa825 grap
Aug 04 07:53:38 registry.computingforgeeks.com dockerd[3978]: time="2018-08-04T07:53:38.364571771Z" level=info msg="Daemon has completed initializatio
Aug 04 07:53:38 registry.computingforgeeks.com systemd[1]: Started Docker Application Container Engine.
Aug 04 07:53:38 registry.computingforgeeks.com dockerd[3978]: time="2018-08-04T07:53:38.382264829Z" level=info msg="API listen on /var/run/docker.sock

Step 1: Get Let’s Encrypt SSL Certificates

In this Docker Registry setup, we will use Let’s Encrypt SSL Certificates which expire every 90 days and you’ll need to renew.

Install certbot-auto

wget https://dl.eff.org/certbot-auto 
chmod +x certbot-auto 
sudo mv certbot-auto  /usr/local/bin/certbot-auto

Request for SSL certificate:

export DOMAIN="registry.domain.com"
export EMAIL="email@domain.com"
certbot-auto certonly --standalone -d $DOMAIN --preferred-challenges http --agree-tos -n -m $EMAIL --keep-until-expiring

Your certs will be saved under /etc/letsencrypt/live/

/etc/letsencrypt/live/registry.domain.com/fullchain.pem
/etc/letsencrypt/live/registry.domain.com/privkey.pem

fullchain.pem ⇒ combined file cert.pem and chain.pem
chain.pem ⇒ intermediate certificate
cert.pem ⇒ SSL Server cert(includes public-key)
privkey.pem ⇒ private-key file

Step 2: Configure and Start Docker Registry container

You can either run docker registry with SSL or without. But first, create a directory that will hold Docker registry images:

sudo mkdir /var/lib/docker/registry

Run Local Docker registry without SSL

$ docker run -d -p 5000:5000 \
--name docker-registry \
--restart=always \
-v /var/lib/docker/registry:/var/lib/registry \
registry:2

Run Local Docker registry with SSL

Create directory and place certs on the host:

mkdir /certs
cat /etc/letsencrypt/live/registry.domain.com/fullchain.pem > /certs/fullchain.pem
cat /etc/letsencrypt/live/registry.domain.com/privkey.pem > /certs/privkey.pem
cat /etc/letsencrypt/live/registry.domain.com/cert.pem > /certs/cert.pem

Create a Docker Registry container:

$ docker run -d --name docker-registry --restart=always \
-p 5000:5000 \
-e REGISTRY_HTTP_ADDR=0.0.0.0:5000 \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/fullchain.pem  \
-e REGISTRY_HTTP_TLS_KEY=/certs/privkey.pem \
-v /certs:/certs \
-v /var/lib/docker/registry:/var/lib/registry \
registry:2

Replace registry.domain.com with your registry subdomain name.

It will download registry:2 docker image if it doesn’t exist and create a container

....
Unable to find image 'registry:2' locally
2: Pulling from library/registry
4064ffdc82fe: Pull complete 
c12c92d1c5a2: Pull complete 
4fbc9b6835cc: Pull complete 
765973b0f65f: Pull complete 
3968771a7c3a: Pull complete 
Digest: sha256:51bb55f23ef7e25ac9b8313b139a8dd45baa832943c8ad8f7da2ddad6355b3c8
Status: Downloaded newer image for registry:2
211c906fdbc3f0ccc2ce5cf7f6af5f7b7448eb006bca96ba275c093182f63888

Check container state:

# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
5f2ddfba15df        registry:2          "/entrypoint.sh /etc…"   2 minutes ago       Up 2 minutes        0.0.0.0:5000->5000/tcp   docker-registry

To push images to Registry Container server, set like below:

$ curl https://registry.computingforgeeks.com:5000/v2/_catalog 
{"repositories":[]}

Let’s download two docker images and push them to this local repository:

# docker pull alpine
# docker pull ubuntu

Then set a tag and push images to our Registry:

# docker tag ubuntu registry.computingforgeeks.com:5000/ubuntu:v1
# docker push registry.computingforgeeks.com:5000/ubuntu:v1
The push refers to repository [registry.computingforgeeks.com:5000/ubuntu]
268a067217b5: Pushed 
c01d74f99de4: Pushed 
ccd4d61916aa: Pushed 
8f2b771487e9: Pushed 
f49017d4d5ce: Pushed 
v1: digest: sha256:958eaeb7e33e6c4f68f7fef69b35ca178c7f5fb0dd40db7b44a8b9eb692b9bc5 size: 1357

Verify using docker images command:

# docker images
REPOSITORY                                   TAG                 IMAGE ID            CREATED             SIZE
ubuntu                                       latest              735f80812f90        8 days ago          83.5MB
registry.computingforgeeks.com:5000/ubuntu   v1                  735f80812f90        8 days ago          83.5MB
registry                                     2                   b2b03e9146e1        4 weeks ago         33.3MB
alpine                                       latest              11cd0b38bc3c        4 weeks ago         4.41MB

 # curl https://registry.computingforgeeks.com:5000/v2/_catalog 
{"repositories":["ubuntu"]}

# # ls /var/lib/docker/registry/docker/registry/v2/repositories/
ubuntu

On Docker hosts, you can pull the image using:

# docker pull registry.computingforgeeks.com:5000/ubuntu:v1
v1: Pulling from ubuntu
c64513b74145: Pull complete 
01b8b12bad90: Pull complete 
c5d85cf7a05f: Pull complete 
b6b268720157: Pull complete 
e12192999ff1: Pull complete 
Digest: sha256:958eaeb7e33e6c4f68f7fef69b35ca178c7f5fb0dd40db7b44a8b9eb692b9bc5
Status: Downloaded newer image for registry.computingforgeeks.com:5000/ubuntu:v1

The image should now be visible:

# docker images
REPOSITORY                                   TAG                 IMAGE ID            CREATED             SIZE
registry.computingforgeeks.com:5000/ubuntu   v1                  735f80812f90        8 days ago          83.5MB
busybox                                      latest              22c2dd5ee85d        2 weeks ago         1.16MB
gliderlabs/herokuish                         latest              ce23b0b51ca7        2 months ago        914MB
gliderlabs/herokuish                         v0.4.2              ce23b0b51ca7        2 months ago        914MB
postgres                                     10.2                c12289de6f88        5 months ago        288MB
dokkupaas/s3backup                           0.8.0               7c096c5ae8fd        11 months ago       98.5MB
dokkupaas/wait                               0.2                 f191c874dade        23 months ago       9.08MB
svendowideit/ambassador                      latest              8f4da6f7d98a        2 years ago         8.03MB

Start Registry with Authentication support

Except for registries running on secure local networks, registries should always implement access restrictions. The simplest way to achieve access restriction is through basic authentication:

Create a password file with one entry for the user, dockadmin with password registrypassword:

$ docker run \
--entrypoint htpasswd \
registry:2 -Bbn dockadmin registrypassword > ~/.htpasswd

$ cat ~/.htpasswd
dockadmin:$2y$05$WN6moKCdUB1i3lFloCPR/Oaw5b4aVcphXSUlEHuRMD2knofj1FuBW

Remove current docker registry.

$ docker rm -f docker-registry

Start the registry with basic authentication.

$ docker run -d --name docker-registry --restart=always \
-p 5000:5000 \
-v ~/.htpasswd:/auth_htpasswd \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth_htpasswd \
-e REGISTRY_HTTP_ADDR=0.0.0.0:5000 \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/fullchain.pem \
-e REGISTRY_HTTP_TLS_KEY=/certs/privkey.pem \
-v /certs:/certs \
-v /var/lib/docker/registry:/var/lib/registry \
registry:2

Try to pull an image from the registry, or push an image to the registry. These commands fail.

$ docker pull registry.computingforgeeks.com:5000/ubuntu:v1
Error response from daemon: Get https://registry.computingforgeeks.com:5000/v2/ubuntu/manifests/v1: no basic auth credentials

You need to log in to the registry.

# docker login registry.computingforgeeks.com:5000
Username: dockadmin
Password: 
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

# cat /root/.docker/config.json
{
	"auths": {
		"registry.computingforgeeks.com:5000": {
			"auth": "ZG9ja2FkbWluOnJlZ2lzdHJ5cGFzc3dvcmQ="
		}
	},
	"HttpHeaders": {
		"User-Agent": "Docker-Client/18.06.0-ce (linux)"
	}
}

You should now be able to download and push images to the repository:

$ docker tag alpine registry.computingforgeeks.com:5000/alpine_local
$ docker push registry.computingforgeeks.com:5000/alpine_local
The push refers to repository [registry.computingforgeeks.com:5000/alpine_local]
73046094a9b8: Pushed 
latest: digest: sha256:0873c923e00e0fd2ba78041bfb64a105e1ecb7678916d1f7776311e45bf5634b size: 528

Stop a local Docker registry

To stop the registry, use the same docker container stop command as with any other container.

$ docker container stop docker-registry

To remove the container, use docker container rm.

$ docker container stop registry
$ docker container rm -v registry
$ docker container rm -f -v registry # Force remove running

Conclusion

You now have a working Local Docker registry, you’re free to choose the deployment that suits your need; registry without SSL, registry with SSL but now authentication or Registry with SSL and Basic Authentication. I recommend setting up secure private Docker registry for production environments – This will have both SSL and Authentication.