Skip to main content
Self-hosting a Docker registry can provide several benefits:
  • Security & Privacy: Keep proprietary code safe within your infrastructure. Essential for compliance and integrating custom vulnerability scanning.
  • Performance: Drastically faster pulls on local networks, speeding up CI/CD pipelines and reducing reliance on external bandwidth.
  • Cost Savings: Avoid per-user fees common in SaaS solutions and manage your own storage backends (S3, local disk).
  • Reliability: No dependency on public registry uptime or rate limits. Perfect for air-gapped environments.
  • Customization: Full control over garbage collection, retention policies, and authentication integrations (LDAP, AD).

Setup Steps

1
Create a docker-compose.yml file
2
We’ll use the official Docker Registry image to create a self-hosted registry.
3
name: Registry

services:
  registry:
    image: registry:3
    container_name: registry
    ports:
      - "5000:5000"
    restart: unless-stopped
    environment:
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry
    volumes:
      - ./registry-data:/var/lib/registry
4
Run the registry
5
Navigate to the directory containing the docker-compose.yml file and run:
6
docker compose up -d
7
Use the registry
8
You can now tag, push, and pull images from your registry.
9
docker tag <image_name> <registry_url>/<image_name>
docker push <registry_url>/<image_name>
docker pull <registry_url>/<image_name>
10
Here is an example:
11
docker tag hello-world:latest localhost:5000/hello-world:v1
docker push localhost:5000/hello-world:v1
docker pull localhost:5000/hello-world:v1
12
Set up the UI (Optional)
13
By default, the official Docker Registry image does not come with a UI. We’ll use joxit/docker-registry-ui to add one.
14
name: Registry

services:
  registry:
    image: registry:3
    container_name: registry
    ports:
      - "5000:5000"
    restart: unless-stopped
    environment:
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Origin: '[http://registry-ui.example.com]'
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods: '[HEAD,GET,OPTIONS,DELETE]'
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Credentials: '[true]'
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers: '[Authorization,Accept,Cache-Control]'
      REGISTRY_HTTP_HEADERS_Access-Control-Expose-Headers: '[Docker-Content-Digest]'
      REGISTRY_STORAGE_DELETE_ENABLED: 'true'
    volumes:
      - ./registry-data:/var/lib/registry
  registry-ui:
    image: joxit/docker-registry-ui:latest
    container_name: registry-ui
    ports:
      - "5001:80"
    restart: unless-stopped
    environment:
      - SINGLE_REGISTRY=true
      - REGISTRY_TITLE=Docker Registry UI
      - DELETE_IMAGES=true
      - SHOW_CONTENT_DIGEST=true
      - NGINX_PROXY_PASS_URL=http://registry:5000
      - SHOW_CATALOG_NB_TAGS=true
      - CATALOG_MIN_BRANCHES=1
      - CATALOG_MAX_BRANCHES=1
      - TAGLIST_PAGE_SIZE=100
      - REGISTRY_SECURED=false
      - CATALOG_ELEMENTS_LIMIT=1000
    depends_on:
      - registry
15
Start both services:
16
docker compose up -d
17
Access the UI at http://localhost:5001.

Build docs developers (and LLMs) love