Private Image Repository

Don't want to push your company's code to the public Docker Hub? Set up a private image repository in 5 minutes.

1. What You'll Learn


2. A True Story of a Team Leader

(1) Pain Point: Code cannot be pushed to a public repository

Alice doesn’t want to push the company’s code to the public Docker Hub—the images might contain sensitive information such as configuration files and default environment variable values. However, the team needs an image repository to share build artifacts; manually docker save/load transferring them is too inefficient—it takes five developers 30 minutes each time to pass an image along.

(2) Solutions for Private Registries

I set up a private repository with authentication using just three lines of configuration.

BASH
# Start a private registry with authentication
docker run -d -p 5000:5000 \
  -v registry-data:/var/lib/registry \
  --restart=always \
  --name registry registry:2

(3) Benefits: 5-minute image sharing

Teams can start using private repositories within 5 minutes. docker push Once, and everyone docker pull is set. Image delivery time has been reduced from 30 minutes to 30 seconds.


3. Comparison of Mirror Repository Solutions

(1) Comparison of the Three Options

Dimension Docker Hub (Public) Private Registry Harbor
Cost Free with limits; paid plan $5/month Completely free Free and open source
Authentication Docker Account htpasswd/TLS LDAP/OIDC/DB
Web UI
Image Scanning ✅ (Paid) ✅ (Trivy integration)
Deployment Difficulty None (Already live) Low (1 command) Medium (Docker Compose)
Suitable Scale Individuals/Small Teams Small to Medium-Sized Teams Enterprise

(2) Image Naming Conventions

TEXT
# Official images on Docker Hub
docker.io/library/nginx:1.25

# User images on Docker Hub
docker.io/myorg/myapp:v2.0

# Private registry images
localhost:5000/myapp:v2.0
registry.example.com/team/app:v1.0

4. Deploying a Private Registry

▶ Example: Starting the Registry Container (Difficulty: ⭐)

BASH
# Start a basic private registry
docker run -d \
  -p 5000:5000 \
  --name registry \
  --restart=always \
  -v registry-data:/var/lib/registry \
  registry:2

# Verify it's running
curl http://localhost:5000/v2/_catalog
💻 Output:

TEXT
{"repositories":[]}

(1) Data Persistence

Method Command Description
Named Volume -v registry-data:/var/lib/registry Docker-managed persistent storage
Bind Mount -v /data/registry:/var/lib/registry Specify host directory
Default (without -v) No persistence Image data is lost when the container is deleted
⚠️ Note: In a production environment, you must use a volume or a bind mount to persist data. Otherwise, all pushed images will be lost when the Registry container is deleted.


5. Pushing and Pulling Images

▶ Example: Pushing to a private repository (Difficulty: ⭐⭐)

BASH
# 1. Tag an image for the private registry
docker tag nginx:1.25-alpine localhost:5000/web/nginx:v1

# 2. Push the image
docker push localhost:5000/web/nginx:v1

# 3. Verify it's in the registry
curl http://localhost:5000/v2/web/nginx/tags/list
💻 Output:

TEXT
{"name":"web/nginx","tags":["v1"]}

▶ Example: Pulling an image from a private repository (Difficulty: ⭐⭐)

BASH
# 1. Remove the local copy
docker rmi localhost:5000/web/nginx:v1

# 2. Pull from the private registry
docker pull localhost:5000/web/nginx:v1

# 3. Run the pulled image
docker run --rm localhost:5000/web/nginx:v1 nginx -v
💻 Output:

TEXT
nginx version: nginx/1.25.3

6. Repository Authentication Configuration

By default, the registry is not authenticated, so anyone can push or pull. Authentication must be configured for production environments.

▶ Example: Generating an htpasswd password file (Difficulty: ⭐⭐⭐)

BASH
# Create a directory for auth data
mkdir -p auth

# Generate htpasswd file (requires httpd-tools / apache2-utils)
htpasswd -Bbn admin secret123 > auth/htpasswd

# Start registry with authentication
docker run -d \
  -p 5000:5000 \
  --name registry-auth \
  --restart=always \
  -v registry-data:/var/lib/registry \
  -v $(pwd)/auth:/auth \
  -e "REGISTRY_AUTH=htpasswd" \
  -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
  -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
  registry:2

(1) Post-Authentication Operations

BASH
# Login to the private registry
docker login localhost:5000
# Username: admin
# Password: secret123

# Push (requires login)
docker push localhost:5000/myapp:v1

# Pull (requires login)
docker pull localhost:5000/myapp:v1

# Logout
docker logout localhost:5000

7. View the repository directory

▶ Example: Use curl to view the catalog and tags (Difficulty: ⭐⭐)

BASH
# List all repositories
curl -s http://localhost:5000/v2/_catalog | python3 -m json.tool

# List tags for a specific repository
curl -s http://localhost:5000/v2/web/nginx/tags/list | python3 -m json.tool

# View image manifest (detailed metadata)
curl -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
  http://localhost:5000/v2/web/nginx/manifests/v1 | python3 -m json.tool
💻 Output:

TEXT
# catalog
{
  "repositories": [
    "web/nginx",
    "team/api"
  ]
}

