Ravindra BagaleCourses & study guides मराठी

Chapter 48: Docker Images: Layers, Tags, Registries, Docker Hub, ECR, Multi-Arch and Every Image Command

48.5 Run your own private registry

Chala mitrano! Sometimes there is no internet, or the company wants every image on its own server. Then we run our own registry. Today we build one with the registry:2 image and go all the way to volume, password, HTTPS, delete and a UI.

1. Start from an offline factory

A car factory near Pune has machines on a network with no internet, for safety. The machines still run containers, so the images must come from somewhere inside the factory.

A private registry is a server you run yourself that speaks the same language as Docker Hub. Docker publishes the official image registry:2 for this (the project is now called Distribution). You push to it and pull from it with normal docker commands.

We build it in steps: first a quick test, then storage that survives, then a password, then HTTPS so other machines can trust it.

Laptop or CIdocker pushregistry:2 containerport 5000Volumestores the layersFactory serversdocker pull
Picture 1: a home registry

2. A quick registry

Step 1: Start registry:2

Why: You want to see a registry working in one minute.

What: Run the registry image and map port 5000. localhost is trusted by Docker without HTTPS, so testing is easy.

Example 1: Run it

Type this:

docker run -d --name reg -p 5000:5000 registry:2

You will see:

Unable to find image 'registry:2' locally
2: Pulling from library/registry
6c1a8c2b9d3e...

What it means: A registry is now listening on 5000.

Example 2: Ask it something

Type this:

curl -s localhost:5000/v2/

You will see:

{}

What it means: An empty JSON answer means the registry API is alive.

Example 3: List repositories

Type this:

curl -s localhost:5000/v2/_catalog

You will see:

{"repositories":[]}

What it means: Nothing pushed yet.

Step 2: Push to it

Why: The image name must start with the registry address.

What: Tag the image as localhost:5000/name:tag and push.

Example 4: Tag

Type this:

docker pull alpine:3.20
docker tag alpine:3.20 localhost:5000/base/alpine:3.20

You will see:

(pull output)

What it means: base is a folder name we chose.

Example 5: Push

Type this:

docker push localhost:5000/base/alpine:3.20

You will see:

The push refers to repository [localhost:5000/base/alpine]
3.20: digest: sha256:1e42... size: 528

What it means: Stored in our own registry.

Example 6: Check the catalog and tags

Type this:

curl -s localhost:5000/v2/_catalog
curl -s localhost:5000/v2/base/alpine/tags/list

You will see:

{"repositories":["base/alpine"]}
{"name":"base/alpine","tags":["3.20"]}

What it means: The registry API lists repositories and tags.

Step 3: Pull from it

Why: Prove that the image comes from your registry, not Docker Hub.

What: Remove the local copies, then pull with the registry name.

Example 7: Remove local copies

Type this:

docker rmi alpine:3.20 localhost:5000/base/alpine:3.20

You will see:

Untagged: alpine:3.20
Untagged: localhost:5000/base/alpine:3.20

What it means: No local copy left.

Example 8: Pull

Type this:

docker pull localhost:5000/base/alpine:3.20 | tail -1

You will see:

localhost:5000/base/alpine:3.20

What it means: It came from port 5000.

Example 9: Run it

Type this:

docker run --rm localhost:5000/base/alpine:3.20 cat /etc/alpine-release

You will see:

3.20.3

What it means: Works like any image.

3. Make it last

Step 4: Keep data in a volume

Why: If the registry container is removed, all images inside are lost.

What: The registry stores data in /var/lib/registry. Mount a named volume there.

Example 10: Lose data first

Type this:

docker rm -f reg
docker run -d --name reg -p 5000:5000 registry:2
curl -s localhost:5000/v2/_catalog

You will see:

{"repositories":[]}

What it means: The old images are gone.

Example 11: Use a volume

Type this:

docker rm -f reg
docker volume create regdata
docker run -d --name reg -p 5000:5000 --restart unless-stopped -v regdata:/var/lib/registry registry:2

You will see:

regdata
7c2d...

What it means: Data now lives in regdata. It also restarts after reboot.

Example 12: Prove it survives

Type this:

docker tag localhost:5000/base/alpine:3.20 localhost:5000/base/alpine:keep
docker push -q localhost:5000/base/alpine:keep
docker rm -f reg
docker run -d --name reg -p 5000:5000 -v regdata:/var/lib/registry registry:2
curl -s localhost:5000/v2/base/alpine/tags/list

You will see:

{"name":"base/alpine","tags":["keep"]}

What it means: The tag survived a new container.

Step 5: Store layers in S3

Why: At a company on AWS, a single disk is a risk; S3 is safer.

