# How To Set Up a Private Docker Registry on Ubuntu

## [Step 1 — Installing and Configuring the Docker Registry](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-private-docker-registry-on-ubuntu-20-04#step-1-installing-and-configuring-the-docker-registry)

Docker on the command line is useful when starting out and testing containers, but proves to be unwieldy for bigger deployments involving multiple containers running in parallel.

With Docker Compose, you can write one `.yml` file to set up each container’s configuration and information the containers need to communicate with each other. You can use the `docker-compose` command-line tool to issue commands to all the components that make up your application, and control them as a group.

Docker Registry is itself an application with multiple components, so you will use Docker Compose to manage it. To start an instance of the registry, you’ll set up a `docker-compose.yml` file to define it and the location on disk where your registry will be storing its data.

You’ll store the configuration in a directory called `docker-registry` on the main server. Create it by running:

<div class="code-toolbar" id="bkmrk-mkdir-%7E%2Fdocker-regis"><div class="code-toolbar">1. <span class="token function">mkdir</span> ~/docker-registry

</div></div><div class="code-toolbar" id="bkmrk-copy"><div class="toolbar"></div></div>Navigate to it:

<div class="code-toolbar" id="bkmrk-cd-%7E%2Fdocker-registry"><div class="code-toolbar">1. <span class="token builtin class-name">cd</span> ~/docker-registry

</div></div><div class="code-toolbar" id="bkmrk-copy-1"><div class="toolbar"></div></div>Then, create a subdirectory called `data`, where your registry will store its images:

<div class="code-toolbar" id="bkmrk-mkdir-data"><div class="code-toolbar">1. <span class="token function">mkdir</span> data

</div></div><div class="code-toolbar" id="bkmrk-copy-2"><div class="toolbar"><div class="toolbar-item">  
</div></div></div>Create and open a file called `docker-compose.yml` by running:

<div class="code-toolbar" id="bkmrk-nano-docker-compose."><div class="code-toolbar">1. <span class="token function">nano</span> docker-compose.yml

</div></div><div class="code-toolbar" id="bkmrk-copy-3"><div class="toolbar"></div></div>Add the following lines, which define a basic instance of a Docker Registry:

<div class="code-label" id="bkmrk-%7E%2Fdocker-registry%2Fdo" title="~/docker-registry/docker-compose.yml">~/docker-registry/docker-compose.yml</div>```
version: '3'

services:
  registry:
    image: registry:2
    ports:
    - "5000:5000"
    environment:
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
    volumes:
      - ./data:/data

```

First, you name the first service `registry`, and set its image to `registry`, version `2`. Then, under `ports`, you map the port `5000` on the host to the port `5000` of the container. This allows you to send a request to port `5000` on the server, and have the request forwarded to the registry.

In the `environment` section, you set the `REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY` variable to `/data`, specifying in which volume it should store its data. Then, in the `volumes` section, you map the `/data` directory on the host file system to `/data` in the container, which acts as a passthrough. The data will actually be stored on the host’s file system.

Save and close the file.

You can now start the configuration by running:

<div class="code-toolbar" id="bkmrk-docker-compose-up"><div class="code-toolbar">1. <span class="token function">docker-compose</span> up

</div></div><div class="code-toolbar" id="bkmrk-copy-4"><div class="toolbar"></div></div>The registry container and its dependencies will be downloaded and started.

You’ll address the `No HTTP secret provided` warning message later in this tutorial. Notice that the last line of the output shows it has successfully started listening on port `5000`.

You can press `CTRL+C` to stop its execution.

In this step, you have created a Docker Compose configuration that starts a Docker Registry listening on port `5000`. In the next steps, you’ll expose it at your domain and set up authentication.

## [Step 2 — Setting Up Nginx Port Forwarding](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-private-docker-registry-on-ubuntu-20-04#step-2-setting-up-nginx-port-forwarding)

As part of the prerequisites, you’ve enabled HTTPS at your domain. To expose your secured Docker Registry there, you’ll only need to configure Nginx to forward traffic from your domain to the registry container.

You have already set up the `/etc/nginx/sites-available/<mark>your_domain</mark>` file, containing your server configuration. Open it for editing by running:

<div class="code-toolbar" id="bkmrk-sudo-nano-%2Fetc%2Fnginx"><div class="code-toolbar">1. <span class="token function">sudo</span> <span class="token function">nano</span> /etc/nginx/sites-available/<mark>your\_domain</mark>

