Skip to Content

Docker private registry - setup

Want to create own docker registry? Cheat sheet

Share on:

It is very common to setup own Docker registry for our private images.

Basic registry setup

If we want basic setup without TLS and any access control for example for lab, we can create it with command:

[lukas@docker-host ~]$ docker run -d -p 5000:5000 --restart=always --name registry registry:2
Unable to find image 'registry:2' locally
2: Pulling from library/registry
486039affc0a: Pull complete
ba51a3b098e6: Pull complete
8bb4c43d6c8e: Pull complete
6f5f453e5f2d: Pull complete
42bc10b72f42: Pull complete
Digest: sha256:7d081088e4bfd632a88e3f3bcd9e007ef44a796fddfe3261407a3f9f04abe1e7
Status: Downloaded newer image for registry:2
8f0fec4813cd6dc1d6fc65b8530dd2c3ca413a4e42904debf3005843220bcdb3

[lukas@docker-host ~]$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
8f0fec4813cd        registry:2          "/entrypoint.sh /etc…"   6 seconds ago       Up 5 seconds        0.0.0.0:5000->5000/tcp   registry

Docker daemon started registry with 5000 port exposed(we can change it here if we want), named registry using registry:2 image.
Registry will be started with Docker host and will be restarted after every failure by --restart=always policy.

We can operate on it with standard container commands like docker stop.
It is normal container with proper service in it.

Testing push and pull to private registry

Push

First tag our local image properly:

[lukas@docker-host ~]$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
alpine              latest              a187dde48cd2        3 weeks ago         5.6MB
ubuntu              latest              4e5021d210f6        3 weeks ago         64.2MB
registry            2                   708bc6af7e5e        2 months ago        25.8MB

[lukas@docker-host ~]$ docker tag alpine:latest localhost:5000/alpine:latest

Push image to private registry:

[lukas@docker-host ~]$ docker push localhost:5000/alpine:latest
The push refers to repository [localhost:5000/alpine]
beee9f30bc1f: Pushed
latest: digest: sha256:cb8a924afdf0229ef7515d9e5b3024e23b3eb03ddbba287f4a19c6ac90b8d221 size: 528

Pull

Remove image from local registry of Docker host:

[lukas@docker-host ~]$ docker image rm localhost:5000/alpine
Untagged: localhost:5000/alpine:latest
Untagged: localhost:5000/alpine@sha256:cb8a924afdf0229ef7515d9e5b3024e23b3eb03ddbba287f4a19c6ac90b8d221

Pull image from private registry in container:

[lukas@docker-host ~]$ docker pull localhost:5000/alpine
Using default tag: latest
latest: Pulling from alpine
Digest: sha256:cb8a924afdf0229ef7515d9e5b3024e23b3eb03ddbba287f4a19c6ac90b8d221
Status: Downloaded newer image for localhost:5000/alpine:latest
localhost:5000/alpine:latest

[lukas@docker-host ~]$ docker image ls
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
alpine                  latest              a187dde48cd2        3 weeks ago         5.6MB
localhost:5000/alpine   latest              a187dde48cd2        3 weeks ago         5.6MB
ubuntu                  latest              4e5021d210f6        3 weeks ago         64.2MB
registry                2                   708bc6af7e5e        2 months ago        25.8MB

Advanced registry setup

Check storage location for registry

By default registry container use volumes to store data.

[lukas@docker-host ~]$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
8f0fec4813cd        registry:2          "/entrypoint.sh /etc…"   21 minutes ago      Up 21 minutes       0.0.0.0:5000->5000/tcp   registry

