DEV Community

Celso Nery
Celso Nery

Posted on

On-Premise Kubernetes: Setting Up a Local Docker Image Registry

In on-premise Kubernetes clusters, it doesn't always make sense to rely on Docker Hub (or any public cloud registry) to store application images — whether for security reasons, limited internet connectivity, or simply to keep everything within your own infrastructure. In this article, I show how to set up a local Docker image registry, both in a simple version (for testing) and in a version with authentication (closer to a real-world scenario).

⚠️ The steps below use a registry without TLS, suitable only for testing environments. For production, configuring a certificate and TLS connection is essential — never expose a registry without encryption in production.

Initial configuration

Before starting the registry, you need to tell Docker it can trust an "insecure" registry (without TLS). This needs to be configured on the server running the registry and on every node in the cluster that will use it.

Edit the /etc/docker/daemon.json file, providing the host (IP or machine name) and the registry's port:

{
    "insecure-registries": [ "host:port" ]
}
Enter fullscreen mode Exit fullscreen mode

Then restart the Docker service to apply the change:

# systemctl restart docker
Enter fullscreen mode Exit fullscreen mode

Creating the registry server

The simplest way to spin up a registry is by running the official registry image as a container:

$ docker run -d -p 5000:5000 --restart=always --name=repository registry
Enter fullscreen mode Exit fullscreen mode

Explaining each option:

Option Purpose
-d Runs the container in the background (daemon)
-p 5000:5000 Maps port 5000 of the container to port 5000 of the host
--restart=always Automatically restarts the container in case of failure
--name=repository Name given to the container
registry Official image used to run the registry server

Testing the registry

To confirm the registry is up, just access it through the browser:

http://registry.zion.local:5000/v2/
Enter fullscreen mode Exit fullscreen mode

An empty JSON response ({}) already indicates the service is working correctly.

Publishing an image to the local registry

1. List the locally available images:

$ docker image ls
Enter fullscreen mode Exit fullscreen mode

2. Tag the image, pointing it to the local registry's address — in the example, the image oregontecnologia/myapp-api:

$ docker tag oregontecnologia/myapp-api registry.zion.local:5000/myapp-api
Enter fullscreen mode Exit fullscreen mode

3. Push the image to the local registry:

$ docker push registry.zion.local:5000/myapp-api
Enter fullscreen mode Exit fullscreen mode

Using the local registry's image in Kubernetes

With the image published, you can already create a Deployment in the cluster pointing directly to the local registry:

$ kubectl create deploy myapp-api --image=registry.zion.local:5000/myapp-api
Enter fullscreen mode Exit fullscreen mode

And confirm the pod came up correctly:

$ kubectl get pods -o wide
Enter fullscreen mode Exit fullscreen mode

If Kubernetes can't pull the image (ImagePullBackOff error), check whether insecure-registries was configured on all nodes of the cluster — not just on the server running the registry —, since any worker could be chosen to schedule the pod.

Leveling up: registry with authentication

The example above is functional, but with no protection whatsoever — anyone with network access can push or pull images. For a more realistic scenario, let's add authentication via htpasswd.

Creating the credentials

First, install the htpasswd utility (part of the apache2-utils package):

$ sudo apt install apache2-utils
Enter fullscreen mode Exit fullscreen mode

Create a folder to store the password file:

$ mkdir ~/home-user/auth && cd $_
Enter fullscreen mode Exit fullscreen mode

Create the registry.password file, adding the first user with the -c flag (which creates the file) and -B (which uses bcrypt encryption):

$ htpasswd -Bc registry.password username
Enter fullscreen mode Exit fullscreen mode

To add other users afterward, don't use the -c flag again (it would overwrite the file):

$ htpasswd -B registry.password new_username
Enter fullscreen mode Exit fullscreen mode

Running the authenticated registry with Docker Compose

Instead of a single docker run command, an authenticated registry is better organized as a docker-compose.yml:

$ vim docker-compose.yml
Enter fullscreen mode Exit fullscreen mode
version: '3'

services:
  registry:
    image: registry:2
    ports:
    - "5000:5000"
    environment:
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: Registry
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.password
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
    volumes:
      - ./auth:/auth
      - ./data:/data
    restart: always
Enter fullscreen mode Exit fullscreen mode

This file:

  • Uses the official registry:2 image;
  • Enables authentication via htpasswd, pointing to the password file created earlier;
  • Persists images in a local volume (./data), preventing data loss if the container is recreated.

Start the service:

$ docker-compose up
Enter fullscreen mode Exit fullscreen mode

Testing the authenticated registry

http://server-name-or-ip:5000/v2
Enter fullscreen mode Exit fullscreen mode

When trying to access it, the browser (or docker login) should prompt for a username and password — confirming that authentication is active.

Final thoughts

Having a local image registry gives more control and independence to the on-premise cluster, especially in environments without unrestricted internet access or with stricter security policies. The authenticated version via htpasswd is already a good starting point for real environments — but for production, it's worth adding TLS and, depending on scale, considering more robust solutions like Harbor, which adds features like granular access control, vulnerability scanning, and registry replication.

If your cluster needs to automatically authenticate against a private registry (public or local) when pulling images, also check out the article on configuring an imagePullSecret with regcred on Kubernetes.

Top comments (1)

Collapse
 
topstar_ai profile image
Luis Cruz

I appreciate the detailed steps you've provided for setting up a local Docker image registry, especially the emphasis on configuring insecure-registries on every node in the cluster. When implementing this in a production environment, I've found it crucial to not only configure TLS but also to ensure that the registry is highly available. Have you considered using a load balancer or a distributed storage solution to ensure the registry remains accessible even in the event of node failures?