What: The registry can store data in S3 with environment variables. The EC2 role gives access, so no keys go in the command.

Example 13: Run with S3 storage

Type this:

docker rm -f reg
docker run -d --name reg -p 5000:5000 -e REGISTRY_STORAGE=s3 -e REGISTRY_STORAGE_S3_REGION=ap-south-1 -e REGISTRY_STORAGE_S3_BUCKET=pune-factory-registry registry:2

You will see:

9e1f...

What it means: Layers now go to the bucket.

Example 14: Push and look in S3

Type this:

docker push -q localhost:5000/base/alpine:keep
aws s3 ls s3://pune-factory-registry/docker/registry/v2/repositories/

You will see:

PRE base/

What it means: The registry wrote its folders to S3.

Example 15: Check logs for errors

Type this:

docker logs reg 2>&1 | grep -i -c error

You will see:

0

What it means: No storage errors.

htpasswd fileusers and hashed passwordsTLS certificateHTTPS for other machinesregistry:2checks bothClientsdocker login, then push and pull
Picture 2: securing the registry

Step 6: Add a password

Why: Without a password, anyone on the network can push bad images.

What: The registry supports basic auth with an htpasswd file, which stores users with hashed passwords.

Example 16: Make the file

Type this:

mkdir -p auth
docker run --rm --entrypoint htpasswd httpd:2.4 -Bbn ci 'Pr4ctice-Only' > auth/htpasswd
cut -d: -f1 auth/htpasswd

You will see:

ci

What it means: -B means a bcrypt hash. Pr4ctice-Only is a lab password; use your own strong one.

Example 17: Run with auth

Type this:

docker rm -f reg
docker run -d --name reg -p 5000:5000 -v regdata:/var/lib/registry -v $PWD/auth:/auth -e REGISTRY_AUTH=htpasswd -e REGISTRY_AUTH_HTPASSWD_REALM=reg -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd registry:2
curl -s -o /dev/null -w '%{http_code}\n' localhost:5000/v2/

You will see:

401

What it means: 401 means a login is needed.

Example 18: Log in and push

Type this:

printf '%s' 'Pr4ctice-Only' | docker login localhost:5000 -u ci --password-stdin
docker push -q localhost:5000/base/alpine:keep

You will see:

Login Succeeded
localhost:5000/base/alpine:keep

What it means: Only logged-in users can push now.

Step 7: Add HTTPS

Why: Other machines refuse a plain HTTP registry.

What: Docker needs HTTPS for any registry that is not localhost. Give the registry a certificate and key. In a company, use a certificate from your internal certificate authority or Let's Encrypt.

Example 19: The error from another server

Type this:

docker pull 10.0.1.50:5000/base/alpine:keep

You will see:

Error response from daemon: Get "https://10.0.1.50:5000/v2/": http: server gave HTTP response to HTTPS client

What it means: Docker wanted HTTPS.

Example 20: Run with a certificate

Type this:

docker rm -f reg
docker run -d --name reg -p 443:443 -v regdata:/var/lib/registry -v $PWD/certs:/certs -e REGISTRY_HTTP_ADDR=0.0.0.0:443 -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry.crt -e REGISTRY_HTTP_TLS_KEY=/certs/registry.key registry:2

You will see:

5a6b...

What it means: Now on port 443 with HTTPS. Keep registry.key at mode 0600.

Example 21: Trust it on clients

Type this:

sudo mkdir -p /etc/docker/certs.d/registry.factory.local
sudo cp ca.crt /etc/docker/certs.d/registry.factory.local/
docker pull registry.factory.local/base/alpine:keep | tail -1

You will see:

registry.factory.local/base/alpine:keep

What it means: Docker trusts the company CA for this registry only. Avoid the insecure-registries setting outside a lab.

Step 8: Write it as a Compose file

Why: Long docker run lines are hard to repeat on the next server.

What: Put the registry settings in compose.yaml, so one command starts the same registry anywhere.

Example 22: The file

Type this:

printf 'services:\n  reg:\n    image: registry:2\n    restart: unless-stopped\n    ports: ["5000:5000"]\n    environment:\n      REGISTRY_AUTH: htpasswd\n      REGISTRY_AUTH_HTPASSWD_REALM: reg\n      REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd\n      REGISTRY_STORAGE_DELETE_ENABLED: "true"\n    volumes: ["regdata:/var/lib/registry", "./auth:/auth:ro"]\nvolumes:\n  regdata: {}\n' > compose.yaml

You will see:

(no output)

What it means: Auth, deletes, storage and restart, all in one file. The auth folder is mounted read-only.

Example 23: Start it

Type this:

docker rm -f reg; docker compose up -d

You will see:

