• Home
  • LLMs
  • Python
  • Docker
  • Kubernetes
  • Java
  • Maven
  • All
  • About
Docker | Set up a private Docker registry (Ubuntu)
  1. Notes
  2. Configure /etc/ssl/openssl.cnf
  3. Generate a self-signed certificate
  4. Create htpasswd
  5. Configure config.yml
  6. Create the registry Dockerfile
  7. Build the registry Docker image
  8. Run the registry
  9. Test the registry
  10. Push an image into the registry
  11. Pull an image from the registry
  12. Verify the created repositories inside the Registry's container
  13. Verify the created repositories using the Registry API

  1. Notes
    See these pages for more information:
    https://docs.docker.com/registry/
    https://docs.docker.com/registry/spec/api/
    https://docs.docker.com/registry/configuration/
  2. Configure /etc/ssl/openssl.cnf
    First you need to configure openssl.cnf to avoid getting this error when you try to log into the registry using the ip address:
    Error response from daemon: Get https://192.168.2.22:5000/v2/: x509: cannot validate certificate for 192.168.2.22 because it doesn't contain any IP SANs
    http: TLS handshake error from 192.168.2.22:38696: remote error: tls: bad certificate
    To fix this error: add your IP address under the section v3_ca
    $ sudo vi /etc/ssl/openssl.cnf
    [ v3_ca ]
    subjectAltName = IP:192.168.2.22
  3. Generate a self-signed certificate
    Create a working directory:
    $ mkdir ~/registry
    $ cd ~/registry/
    Generate certificate:
    $ openssl req -x509 -nodes -sha256 -newkey rsa:4096 \
       -keyout registry.key -out registry.crt \
       -days 14 -subj '/CN=192.168.2.22'
    $ ls -1 ~/registry
    registry.crt
    registry.key
    Copy certificate into /etc/docker/certs.d/ directory:
    $ sudo mkdir -p "/etc/docker/certs.d/192.168.2.22:5000"
    $ sudo cp ~/registry/registry.crt "/etc/docker/certs.d/192.168.2.22:5000"
    Restart Docker:
    $ sudo systemctl restart docker
  4. Create htpasswd
    $ cd ~/registry/
    Create htpasswd with username/password: admin/admin:
    $ docker container run --rm --entrypoint htpasswd registry:2.7.0 -Bbn admin admin > htpasswd
    $ cat ~/registry/htpasswd
    admin:$2y$05$ICkq3vbj0hkj3sqV/R.dkOrnvLHyR/JDmD7fhNCBP3WHpZTWqRed6
  5. Configure config.yml
    See this page for more information: https://docs.docker.com/registry/configuration/

    Create config.yml file (make sure to adjust the http host with your IP address):
    $ vi ~/registry/config.yml
    version: 0.1
    log:
      accesslog:
        disabled: false
      level: info
      fields:
        service: registry
        environment: development
    storage:
      filesystem:
        rootdirectory: /var/lib/registry
      delete:
        enabled: true
      cache:
        blobdescriptor: inmemory
    auth:
      htpasswd:
        realm: class-realm
        path: /etc/docker/registry/htpasswd
    http:
      addr: 0.0.0.0:5000
      host: https://192.168.2.22:5000
      secret: asecretforlocaldevelopment
      tls:
        certificate: /etc/docker/registry/certs/registry.crt
        key: /etc/docker/registry/certs/registry.key
      debug:
        addr: 0.0.0.0:5001
  6. Create the registry Dockerfile
    $ vi ~/registry/Dockerfile
    FROM registry:2.7.1
    
    ADD config.yml /etc/docker/registry/config.yml
    ADD registry.crt /etc/docker/registry/certs/registry.crt
    ADD registry.key /etc/docker/registry/certs/registry.key
    ADD htpasswd /etc/docker/registry/htpasswd
  7. Build the registry Docker image
    First let's check that you have all the required files:
    $ ls -1 ~/registry
    config.yml
    Dockerfile
    htpasswd
    registry.crt
    registry.key
    Build registry:
    $ DOCKER_BUILDKIT=0 docker build -t local-registry .
    Sending build context to Docker daemon  11.26kB
    
    Step 1/5 : FROM registry:2.7.1
    2.7.1: Pulling from library/registry
    ...
    Digest: sha256:8be26f81ffea54106bae012c6f349df70f4d5e7e2ec01b143c46e2c03b9e551d
    Status: Downloaded newer image for registry:2.7.1
     ---> 2d4f4b5309b1
    
    Step 2/5 : ADD config.yml /etc/docker/registry/config.yml
     ---> 845a8d18a3b5
    
    Step 3/5 : ADD registry.crt /etc/docker/registry/certs/registry.crt
     ---> 87b386161c5b
    
    Step 4/5 : ADD registry.key /etc/docker/registry/certs/registry.key
     ---> 545e56e52c62
    
    Step 5/5 : ADD htpasswd /etc/docker/registry/htpasswd
     ---> 682f3dd78080
    
    Successfully built 682f3dd78080
    Successfully tagged local-registry:latest
  8. Run the registry
    $ docker container run -d -p 5000:5000 --name registry local-registry
    4619b37683cd272f2daf43b47877bfe03d01c216db5436d21fcaa5c4526a9634
    $ docker container ls --no-trunc
    CONTAINER ID                                                       IMAGE            COMMAND                                            CREATED          STATUS          PORTS                    NAMES
    a948cd767c54312925313fb4fa120af5e7d5097bc5ca8bcf3646f7d5a22c61eb   local-registry   "/entrypoint.sh /etc/docker/registry/config.yml"   43 seconds ago   Up 42 seconds   0.0.0.0:5000->5000/tcp   registry
  9. Test the registry
    • Login to registry:
      $ docker login 192.168.2.22:5000
      Username: admin
      Password: admin
      WARNING! Your password will be stored unencrypted in $HOME/.docker/config.json.
      Configure a credential helper to remove this warning. See
      https://docs.docker.com/engine/reference/commandline/login/#credentials-store
      
      Login Succeeded
      Verify ~/.docker/config.json:
      $ cat ~/.docker/config.json
      {
        "auths": {
          "192.168.2.22:5000": {
            "auth": "YWRtaW46YWRtaW4="
          }
        },
        "HttpHeaders": {
          "User-Agent": "Docker-Client/19.03.12 (linux)"
        }
      }
      The aude value is base64 encoded. To decode it:
      $ echo "YWRtaW46YWRtaW4=" | base64 --decode
      admin:admin
    • Logout from registry:
      $ docker logout 192.168.2.22:5000
      Removing login credentials for 192.168.2.22:5000
      Verify that credentials were removed from ~/.docker/config.json:
      $ cat ~/.docker/config.json
      {
        "auths": {},
        "HttpHeaders": {
          "User-Agent": "Docker-Client/19.03.12 (linux)"
        }
      }
  10. Push an image into the registry
    Let's first tag an existing image:
    $ docker image tag alpine:latest 192.168.2.22:5000/alpine:latest
    List images:
    $ docker image ls
    REPOSITORY                                                                           TAG                 IMAGE ID            CREATED             SIZE
    192.168.2.22:5000/alpine                                                             latest              a24bb4013296        8 weeks ago         5.57MB
    alpine                                                                               latest              a24bb4013296        8 weeks ago         5.57MB
    Push the image to registry:
    $ docker image push 192.168.2.22:5000/alpine:latest
    The push refers to repository [192.168.2.22:5000/alpine]
    50644c29ef5a: Pushed
    latest: digest: sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65 size: 528
    If you get the following error, then you are probably not logged in to the registry (see above):
    The push refers to repository [192.168.2.22:5000/alpine]
    50644c29ef5a: Preparing
    no basic auth credentials
  11. Pull an image from the registry
    $ docker image pull 192.168.2.22:5000/alpine:latest
    latest: Pulling from alpine
    Digest: sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65
    Status: Downloaded newer image for 192.168.2.22:5000/alpine:latest
    192.168.2.22:5000/alpine:latest
    If you get the following error, then you are probably not logged in to the registry (see above):
    Error response from daemon: Get https://192.168.2.22:5000/v2/alpine/manifests/latest: no basic auth credentials
    Notes:
    The tag latest is a special static build number. The referenced image might be different every time you pull it. If you request a new pull, Docker will download only the layers that were updated since the latest pull. It's recommended to use an incremental build number to distinguish between different builds of an Image. This can help for debugging issues with an image and eventually been able to revert back to an old working tag if needed. We can also pull an image using its full SHA-256 hash ID docker image pull alpine@sha256:abcxz.
  12. Verify the created repositories inside the Registry's container
    $ docker container ls
    CONTAINER ID   IMAGE            COMMAND                                            CREATED          STATUS          PORTS                    NAMES
    a948cd767c54   local-registry   "/entrypoint.sh /etc/docker/registry/config.yml"   43 seconds ago   Up 42 seconds   0.0.0.0:5000->5000/tcp   registry
    List repositories:
    $ docker container exec -it a948cd767c54 /bin/sh
    / # ls -1 /var/lib/registry/docker/registry/v2/repositories/
    alpine
    Verify the registry configuration:
    $ docker container exec -it a948cd767c54 /bin/sh
    / # ls -1R /etc/docker/registry/
    /etc/docker/registry/:
    certs
    config.yml
    htpasswd
    
    /etc/docker/registry/certs:
    registry.crt
    registry.key
  13. Verify the created repositories using the Registry API
    Check API Version:
    $ curl -u admin:admin -i -k https://192.168.2.22:5000/v2/
    HTTP/2 200
    content-type: application/json; charset=utf-8
    docker-distribution-api-version: registry/2.0
    
    {}
    List repositories:
    $ curl -u admin:admin -i -k https://192.168.2.22:5000/v2/_catalog
    HTTP/2 200
    content-type: application/json; charset=utf-8
    docker-distribution-api-version: registry/2.0
    
    {"repositories":["alpine"]}
    Note the option -k with the curl command. It's used to suppress this certificate error:
    curl: (60) SSL certificate problem: self signed certificate
© 2025  mtitek