How to setup private docker registry for your projects to save money
Tags: docker, devops, privacy, tutorial
Published: 2019-07-17 16:03:54

I got to use docker for my projects which are not open-sourced, some of them are hobby projects or try-outs which I don't want yet to publish, so I need to store them privately. 

DockerHub only allows 1 private image to be stored in the Free plan, 5 images for 7$ and 10 for 12$ which is decent price for production projects, but for personal hobby projects may be a lot.

I have a 5$ server on DigitalOcean for personal needs, which I want also to use as a docker registry, and CI for my private projects.

The steps in this guide are not the only one possible, there are many ways to achieve same result, but I tried to make it as quick and as simple as possible, I will describe each configuration why it is required.

# Prerequisites

1. I am using Ubuntu 18.04, so all commands will be related to this system.

2. You need to have Docker and Docker Compose installed on your host  Official docs:
  https://docs.docker.com/install/linux/docker-ce/ubuntu/


3. You need to have an installed **nginx**.


4. Examples assume you have a domain: `example.com`, and that you want to start registry on: `registry.example.com`


# Step 1: Setup nginx

I don't use nginx as docker container as I have also some other static sites on same machine, which I don't want to wrap into containers. And also I find it easier to have nginx outside containers and on the host machine.

ssh to host machine and install nginx, if you don't have it yet

```shell
apt install nginx

```

Then create nginx configuration for your domain:

`/etc/nginx/sites-enabled/registry.example.com`

```nginx
upstream docker-registry {
  server 127.0.0.1:5000;
}

server {
    server_name registry.example.com;

    server_name_in_redirect off;

    # disable any limits to avoid HTTP 413 for large image uploads
    client_max_body_size 0;

    # required to avoid HTTP 411: see Issue #1486 (https://github.com/moby/moby/issues/1486)
    chunked_transfer_encoding on;

    location /v2/ {
      # Do not allow connections from docker 1.5 and earlier
      # docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
      if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
        return 404;
      }

      proxy_pass                          http://docker-registry;
      proxy_set_header  Host              $http_host;   # required for docker client's sake
      proxy_set_header  X-Real-IP         $remote_addr; # pass on real client's IP
      proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
      proxy_set_header  X-Forwarded-Proto $scheme;
      proxy_read_timeout                  900;
    }
}

```

Because we would use BasicAuth to authenticate to registry we need to setup SSL so that our login/password is not taken by man in the middle attack.
Currently this nginx config does not support SSL which is required to make our registry accessible from outside.
We would use CertBot to update our nginx configuration to support SSL.


# Step 2: CertBot

Install CertBot as described here: https://certbot.eff.org/lets-encrypt/ubuntubionic-nginx

Now you can run command:

```shell
certbot --nginx -d registry.example.com

```

It would modify `/etc/nginx/sites-enabled/registry.example.com` so it supports SSL.


# Step 3: Docker Registry

Official doc is here: https://docs.docker.com/registry/deploying/
The official doc is quite long, and describes lots of options for different use cases

Just connect via ssh to your host, go to /root folder. 

```
mkdir docker-volumes
mkdir docker-volumes/registry/
mkdir docker-volumes/registry/registry
mkdir docker-volumes/registry/auth
```

Create there such `docker-compose.yml` file:

```
version: "3"
services:
  registry:
    restart: always
    image: 'registry:2'
    ports:
      - "5000:5000"
    environment:
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
    volumes:
      - /root/docker-volumes/registry/registry:/var/lib/registry
      - /root/docker-volumes/registry/auth:/auth
```

# Step 4: Authentication

Then we need to restrict access to registry by login/password, we would use BasicAuth. To do so we need to generate htpasswd file which we would put into registry container. Change testuser/testpassword to your own.

```shell
docker run --entrypoint htpasswd registry:2 -Bbn testuser testpassword > ./docker-volumes/registry/auth/htpasswd

```

# Step 5: Starting it all up
Now we can start registry, run:

```shell
docker-compose up -d

```

Also restart nginx so it loads new config for registry.example.com
```shell

/etc/init.d/nginx restart
```

# Step 6: Verify

In order to check that we configured everything correctly, go to console on your local machine, which you would use to build images (your laptop probably), and try to login:

```shell
docker login registry.example.com
```

Use credentials from Step 4


Now it should be all up. In upcoming posts I will write how to setup your private CI using Drone.io so it can use your private registry in its builds.