[+] Running 2/2
 ✔ Volume "lab_regdata"  Created
 ✔ Container lab-reg-1   Started

What it means: Same registry, one command.

Example 24: Check

Type this:

curl -s -u ci:Pr4ctice-Only localhost:5000/v2/_catalog

You will see:

{"repositories":[]}

What it means: A fresh volume, so it is empty. Commit compose.yaml, but never the htpasswd file.

4. Run it day to day

Step 9: Delete images

Why: Registries fill up with old tags.

What: Turn on deletes with REGISTRY_STORAGE_DELETE_ENABLED=true, delete by digest through the API, then run garbage collection to free disk.

Example 25: Find the digest

Type this:

curl -s -u ci:Pr4ctice-Only -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -I https://registry.factory.local/v2/base/alpine/manifests/keep | grep -i docker-content-digest

You will see:

docker-content-digest: sha256:1e42...

What it means: Deletes need the digest, not the tag.

Example 26: Delete

Type this:

curl -s -u ci:Pr4ctice-Only -X DELETE https://registry.factory.local/v2/base/alpine/manifests/sha256:1e42... -o /dev/null -w '%{http_code}\n'

You will see:

202

What it means: 202 means accepted.

Example 27: Free the disk

Type this:

docker exec reg registry garbage-collect /etc/docker/registry/config.yml

You will see:

base/alpine
0 blobs marked, 3 blobs and 0 manifests eligible for deletion

What it means: Layers no image uses are removed. Run it in a quiet hour.

Step 10: A pull-through cache

Why: Many Jenkins agents at a factory pull node:20 every hour from Docker Hub and hit limits.

What: Set REGISTRY_PROXY_REMOTEURL, and the registry becomes a cache of Docker Hub. The first pull fetches; later pulls are local.

Example 28: Run a cache

Type this:

docker run -d --name hubcache -p 5001:5000 -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io -v hubcache:/var/lib/registry registry:2

You will see:

3b4c...

What it means: A second registry, used only as a cache.

Example 29: Point Docker at it

Type this:

printf '{\n  "registry-mirrors": ["http://localhost:5001"]\n}\n' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker

You will see:

{
  "registry-mirrors": ["http://localhost:5001"]
}

What it means: Merge with your existing daemon.json.

Example 30: Pull twice

Type this:

docker pull node:20 >/dev/null; curl -s localhost:5001/v2/_catalog

You will see:

{"repositories":["library/node"]}

What it means: node:20 is now cached for every machine using this mirror.

Step 11: A web UI and cleanup

Why: Not everyone likes curl.

What: Community UIs such as joxit/docker-registry-ui show repositories and tags in a browser. When you finish, remove the practice setup.

Example 31: Run a UI

Type this:

docker run -d --name regui -p 8085:80 -e REGISTRY_TITLE='Factory registry' -e NGINX_PROXY_PASS_URL=http://reg:5000 joxit/docker-registry-ui:2

You will see:

4d5e...

What it means: Put it on the same network as reg so the name works.

Example 32: Open it

Type this:

curl -s localhost:8085 | grep -o '<title>.*</title>'

You will see:

<title>Docker Registry UI</title>

What it means: Browse at http://localhost:8085.

Example 33: Remove everything

Type this:

docker rm -f reg regui hubcache
docker volume rm regdata hubcache

You will see:

reg
regui
hubcache
regdata
hubcache

What it means: Lab finished.

Ravindra Bagale's Tip

Always give a registry a volume, a password and HTTPS. insecure-registries is for labs only, never production.

Check yourself

Q1. Which image runs a private registry?

registry:2.

Q2. Where does the registry store data inside the container?

/var/lib/registry.

Q3. Why does pulling from 10.0.1.50:5000 fail by default?

Docker needs HTTPS for any registry except localhost.

Q4. Does deleting a manifest free disk at once?

No. Run garbage-collect after.

Q5. What does a pull-through cache do?

It keeps copies of Docker Hub images so later pulls are local.

Friends, your own registry is your own warehouse: goods in the volume, a lock called password, and HTTPS as the safe road. Did you understand? Let us try it once more. If not, look at Picture 2 again.

Lab: try at home

  1. Run registry:2, push alpine and read the catalog.
  2. Move data to a volume and prove it survives a new container.
  3. Add htpasswd auth and see the 401.
  4. Delete a tag by digest and run garbage-collect.
  5. Set up a pull-through cache and pull node:20 through it.

Recap

  • registry:2 is a self-hosted registry.
  • Use a volume or S3 for storage.
  • Add htpasswd auth and HTTPS.
  • Delete by digest, then garbage-collect.
  • A pull-through cache saves Docker Hub pulls.

Ravi Sir's Cards for this lesson

मराठी cards