Docker Private Registry

Warning: This post is over 365 days old. The information may be out of date.

Small howto to explain how to create a private Docker registry behind a Nginx proxy. My setup will focus on OpenBSD (because it’s my main system).

Why and how a private registry ?

By default Docker uses the Docker repository. You can pull images but by default you cannot push images on it. Unless you create an account on hub.

With your account, you will push images but they will be in public mode (you only have 1 private repository). If you are ok with this, let’s go !

Please note that you can upgrade your plan to have more private repositories (infos)

But maybe you want more private repositories or you have confidential informations or whatever reasons you want.

If you want to use a private registry, you will have 2 options :

  • Use the docker image
  • Build your own registry program (because you don’t want to run it in a container, because your system doesn’t have Docker (hello OpenBSD), etc…)

For the first case, please follow the documentation


The setup focus on OpenBSD but you could adapt it for your distribution. The only dependencies you will need are : git, Go and nginx.

We will folllow these steps:

  • Install dependencies

  • Create an unprivileged user to run the registry

  • Build and configure the registry program

  • Configure Nginx

  • Install the dependencies:

    $ doas pkg_add git go nginx
    quirks-2.405 signed on 2018-01-08T15:59:34Z
    quirks-2.405: ok
    git-2.15.1:nghttp2-1.29.0: ok
    git-2.15.1:curl-7.57.0: ok
    git-2.15.1:p5-Error-0.17024: ok
    git-2.15.1:cvsps-2.1p1: ok
    git-2.15.1:libiconv-1.14p3: ok
    git-2.15.1:gettext- ok
    git-2.15.1:rsync-3.1.2p1: ok
    git-2.15.1: ok
    go-1.9.2: ok
    nginx-1.12.1p2:pcre-8.41: ok
    nginx-1.12.1p2: ok
    The following new rcscripts were installed: /etc/rc.d/gitdaemon /etc/rc.d/nginx /etc/rc.d/rsyncd
    See rcctl(8) for details.
    Look in /usr/local/share/doc/pkg-readmes for extra documentation.
  • Create an unprivileged user (avoid to run your registry as root)

    $ doas adduser registry 
    Use option ``-silent'' if you don't want to see all warnings and questions.
    Reading /etc/shells
    Check /etc/master.passwd
    Check /etc/group
    Ok, let's go.
    Don't worry about mistakes. There will be a chance later to correct any input.
    Enter username []: registry
    Enter full name []: Docker private registry user
    Enter shell csh ksh nologin sh [nologin]: 
    Uid [1001]: 
    Login group registry [registry]: 
    Login group is ``registry''. Invite registry into other groups: guest no 
    Login class      authpf bgpd daemon default pbuild staff unbound 
    Enter password []: 
    Disable password logins for the user? (y/n) [n]: y
    Name:        registry
    Password:    ****
    Fullname:    Docker private registry user
    Uid:         1001
    Gid:         1001 (registry)
    Groups:      registry 
    Login Class: default
    HOME:        /home/registry
    Shell:       /sbin/nologin
    OK? (y/n) [y]:  
    Added user ``registry''
    Copy files from /etc/skel to /home/registry
    Add another user? (y/n) [y]: n
  • Build and configure the registry program. All the commands are typed as user registry

    $ doas su -s /bin/sh - registry

    Now get the source and compile the program. Please note that since Go 1.8, you no longer need to set the GOPATH variable (source)

    $ go get
    $ ./go/bin/registry --version
    ./go/bin/registry v2.6.0+unknown

    In this setup, i have chosen to deploy the registry program in the homedir of the user registry. So i will create a few directories to put configurations file.

    $ mkdir -p ~/base/etc ~/base/auth
    • /home/registry/base : root directory

    • /home/registry/base/etc : directory for the config file

    • /home/registry/base/auth : directory for the htpassword file

      Of course feel free to change this 😏

    Now get a default config file

    $ cd ~/base/etc && wget -O config.yml

    Edit the file to have it like this

    version: 0.1
    	service: registry
    	blobdescriptor: inmemory
    		rootdirectory: /home/registry/base
    	addr: :5000
    			X-Content-Type-Options: [nosniff]
    			enabled: true
    			interval: 10s
    			threshold: 3
    	realm: basic-realm
    	path: /home/registry/base/auth/htpasswd

    I have just added the auth section and modify the rootdirectory.

    Now, create the htpasswd file :

    $ htpasswd -c ~/auth/htpasswd pea
    New password: 

Re-type new password: Adding password for user pea ```

Everything is configured. Launch the registry :
$ /home/registry/go/bin/registry serve /home/registry/base/etc/config.yml
WARN[0000] No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer.
To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable.
go.version=go1.9.2 version="v2.6.0+unknown"
INFO[0000] redis not configured                          go.version=go1.9.2 version="v2.6.0+unknown"

INFO[0000] Starting upload purge in 19m0s go.version=go1.9.2 version=“v2.6.0+unknown” INFO[0000] using inmemory blob descriptor cache go.version=go1.9.2 version=“v2.6.0+unknown” INFO[0000] listening on go.version=go1.9.2 version=“v2.6.0+unknown” There is a warning about the *HTTP secret*. It's only important if you have multiples registries behind a load balancer. If this is the case or if you want to get red of this warning, simply add this line under *http:*yaml secret: YourSuperSecret ```

Your registry is now running... but not securised ! We need to setup SSL.

This setup is really basic. If you want to tune your config file, you will find more informations in the Docker [documentation](

Nginx configuration

The Nginx configuration is very easy. I use it as a reverse proxy and because i use a Let’s Encrypt SSL.

NOTE: the registry program can handle TLS and Let’s Encrypt too but i’m more confident to have Nginx exposed to the world than the registry program…

server {
        listen       443 http2;
        listen       [::]:443 http2;
        server_name  registry.yourdomain.tld;

        ssl                  on;
        ssl_certificate      /etc/nginx/fullchain.pem;
        ssl_certificate_key  /etc/nginx/privkey.pem;

        location / {

That’s all !

HINT : you will have to increase the value of client_max_body_size in order to upload big images.


  • Login to the repository:

% docker login -u pea registry.xxxx.xxxx Password: Login Succeeded ``` You can check that the file ~/.docker/config.json has been created with your credentials.

**WARNING**: keep this file safe and with good permissions (600).
  • Upload images :

    % docker tag alpine:3.6
    % % docker push 

The push refers to repository [] 3fb66f713c9f: Pushed latest: digest: sha256:9ab547ed4d6475a45675f8a127dae347a50957f431a003164a5d1c825868c013 size: 528 ```

  • Where are my images ?

    All the images you will push to the registry will leave in /home/registry/base/docker. Be carefull to have enought disk space on your partition.

And now ?

Now you can enjoy your private registry. The final step is to launch the registry at boot. This step is left to the reader as exercise 😄.

For the curious, i have chosen a quick and dirty way :

  • Create a crontab for the user registry :

    $ crontab -u registry -l
    @reboot         /usr/bin/tmux new -d -s docker "/home/registry/go/bin/registry serve /home/registry/base/etc/config.yml"