</div></div><div class="code-toolbar" id="bkmrk-copy-5"><div class="toolbar"><div class="toolbar-item">  
</div></div></div>Find the existing `location` block:

<div class="code-label" id="bkmrk-%2Fetc%2Fnginx%2Fsites-ava" title="/etc/nginx/sites-available/your_domain">/etc/nginx/sites-available/your_domain</div>```
...
location / {
  ...
}
...

```

You need to forward traffic to port `5000`, where your registry will be listening for traffic. You also want to append headers to the request forwarded to the registry, which provides additional information from the server about the request itself. Replace the existing contents of the `location` block with the following lines:

<div class="code-label" id="bkmrk-%2Fetc%2Fnginx%2Fsites-ava-1" title="/etc/nginx/sites-available/your_domain">/etc/nginx/sites-available/your_domain</div>```
...
location / {
    # 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://localhost:5000;
    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;
}
...

```

The `if` block checks the user agent of the request and verifies that the version of the Docker client is above 1.5, as well as that it’s not a `Go` application that’s trying to access. For more explanation on this, you can find the `nginx` header configuration in [Docker’s registry Nginx guide](https://docs.docker.com/registry/recipes/nginx/#setting-things-up).

Save and close the file when you’re done. Apply the changes by restarting Nginx:

<div class="code-toolbar" id="bkmrk-sudo-systemctl-resta"><div class="code-toolbar">1. <span class="token function">sudo</span> systemctl restart nginx

</div></div><div class="code-toolbar" id="bkmrk-copy-6"><div class="toolbar"></div></div>If you get an error, double-check the configuration you’ve added.

To confirm that Nginx is properly forwarding traffic to your registry container on port `5000`, run it:

<div class="code-toolbar" id="bkmrk-docker-compose-up-1"><div class="code-toolbar">1. <span class="token function">docker-compose</span> up

</div></div><div class="code-toolbar" id="bkmrk-copy-7"><div class="toolbar"></div></div>Then, in a browser window, navigate to your domain and access the `v2` endpoint, like so:

```
https://<mark>your_domain</mark>/v2

```

You will see an empty JSON object:

```
{}

```

<div class="code-toolbar" id="bkmrk-copy-8"><div class="toolbar"><div class="toolbar-item">  
</div></div></div>In your terminal, you’ll receive output similar to the following:

<div class="secondary-code-label" id="bkmrk-output" title="Output">Output</div>```
registry_1  | time="2018-11-07T17:57:42Z" level=info msg="response completed" go.version=go1.7.6 http.request.host=cornellappdev.com http.request.id=a8f5984e-15e3-4946-9c40-d71f8557652f http.request.method=GET http.request.remoteaddr=128.84.125.58 http.request.uri="/v2/" http.request.useragent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/604.4.7 (KHTML, like Gecko) Version/11.0.2 Safari/604.4.7" http.response.contenttype="application/json; charset=utf-8" http.response.duration=2.125995ms http.response.status=200 http.response.written=2 instance.id=3093e5ab-5715-42bc-808e-73f310848860 version=v2.6.2
registry_1  | 172.18.0.1 - - [07/Nov/2018:17:57:42 +0000] "GET /v2/ HTTP/1.0" 200 2 "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/604.4.7 (KHTML, like Gecko) Version/11.0.2 Safari/604.4.7"

```

You can see from the last line that a `GET` request was made to `/v2/`, which is the endpoint you sent a request to, from your browser. The container received the request you made, from the port forwarding, and returned a response of `{}`. The code `200` in the last line of the output means that the container handled the request successfully.

Press `CTRL+C` to stop its execution.

Now that you have set up port forwarding, you’ll move on to improving the security of your registry.

## [Step 3 — Setting Up Authentication](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-private-docker-registry-on-ubuntu-20-04#step-3-setting-up-authentication)

Nginx allows you to set up HTTP authentication for the sites it manages, which you can use to limit access to your Docker Registry. To achieve this, you’ll create an authentication file with `htpasswd` and add username and password combinations to it that will be accepted.

You can obtain the `htpasswd` utility by installing the `apache2-utils` package. Do so by running:

<div class="code-toolbar" id="bkmrk-sudo-apt-install-apa"><div class="code-toolbar">1. <span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> apache2-utils <span class="token parameter variable">-y</span>

</div></div><div class="code-toolbar" id="bkmrk-copy-9"><div class="toolbar"><div class="toolbar-item">  
</div></div></div>You’ll store the authentication file with credentials under `~/docker-registry/auth`. Create it by running:

<div class="code-toolbar" id="bkmrk-mkdir-%7E%2Fdocker-regis-1"><div class="code-toolbar">1. <span class="token function">mkdir</span> ~/docker-registry/auth

</div></div><div class="code-toolbar" id="bkmrk-copy-10"><div class="toolbar"></div></div>Navigate to it:

<div class="code-toolbar" id="bkmrk-cd-%7E%2Fdocker-registry-1"><div class="code-toolbar">1. <span class="token builtin class-name">cd</span> ~/docker-registry/auth

</div></div><div class="code-toolbar" id="bkmrk-copy-11"><div class="toolbar"></div></div>Create the first user, replacing `<mark>username</mark>` with the username you want to use. The `-B` flag orders the use of the `bcrypt` algorithm, which Docker requires:

<div class="code-toolbar" id="bkmrk-htpasswd--bc-registr"><div class="code-toolbar">1. htpasswd <span class="token parameter variable">-Bc</span> registry.password <mark>username</mark>

</div></div><div class="code-toolbar" id="bkmrk-copy-12"><div class="toolbar"></div></div>Enter the password when prompted, and the combination of credentials will be appended to `registry.password`.

**Note:** To add more users, re-run the previous command without `-c`, which creates a new file:

<div class="callout note" id="bkmrk-htpasswd--b-registry"><div class="callout note"><div class="code-toolbar">1. htpasswd <span class="token parameter variable">-B</span> registry.password <mark>username</mark>

</div></div></div>```
```

<div class="callout note" id="bkmrk-copy-13"><div class="code-toolbar"><div class="toolbar"><div class="toolbar-item"><button type="button">Copy</button></div></div></div></div>Now that the list of credentials is made, you’ll edit `docker-compose.yml` to order Docker to use the file you created to authenticate users. Open it for editing by running:

<div class="code-toolbar" id="bkmrk-nano-%7E%2Fdocker-regist"><div class="code-toolbar">1. <span class="token function">nano</span> ~/docker-registry/docker-compose.yml

</div></div><div class="code-toolbar" id="bkmrk-copy-14"><div class="toolbar"></div></div>Add the highlighted lines:

<div class="code-label" id="bkmrk-%7E%2Fdocker-registry%2Fdo-1" title="~/docker-registry/docker-compose.yml">~/docker-registry/docker-compose.yml</div>```
version: '3'

services:
  registry:
    image: registry:2
    ports:
    - "5000:5000"
    environment:
      <mark>REGISTRY_AUTH: htpasswd</mark>
      <mark>REGISTRY_AUTH_HTPASSWD_REALM: Registry</mark>
      <mark>REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.password</mark>
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
    volumes:
      <mark>- ./auth:/auth</mark>
      - ./data:/data

```

You’ve added environment variables specifying the use of HTTP authentication and provided the path to the file `htpasswd` created. For `REGISTRY_AUTH`, you have specified `htpasswd` as its value, which is the authentication scheme you are using, and set `REGISTRY_AUTH_HTPASSWD_PATH` to the path of the authentication file. `REGISTRY_AUTH_HTPASSWD_REALM` signifies the name of `htpasswd` realm.

You’ve also mounted the `./auth` directory to make the file available inside the registry container. Save and close the file.

You can now verify that your authentication works correctly. First, navigate to the main directory:

<div class="code-toolbar" id="bkmrk-cd-%7E%2Fdocker-registry-2"><div class="code-toolbar">1. <span class="token builtin class-name">cd</span> ~/docker-registry

</div></div><div class="code-toolbar" id="bkmrk-copy-15"><div class="toolbar"></div></div>Then, run the registry by executing:

<div class="code-toolbar" id="bkmrk-docker-compose-up-2"><div class="code-toolbar">1. <span class="token function">docker-compose</span> up

</div></div><div class="code-toolbar" id="bkmrk-copy-16"><div class="toolbar"></div></div>In your browser, refresh the page of your domain. You’ll be asked for a username and password.

After providing a valid combination of credentials, you’ll see an empty JSON object:

```
{}

```

<div class="code-toolbar" id="bkmrk-copy-17"><div class="toolbar"></div></div>This means that you’ve successfully authenticated and gained access to the registry. Exit by pressing `CTRL+C`.

Your registry is now secured and can be accessed only after authentication. You’ll now configure it to run as a background process while being resilient to reboots by starting automatically.

## [Step 4 — Starting Docker Registry as a Service](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-private-docker-registry-on-ubuntu-20-04#step-4-starting-docker-registry-as-a-service)

You can ensure that the registry container starts every time the system boots up, or after it crashes, by instructing Docker Compose to always keep it running. Open `docker-compose.yml` for editing:

<div class="code-toolbar" id="bkmrk-nano-docker-compose.-1"><div class="code-toolbar">1. <span class="token function">nano</span> docker-compose.yml

</div></div><div class="code-toolbar" id="bkmrk-copy-18"><div class="toolbar"></div></div>Add the following line under the `registry` block:

<div class="code-label" id="bkmrk-docker-compose.yml" title="docker-compose.yml">docker-compose.yml</div>```
...
  registry:
    <mark>restart: always</mark>
...

```

Setting `restart` to always ensures that the container will survive reboots. When you’re done, save and close the file.

You can now start your registry as a background process by passing in `-d`:

<div class="code-toolbar" id="bkmrk-docker-compose-up--d"><div class="code-toolbar">1. <span class="token function">docker-compose</span> up <span class="token parameter variable">-d</span>

</div></div><div class="code-toolbar" id="bkmrk-copy-19"><div class="toolbar"></div></div>With your registry running in the background, you can freely close the SSH session, and the registry won’t be affected.

Because Docker images may be very large in size, you’ll now increase the maximum file size that Nginx will accept for uploads.

## [Step 5 — Increasing File Upload Size for Nginx](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-private-docker-registry-on-ubuntu-20-04#step-5-increasing-file-upload-size-for-nginx)

Before you can push an image to the registry, you need to ensure that your registry will be able to handle large file uploads.

The default size limit of file uploads in Nginx is `1m`, which is not nearly enough for Docker images. To raise it, you’ll modify the main Nginx config file, located at `/etc/nginx/nginx.conf`. Open it for editing by running:

<div class="code-toolbar" id="bkmrk-sudo-nano-%2Fetc%2Fnginx-1"><div class="code-toolbar">1. <span class="token function">sudo</span> <span class="token function">nano</span> /etc/nginx/nginx.conf

</div></div><div class="code-toolbar" id="bkmrk-copy-20"><div class="toolbar"></div></div>Find the `http` section, and add the following line:

<div class="code-label" id="bkmrk-%2Fetc%2Fnginx%2Fnginx.con" title="/etc/nginx/nginx.conf">/etc/nginx/nginx.conf</div>```
...
http {
        <mark>client_max_body_size 16384m;</mark>
        ...
}
...

```

The `client_max_body_size` parameter is now set to `16384m`, making the maximum upload size equal to 16GB.

Save and close the file when you’re done.

Restart Nginx to apply the configuration changes:

<div class="code-toolbar" id="bkmrk-sudo-systemctl-resta-1"><div class="code-toolbar">1. <span class="token function">sudo</span> systemctl restart nginx

</div></div><div class="code-toolbar" id="bkmrk-copy-21"><div class="toolbar"></div></div>You can now upload large images to your Docker Registry without Nginx blocking the transfer or erroring out.

## [Step 6 — Publishing to Your Private Docker Registry](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-private-docker-registry-on-ubuntu-20-04#step-6-publishing-to-your-private-docker-registry)

Now that your Docker Registry server is up and running, and accepting large file sizes, you can try pushing an image to it. Since you don’t have any images readily available, you’ll use the `ubuntu` image from Docker Hub, a public Docker Registry, to test.

From your second, client server, run the following command to download the `ubuntu` image, run it, and get access to its shell:

<div class="code-toolbar" id="bkmrk-docker-run--t--i-ubu"><div class="code-toolbar">1. <span class="token function">docker</span> run <span class="token parameter variable">-t</span> <span class="token parameter variable">-i</span> ubuntu /bin/bash

</div></div><div class="code-toolbar" id="bkmrk-copy-22"><div class="toolbar"></div></div>The `-i` and `-t` flags give you interactive shell access into the container.

Once you’re in, create a file called `SUCCESS` by running:

<div class="code-toolbar" id="bkmrk-touch-%2Fsuccess"><div class="code-toolbar">1. <span class="token function">touch</span> /SUCCESS

</div></div><div class="code-toolbar" id="bkmrk-copy-23"><div class="toolbar"></div></div>By creating this file, you have customized your container. You’ll later use it to check that you’re using exactly the same container.

Exit the container shell by running:

<div class="code-toolbar" id="bkmrk-exit"><div class="code-toolbar">1. <span class="token builtin class-name">exit</span>

</div></div><div class="code-toolbar" id="bkmrk-copy-24"><div class="toolbar"></div></div>Now, create a new image from the container you’ve just customized:

<div class="code-toolbar" id="bkmrk-docker-commit-%24%28dock"><div class="code-toolbar">1. <span class="token function">docker</span> commit <span class="token variable">$(<span class="token function">docker</span> <span class="token function">ps</span> <span class="token parameter variable">-lq</span>)</span> test-image

</div></div><div class="code-toolbar" id="bkmrk-copy-25"><div class="toolbar"></div></div>The new image is now available locally, and you’ll push it to your new container registry. First, you have to log in:

<div class="code-toolbar" id="bkmrk-docker-login-https%3A%2F"><div class="code-toolbar">1. <span class="token function">docker</span> login https://<mark>your\_domain</mark>

</div></div><div class="code-toolbar" id="bkmrk-copy-26"><div class="toolbar"></div></div>When prompted, enter in a username and password combination that you’ve defined in step 3 of this tutorial.

The output will be:

<div class="secondary-code-label" id="bkmrk-output-1" title="Output">Output</div>```
...
Login Succeeded

```

Once you’re logged in, rename the created image:

<div class="code-toolbar" id="bkmrk-docker-tag-test-imag"><div class="code-toolbar">1. <span class="token function">docker</span> tag test-image <mark>your\_domain</mark>/test-image

</div></div><div class="code-toolbar" id="bkmrk-copy-27"><div class="toolbar"></div></div>Finally, push the newly tagged image to your registry:

<div class="code-toolbar" id="bkmrk-docker-push-your_dom"><div class="code-toolbar">1. <span class="token function">docker</span> push <mark>your\_domain</mark>/test-image

</div></div><div class="code-toolbar" id="bkmrk-copy-28"><div class="toolbar"></div></div>You’ll receive output similar to the following:

<div class="secondary-code-label" id="bkmrk-output-2" title="Output">Output</div>```
The push refers to a repository [<mark>your_domain</mark>/test-image]
420fa2a9b12e: Pushed
c20d459170d8: Pushed
db978cae6a05: Pushed
aeb3f02e9374: Pushed
latest: digest: sha256:88e782b3a2844a8d9f0819dc33f825dde45846b1c5f9eb4870016f2944fe6717 size: 1150

```

You’ve verified that your registry handles user authentication by logging in, and allows authenticated users to push images to the registry. You’ll now try pulling the image from your registry.

## [Step 7 — Pulling From Your Private Docker Registry](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-private-docker-registry-on-ubuntu-20-04#step-7-pulling-from-your-private-docker-registry)

Now that you’ve pushed an image to your private registry, you’ll try pulling from it.

On the main server, log in with the username and password you set up previously:

<div class="code-toolbar" id="bkmrk-docker-login-https%3A%2F-1"><div class="code-toolbar">1. <span class="token function">docker</span> login https://<mark>your\_domain</mark>

</div></div><div class="code-toolbar" id="bkmrk-copy-29"><div class="toolbar"></div></div>Try pulling the `test-image` by running:

<div class="code-toolbar" id="bkmrk-docker-pull-your_dom"><div class="code-toolbar">1. <span class="token function">docker</span> pull <mark>your\_domain</mark>/test-image

</div></div><div class="code-toolbar" id="bkmrk-copy-30"><div class="toolbar"></div></div>Docker should download the image. Run the container with the following command:

<div class="code-toolbar" id="bkmrk-docker-run--it-your_"><div class="code-toolbar">1. <span class="token function">docker</span> run <span class="token parameter variable">-it</span> <mark>your\_domain</mark>/test-image /bin/bash

</div></div><div class="code-toolbar" id="bkmrk-copy-31"><div class="toolbar"></div></div>List the files present by running:

<div class="code-toolbar" id="bkmrk-ls"><div class="code-toolbar">1. <span class="token function">ls</span>

</div></div><div class="code-toolbar" id="bkmrk-copy-32"><div class="toolbar"></div></div>You will see the `SUCCESS` file you’ve created earlier, confirming that its the same image you’ve created:

```
SUCCESS  bin  boot  dev  etc  home  lib  lib64  media   mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

```

Exit the container shell by running:

<div class="code-toolbar" id="bkmrk-exit-1"><div class="code-toolbar">1. <span class="token builtin class-name">exit</span>

</div></div><div class="code-toolbar" id="bkmrk-copy-33"><div class="toolbar"></div></div>Now that you’ve tested pushing and pulling images, you’ve finished setting up a secure registry that you can use to store custom images.