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" ]
}
Then restart the Docker service to apply the change:
# systemctl restart docker
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
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/
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
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
3. Push the image to the local registry:
$ docker push registry.zion.local:5000/myapp-api
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
And confirm the pod came up correctly:
$ kubectl get pods -o wide
If Kubernetes can't pull the image (
ImagePullBackOfferror), check whetherinsecure-registrieswas 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
Create a folder to store the password file:
$ mkdir ~/home-user/auth && cd $_
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
To add other users afterward, don't use the -c flag again (it would overwrite the file):
$ htpasswd -B registry.password new_username
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
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
This file:
- Uses the official
registry:2image; - 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
Testing the authenticated registry
http://server-name-or-ip:5000/v2
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)
I appreciate the detailed steps you've provided for setting up a local Docker image registry, especially the emphasis on configuring
insecure-registrieson 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?