[lukas@docker-host ~]$ docker inspect 8f0fec4813cd
[
    {
        "Id": "8f0fec4813cd6dc1d6fc65b8530dd2c3ca413a4e42904debf3005843220bcdb3",
        "Created": "2020-04-14T11:49:37.10564264Z",
        "Path": "/entrypoint.sh",
        "Args": [
            "/etc/docker/registry/config.yml"
        ],
        "State": {
            "Status": "running",
          [...]
          [...]
          "Mounts": [
            {
                "Type": "volume",
                "Name": "6be22c2b38d6d2d4399e16e3d6a8fa5773db550a1d334423743148e8dfb9387e",
                "Source": "/var/lib/docker/volumes/6be22c2b38d6d2d4399e16e3d6a8fa5773db550a1d334423743148e8dfb9387e/_data",
                "Destination": "/var/lib/registry",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            [...]

From inspect command on registry container we know that /var/lib/registry in it - has got mounted /var/lib/docker/volumes/6be22c2b38d6d2d4399e16e3d6a8fa5773db550a1d334423743148e8dfb9387e/_data from docker host.

Checking location - there is our apline image pushed earlier:

[root@docker-host ~]# ls -lah /var/lib/docker/volumes/6be22c2b38d6d2d4399e16e3d6a8fa5773db550a1d334423743148e8dfb9387e/_data/docker/registry/v2/
total 0
drwxr-xr-x. 4 root root 39 Apr 14 13:54 .
drwxr-xr-x. 3 root root 16 Apr 14 13:54 ..
drwxr-xr-x. 3 root root 20 Apr 14 13:54 blobs
drwxr-xr-x. 3 root root 20 Apr 14 13:54 repositories

[root@docker-host ~]# ls -lah /var/lib/docker/volumes/6be22c2b38d6d2d4399e16e3d6a8fa5773db550a1d334423743148e8dfb9387e/_data/docker/registry/v2/repositories/
total 0
drwxr-xr-x. 3 root root 20 Apr 14 13:54 .
drwxr-xr-x. 4 root root 39 Apr 14 13:54 ..
drwxr-xr-x. 5 root root 55 Apr 14 13:54 alpine

Set custom storage

We can set custom volume location by adding parameter
-v <docker_host_dir>:/var/lib/registry to docker run command.

Set TLS to expose registry wider that localhost

If you want expose registry with dns name, to wider range of people and servers, configure TLS.

To do so:

  • get cert and key from CA
  • put into cert location on docker host cert and key:
[lukas@docker-host ~]$ mkdir certs

[lukas@docker-host ~]$ mv domain.* certs/
  • start registry container with additional parameters:
 -v <docker_host_cert_dir>:/certs  
 -e REGISTRY_HTTP_ADDR=0.0.0.0:443  
 -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/<your_domain_certificate>.crt  
 -e REGISTRY_HTTP_TLS_KEY=/certs/<your_domain_key>.key  
[lukas@docker-host ~]$ docker run -d   --restart=always   --name registry   -v "$(pwd)"/certs:/certs   -e REGISTRY_HTTP_ADDR=0.0.0.0:443   -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt   -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key   -p 443:443   registry:2
037409886c9505e5dd47a00fcd13a7684ae586b1133eb8273b0a95cd6a1a70a6

With proper DNS setup you can now use your domain name for pointing private reposiory.

Registry restricted access setup

If you want to restrict access to registry by setting login and password generate password file for user.
For example lukas username and supersecreatpassword123 for password.

Below command starts new registry container with modified entrypoint script that will generate hash from given username and password parameters.

[lukas@docker-host ~]$ docker run   --entrypoint htpasswd   registry:2 -Bbn lukas supersecretpassword123 > auth/htpasswd

[lukas@docker-host ~]$ cat auth/htpasswd
lukas:$2y$05$0fIUyTyXPFvJ573I3voFWuienv/Pf2DbclRg/2.ASZFq42hBY1yt6

Now we can start our registry(if you have already started stop it and remove) with additional parameters that activated restricted access

-v <docker_host_password_file_dir>:/auth
-e "REGISTRY_AUTH=htpasswd"
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm"
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
[lukas@docker-host ~]$ docker run -d   -p 443:443   --restart=always   --name registry   -v "$(pwd)"/auth:/auth   -e "REGISTRY_AUTH=htpasswd"   -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm"   -e REGISTRY_HTTP_ADDR=0.0.0.0:443   -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd   -v "$(pwd)"/certs:/certs   -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt   -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key   registry:2
5fcd16c838c5aa2d7ce86c06304dee7890c0e7b1b62a0a36e5fb8811e201dab7

Test login

Without login we get failure:

[lukas@docker-host ~]$ docker push localhost:443/alpine:latest
The push refers to repository [localhost:443/alpine]
beee9f30bc1f: Preparing
no basic auth credentials

Now we will login:

[lukas@docker-host ~]$ docker login localhost:443
Username: lukas
Password:
WARNING! Your password will be stored unencrypted in /home/lukas/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
[lukas@docker-host ~]$ docker push localhost:443/alpine:latest
The push refers to repository [localhost:443/alpine]
beee9f30bc1f: Pushed
latest: digest: sha256:cb8a924afdf0229ef7515d9e5b3024e23b3eb03ddbba287f4a19c6ac90b8d221 size: 528