# tags/list
{
  "name": "web/nginx",
  "tags": ["v1", "v2"]
}

8. Garbage Collection

After an image in the Registry is deleted (untagged), the layer data is not automatically cleaned up. You must manually perform garbage collection.

▶ Example: Garbage Collection (Difficulty: ⭐⭐⭐)

BASH
# Delete a tag (mark for deletion)
curl -X DELETE -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
  http://localhost:5000/v2/web/nginx/manifests/<digest>

# Run garbage collection inside the registry container
docker exec registry bin/registry garbage-collect \
  /etc/docker/registry/config.yml

# Or with dry-run first (preview only)
docker exec registry bin/registry garbage-collect --dry-run \
  /etc/docker/registry/config.yml

9. HTTP vs. HTTPS Repositories

(1) Docker Security Policies

By default, Docker only allows HTTPS connections to the registry (except for localhost). Remote registries must be configured with HTTPS, or the insecure-registries option must be added on the client side.

Scenario Security Level Configuration
localhost:5000 HTTP allowed by default No additional configuration required
Remote HTTP Repository Insecure, requires explicit permission insecure-registries
Remote HTTPS Repository ✅ Recommended TLS Certificate

(2) Configure insecure-registries (for test environments only)

JSON
{
  "insecure-registries": ["registry.example.com:5000"]
}
🔒 Security: Production environments must use HTTPS + a trusted TLS certificate. "insecure-registries" is intended for internal testing only; traffic is vulnerable to man-in-the-middle attacks.


10. Complete Example: Deploying a Private Repository with Authentication

BASH
# ============================================
# Complete walkthrough: Private registry with auth
# Covers: deploy, auth, push, pull, catalog, GC
# ============================================

# 1. Create directories for persistent data
mkdir -p auth registry-data

# 2. Generate htpasswd authentication file
htpasswd -Bbn admin secret123 > auth/htpasswd

# 3. Start the registry with authentication and persistence
docker run -d \
  -p 5000:5000 \
  --name my-registry \
  --restart=always \
  -v $(pwd)/registry-data:/var/lib/registry \
  -v $(pwd)/auth:/auth \
  -e "REGISTRY_AUTH=htpasswd" \
  -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
  -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
  registry:2

# 4. Login to the registry
docker login localhost:5000 -u admin -p secret123

# 5. Tag and push an image
docker tag nginx:1.25-alpine localhost:5000/web/nginx:v1.0
docker push localhost:5000/web/nginx:v1.0

# 6. Verify the push
curl -s -u admin:secret123 http://localhost:5000/v2/_catalog
curl -s -u admin:secret123 http://localhost:5000/v2/web/nginx/tags/list

# 7. Pull from another machine (after login)
docker pull localhost:5000/web/nginx:v1.0

# 8. Clean up
docker logout localhost:5000
💻 Output (excerpt):

TEXT
# curl catalog
{"repositories":["web/nginx"]}

# curl tags
{"name":"web/nginx","tags":["v1.0"]}

❓ FAQ

Q How much storage space does a private repository require?
A It depends on the number and size of the images. A single image ranges from 50 to 500 MB, and 10 images take up approximately 1 to 5 GB. We recommend allocating 50 GB initially and regularly cleaning up old versions. For enterprise-level use, we recommend using object storage (S3/OSS) as the registry backend, with storage that scales on demand.
Q How do I set up HTTPS for a repository?
A There are two ways: ① Use Nginx or Caddy as a reverse proxy and configure a TLS certificate (recommended); ② Configure TLS directly in the Registry. We recommend the reverse proxy approach—Nginx handles TLS termination, while the Registry focuses on storage. Let's Encrypt provides free certificates.
Q How do I clean up when there are too many images?
A Two steps: ① Use the DELETE API to remove unnecessary tags/manifests; ② Run registry garbage-collect to free up unreferenced layer data. We recommend writing a script to periodically clean up old versions that are more than N days old. Harbor has built-in cleanup policies.
Q Can others view the images in the repository?
A Without authentication, anyone can pull (anonymous access). After configuring htpasswd authentication, you must log in to push or pull. Recommended for production environments: read-only permissions for pulling, and write permissions for pushing (Registry 2.8+ supports RBAC).
Q Which is better to use, Harbor or Registry?
A Registry is a minimalist repository (suitable for small teams and CI), while Harbor is an enterprise-grade repository (Web UI + image scanning + RBAC + audit logs). Teams with fewer than 10 members should use Registry, while those with more than 10 members or with compliance requirements should use Harbor. Harbor itself is deployed using Docker Compose and can be set up in 5 minutes.

📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty ⭐): Start a private registry using docker run, push nginx:alpine to localhost:5000/test/nginx:v1, and then pull it from another machine (or after deleting the local image) to verify.
  2. Advanced Problem (Difficulty: ⭐⭐): Configure htpasswd authentication so that pushes are rejected when not logged in, but succeed after logging in.
  3. Challenge (Difficulty: ⭐⭐⭐): Use an Nginx reverse proxy to configure HTTPS for the Registry (a self-signed certificate can be used), and access the repository via https://registry.local/v2/_catalog.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