D5.3 Private Registry ΌΔ Ansible đŸ›ïž

΀Îč Ξα φτÎčÎŹÎŸÎżÏ…ÎŒÎ”

┌─────────────────────────────────────────────────────────┐
│  Private Docker Registry                                │
│                                                         │
│  ┌──────────────────────────────────────────────────┐   │
│  │  nextcloud (registry server)                     │   │
│  │                                                  │   │
│  │  ┌────────────────────┐  ┌────────────────────┐  │   │
│  │  │   registry:2       │  │   nginx (SSL/Auth) │  │   │
│  │  │   :5000 (internal) │  │   :443 (external)  │  │   │
│  │  └────────────────────┘  └────────────────────┘  │   │
│  │                                                  │   │
│  │  /opt/registry/                                  │   │
│  │  ├── data/          ← image storage              │   │
│  │  ├── auth/          ← htpasswd                   │   │
│  │  └── certs/         ← SSL certificates           │   │
│  └──────────────────────────────────────────────────┘   │
│                                                         │
│  Clients:                                               │
│  docker login registry.example.com                      │
│  docker push registry.example.com/myapp:1.0.0           │
│  docker pull registry.example.com/myapp:1.0.0           │
└─────────────────────────────────────────────────────────┘

Î”ÎżÎŒÎź Role

# ΔηΌÎčÎżÏ…ÏÎłÎŻÎ± registry role
cd ~/ansible/roles
ansible-galaxy role init registry

tree ~/ansible/roles/registry/
# registry/
# ├── defaults/
# │   └── main.yml
# ├── handlers/
# │   └── main.yml
# ├── tasks/
# │   ├── main.yml
# │   ├── setup.yml
# │   ├── auth.yml
# │   ├── deploy.yml
# │   └── verify.yml
# └── templates/
#     ├── registry-compose.yml.j2
#     └── nginx-registry.conf.j2

defaults/main.yml

cat > ~/ansible/roles/registry/defaults/main.yml << 'EOF'
---
# ============================================================
# Private Registry Defaults
# ============================================================

# ── Registry Settings ─────────────────────────
registry_name:     registry
registry_port:     5000              # internal port
registry_ext_port: 443              # external port (SSL)
registry_hostname: "{{ inventory_hostname }}"

# ── Storage ───────────────────────────────────
registry_base_dir:   /opt/registry
registry_data_dir:   /opt/registry/data
registry_auth_dir:   /opt/registry/auth
registry_certs_dir:  /opt/registry/certs
registry_logs_dir:   /opt/registry/logs

# ── Authentication ────────────────────────────
registry_auth_enabled: true
registry_users:        []
# Format:
# - username: myuser
#   password: "{{ vault_registry_pass }}"

# ── SSL ───────────────────────────────────────
registry_ssl_enabled:    true
registry_ssl_self_signed: true      # ← true ÎłÎčα self-signed
registry_ssl_cert_days:  3650       # 10 χρόΜÎčα

# ── Images ────────────────────────────────────
registry_image:   registry:2
nginx_image:      nginx:1.25.3-alpine

# ── Resources ─────────────────────────────────
registry_memory:  "256m"
registry_cpus:    "0.25"
nginx_memory:     "64m"
nginx_cpus:       "0.25"

# ── Garbage Collection ────────────────────────
registry_gc_enabled:  true
registry_gc_schedule: "0 2 * * 0"  # ΚυρÎčαÎșÎź 02:00
EOF

tasks/main.yml

cat > ~/ansible/roles/registry/tasks/main.yml << 'EOF'
---
# ============================================================
# Private Registry — Main Entry Point
# ============================================================

- name: Setup registry infrastructure
  ansible.builtin.import_tasks:
    file: setup.yml
  tags: [setup, registry]

- name: Configure authentication
  ansible.builtin.import_tasks:
    file: auth.yml
  tags: [auth, registry]

- name: Deploy registry
  ansible.builtin.import_tasks:
    file: deploy.yml
  tags: [deploy, registry]

- name: Verify registry
  ansible.builtin.import_tasks:
    file: verify.yml
  tags: [verify, registry]
EOF

tasks/setup.yml

cat > ~/ansible/roles/registry/tasks/setup.yml << 'EOF'
---
# ============================================================
# Registry Setup — Directories & Certificates
# ============================================================

# ── Directories ───────────────────────────────
- name: Create registry directories
  ansible.builtin.file:
    path:  "{{ item }}"
    state: directory
    mode:  '0755'
    owner: root
    group: root
  loop:
    - "{{ registry_base_dir }}"
    - "{{ registry_data_dir }}"
    - "{{ registry_auth_dir }}"
    - "{{ registry_certs_dir }}"
    - "{{ registry_logs_dir }}"

# ── Self-signed SSL certificate ───────────────
- name: Generate self-signed SSL certificate
  ansible.builtin.command:
    cmd: >
      openssl req -newkey rsa:4096 -nodes -sha256
      -keyout {{ registry_certs_dir }}/registry.key
      -x509
      -days {{ registry_ssl_cert_days }}
      -out {{ registry_certs_dir }}/registry.crt
      -subj "/CN={{ registry_hostname }}"
      -addext "subjectAltName=DNS:{{ registry_hostname }},IP:{{ ansible_facts['default_ipv4']['address'] }}"
  args:
    creates: "{{ registry_certs_dir }}/registry.crt"
  when: registry_ssl_self_signed
  register: cert_created

- name: Set certificate permissions
  ansible.builtin.file:
    path: "{{ item.path }}"
    mode: "{{ item.mode }}"
  loop:
    - path: "{{ registry_certs_dir }}/registry.crt"
      mode: '0644'
    - path: "{{ registry_certs_dir }}/registry.key"
      mode: '0600'
  when: registry_ssl_self_signed

# ── Trust certificate (ÎłÎčα Docker) ────────────
- name: Create Docker certs directory
  ansible.builtin.file:
    path:  "/etc/docker/certs.d/{{ registry_hostname }}:{{ registry_ext_port }}"
    state: directory
    mode:  '0755'

- name: Copy certificate to Docker certs
  ansible.builtin.copy:
    src:        "{{ registry_certs_dir }}/registry.crt"
    dest:       "/etc/docker/certs.d/{{ registry_hostname }}:{{ registry_ext_port }}/ca.crt"
    remote_src: true
    mode:       '0644'

# ── Install htpasswd tool ─────────────────────
- name: Install apache2-utils (ÎłÎčα htpasswd)
  ansible.builtin.apt:
    name:  apache2-utils
    state: present
  when: ansible_facts['os_family'] == "Debian"

- name: Install httpd-tools (ÎłÎčα htpasswd)
  ansible.builtin.dnf:
    name:  httpd-tools
    state: present
  when: ansible_facts['os_family'] == "RedHat"

- name: Install apache2-utils (openSUSE)
  community.general.zypper:
    name:  apache2-utils
    state: present
  when: ansible_facts['os_family'] == "Suse"
EOF

tasks/auth.yml

cat > ~/ansible/roles/registry/tasks/auth.yml << 'EOF'
---
# ============================================================
# Registry Authentication — htpasswd
# ============================================================

# ── ΔηΌÎčÎżÏ…ÏÎłÎŻÎ± htpasswd Î±ÏÏ‡Î”ÎŻÎżÏ… ───────────────
- name: Create htpasswd file
  ansible.builtin.file:
    path:  "{{ registry_auth_dir }}/htpasswd"
    state: touch
    mode:  '0600'
    owner: root
  when: registry_users | length == 0

# ── Î ÏÎżÏƒÎžÎźÎșη users ────────────────────────────
- name: Add registry users
  ansible.builtin.command:
    cmd: >
      htpasswd
      {% if not htpasswd_stat.stat.exists %}
      -Bbc
      {% else %}
      -Bb
      {% endif %}
      {{ registry_auth_dir }}/htpasswd
      {{ item.username }}
      {{ item.password }}
  loop: "{{ registry_users }}"
  loop_control:
    label: "{{ item.username }}"
  no_log: true
  register: htpasswd_result
  vars:
    htpasswd_stat:
      stat:
        exists: true

- name: Check htpasswd file
  ansible.builtin.stat:
    path: "{{ registry_auth_dir }}/htpasswd"
  register: htpasswd_file

- name: Set htpasswd permissions
  ansible.builtin.file:
    path:  "{{ registry_auth_dir }}/htpasswd"
    mode:  '0600'
    owner: root
  when: htpasswd_file.stat.exists

- name: Show auth status
  ansible.builtin.debug:
    msg: >
      Registry auth:
      {{ '✅ ' + (registry_users | length | string) + ' users configured'
         if registry_users | length > 0
         else '⚠ No users configured!' }}
EOF

tasks/deploy.yml

cat > ~/ansible/roles/registry/tasks/deploy.yml << 'EOF'
---
# ============================================================
# Registry Deployment ΌΔ Docker Compose
# ============================================================

# ── Render compose file ───────────────────────
- name: Render registry docker-compose.yml
  ansible.builtin.template:
    src:  registry-compose.yml.j2
    dest: "{{ registry_base_dir }}/docker-compose.yml"
    mode: '0644'

# ── Render nginx config ───────────────────────
- name: Render nginx configuration
  ansible.builtin.template:
    src:  nginx-registry.conf.j2
    dest: "{{ registry_base_dir }}/nginx.conf"
    mode: '0644'

# ── Pull images ───────────────────────────────
- name: Pull registry images
  community.docker.docker_image:
    name:   "{{ item.name }}"
    tag:    "{{ item.tag }}"
    source: pull
  loop:
    - { name: registry, tag: "2"          }
    - { name: nginx,    tag: "1.25.3-alpine" }
  loop_control:
    label: "{{ item.name }}:{{ item.tag }}"

# ── Deploy stack ──────────────────────────────
- name: Deploy registry stack
  community.docker.docker_compose_v2:
    project_src:  "{{ registry_base_dir }}"
    project_name: "{{ registry_name }}"
    state:        present
  register: registry_deploy

# ── Wait for registry ─────────────────────────
- name: Wait for registry to start
  community.docker.docker_container_info:
    name: "{{ registry_name }}_registry"
  register: reg_info
  until: >
    reg_info.container is defined and
    reg_info.container.State.Running == true
  retries: 12
  delay:   5

# ── Wait for nginx ────────────────────────────
- name: Wait for nginx proxy to start
  community.docker.docker_container_info:
    name: "{{ registry_name }}_nginx"
  register: nginx_info
  until: >
    nginx_info.container is defined and
    nginx_info.container.State.Running == true
  retries: 6
  delay:   5

# ── Setup garbage collection ──────────────────
- name: Deploy registry GC script
  ansible.builtin.copy:
    dest:  /usr/local/bin/registry-gc.sh
    mode:  '0755'
    owner: root
    content: |
      #!/bin/bash
      # Registry Garbage Collection
      echo "=== Registry GC $(date) ==="
      docker compose \
          -f {{ registry_base_dir }}/docker-compose.yml \
          -p {{ registry_name }} \
          exec -T registry \
          registry garbage-collect \
          /etc/docker/registry/config.yml
      echo "=== GC complete ==="
  when: registry_gc_enabled

- name: Schedule registry GC
  ansible.builtin.cron:
    name:   "Registry garbage collection"
    minute: "{{ registry_gc_schedule.split()[0] }}"
    hour:   "{{ registry_gc_schedule.split()[1] }}"
    day:    "{{ registry_gc_schedule.split()[2] }}"
    month:  "{{ registry_gc_schedule.split()[3] }}"
    weekday: "{{ registry_gc_schedule.split()[4] }}"
    user:   root
    job:    "/usr/local/bin/registry-gc.sh >> /var/log/registry-gc.log 2>&1"
    state:  present
  when: registry_gc_enabled
EOF

templates/registry-compose.yml.j2

cat > ~/ansible/roles/registry/templates/registry-compose.yml.j2 << 'EOF'
{# Private Registry Docker Compose #}
{# Managed by Ansible — DO NOT EDIT! #}

version: "3.8"

services:

  # ══════════════════════════════════════════
  # REGISTRY — Storage Backend
  # ══════════════════════════════════════════
  registry:
    image: {{ registry_image }}
    container_name: {{ registry_name }}_registry
    restart: unless-stopped
    environment:
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry
      REGISTRY_HTTP_ADDR:                        0.0.0.0:5000
      REGISTRY_HTTP_SECRET:                      {{ vault_registry_http_secret }}
      {% if registry_auth_enabled %}
      REGISTRY_AUTH:                             htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM:             "Registry Realm"
      REGISTRY_AUTH_HTPASSWD_PATH:              /auth/htpasswd
      {% endif %}
      REGISTRY_STORAGE_DELETE_ENABLED:           "true"
    volumes:
      - {{ registry_data_dir }}:/var/lib/registry
      - {{ registry_auth_dir }}:/auth:ro
      - /etc/localtime:/etc/localtime:ro
    networks:
      - registry_net
    deploy:
      resources:
        limits:
          memory: {{ registry_memory }}
          cpus:   "{{ registry_cpus }}"
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    healthcheck:
      test:         ["CMD", "wget", "-q", "-O", "/dev/null", "--no-check-certificate", "http://localhost:5000/v2/"]
      interval:     30s
      timeout:      10s
      retries:      3
      start_period: 10s

  # ══════════════════════════════════════════
  # NGINX — SSL Proxy
  # ══════════════════════════════════════════
  nginx:
    image: {{ nginx_image }}
    container_name: {{ registry_name }}_nginx
    restart: unless-stopped
    ports:
      - "{{ registry_ext_port }}:443"
    volumes:
      - {{ registry_base_dir }}/nginx.conf:/etc/nginx/nginx.conf:ro
      - {{ registry_certs_dir }}:/etc/nginx/certs:ro
      - {{ registry_logs_dir }}:/var/log/nginx
      - /etc/localtime:/etc/localtime:ro
    networks:
      - registry_net
    depends_on:
      registry:
        condition: service_healthy
    deploy:
      resources:
        limits:
          memory: {{ nginx_memory }}
          cpus:   "{{ nginx_cpus }}"
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    healthcheck:
      test:         ["CMD", "wget", "-q", "-O", "/dev/null", "--no-check-certificate", "https://localhost/v2/"]
      interval:     30s
      timeout:      10s
      retries:      3
      start_period: 15s

networks:
  registry_net:
    name: {{ registry_name }}_net
    driver: bridge
    internal: false
EOF

templates/nginx-registry.conf.j2

cat > ~/ansible/roles/registry/templates/nginx-registry.conf.j2 << 'EOF'
{# Nginx config for Docker Registry #}
{# Managed by Ansible — DO NOT EDIT! #}

user nginx;
worker_processes 1;
pid /var/run/nginx.pid;

events {
    worker_connections 512;
}

http {
    include      /etc/nginx/mime.types;
    default_type application/octet-stream;

    access_log /var/log/nginx/registry_access.log;
    error_log  /var/log/nginx/registry_error.log warn;

    # ── Large image uploads ───────────────────
    client_max_body_size 0;            # ← no limit ÎłÎčα images!
    chunked_transfer_encoding on;

    server {
        listen 443 ssl;
        server_name {{ registry_hostname }};

        # ── SSL ───────────────────────────────
        ssl_certificate     /etc/nginx/certs/registry.crt;
        ssl_certificate_key /etc/nginx/certs/registry.key;
        ssl_protocols       TLSv1.2 TLSv1.3;
        ssl_ciphers         HIGH:!aNULL:!MD5;
        ssl_session_cache   shared:SSL:10m;
        ssl_session_timeout 10m;

        # ── Security headers ──────────────────
        add_header Strict-Transport-Security "max-age=31536000" always;
        add_header X-Content-Type-Options    "nosniff"          always;
        add_header X-Frame-Options           "DENY"             always;

        # ── Registry proxy ────────────────────
        location /v2/ {
            # ── Auth check ────────────────────
            {% if registry_auth_enabled %}
            auth_basic           "Docker Registry";
            auth_basic_user_file /etc/nginx/certs/../../../auth/htpasswd;
            {% endif %}

            # ── Proxy settings ────────────────
            proxy_pass                        http://registry:5000;
            proxy_set_header  Host            $host;
            proxy_set_header  X-Real-IP       $remote_addr;
            proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header  X-Forwarded-Proto $scheme;

            # ── Timeouts ÎłÎčα ÎŒÎ”ÎłÎŹÎ»Î± images ────
            proxy_connect_timeout  300;
            proxy_send_timeout     300;
            proxy_read_timeout     300;

            # ── Buffer settings ───────────────
            proxy_buffering            off;
            proxy_request_buffering    off;
        }

        # ── Health check ──────────────────────
        location /health {
            access_log off;
            return 200 "OK\n";
            add_header Content-Type text/plain;
        }
    }

    # ── HTTP → HTTPS redirect ─────────────────
    server {
        listen 80;
        server_name {{ registry_hostname }};
        return 301 https://$host$request_uri;
    }
}
EOF

tasks/verify.yml

cat > ~/ansible/roles/registry/tasks/verify.yml << 'EOF'
---
# ============================================================
# Registry Verification
# ============================================================

# ── Basic connectivity ────────────────────────
- name: Check registry API (internal)
  ansible.builtin.uri:
    url:              "http://localhost:5000/v2/"
    status_code:      [200, 401]
    validate_certs:   false
  register:     reg_api_check
  ignore_errors: true

- name: Check registry API (external SSL)
  ansible.builtin.uri:
    url:            "https://localhost:{{ registry_ext_port }}/v2/"
    status_code:    [200, 401]
    validate_certs: false
  register:     reg_ssl_check
  ignore_errors: true

# ── Authenticated check ───────────────────────
- name: Check registry ΌΔ credentials
  ansible.builtin.uri:
    url:            "https://localhost:{{ registry_ext_port }}/v2/"
    status_code:    200
    validate_certs: false
    url_username:   "{{ registry_users[0].username }}"
    url_password:   "{{ registry_users[0].password }}"
    force_basic_auth: true
  register:      reg_auth_check
  when:          registry_users | length > 0
  no_log:        true
  ignore_errors: true

# ── Test push & pull ──────────────────────────
- name: Login ÏƒÏ„Îż private registry
  community.docker.docker_login:
    registry_url:   "https://{{ registry_hostname }}:{{ registry_ext_port }}"
    username:       "{{ registry_users[0].username }}"
    password:       "{{ registry_users[0].password }}"
    tls_verify:     false
  no_log:  true
  when:    registry_users | length > 0
  register: login_result

- name: Pull test image
  community.docker.docker_image:
    name:   alpine
    tag:    "3.19"
    source: pull
  when: login_result is not failed

- name: Tag ÎłÎčα private registry
  community.docker.docker_image:
    name:       "alpine:3.19"
    repository: "{{ registry_hostname }}:{{ registry_ext_port }}/test/alpine"
    tag:        "3.19"
    source:     local
  when: login_result is not failed

- name: Push test image ÏƒÏ„Îż registry
  community.docker.docker_image:
    name:   "{{ registry_hostname }}:{{ registry_ext_port }}/test/alpine"
    tag:    "3.19"
    source: local
    push:   true
    tls_verify: false
  when:    login_result is not failed
  register: push_test

- name: Delete local test image
  community.docker.docker_image:
    name:         "{{ registry_hostname }}:{{ registry_ext_port }}/test/alpine"
    tag:          "3.19"
    state:        absent
    force_absent: true
  when: login_result is not failed

- name: Pull test image από registry
  community.docker.docker_image:
    name:       "{{ registry_hostname }}:{{ registry_ext_port }}/test/alpine"
    tag:        "3.19"
    source:     pull
    tls_verify: false
  when:    login_result is not failed
  register: pull_test

- name: Logout
  community.docker.docker_login:
    registry_url: "https://{{ registry_hostname }}:{{ registry_ext_port }}"
    username:     "{{ registry_users[0].username }}"
    state:        absent
  no_log: true
  when:   registry_users | length > 0

# ── List catalog ──────────────────────────────
- name: Get registry catalog
  ansible.builtin.uri:
    url:            "https://localhost:{{ registry_ext_port }}/v2/_catalog"
    status_code:    200
    validate_certs: false
    url_username:   "{{ registry_users[0].username }}"
    url_password:   "{{ registry_users[0].password }}"
    force_basic_auth: true
  register:     catalog
  when:         registry_users | length > 0
  no_log:       true
  ignore_errors: true

# ── Report ────────────────────────────────────
- name: Registry Verification Report
  ansible.builtin.debug:
    msg:
      - "╔══════════════════════════════════════╗"
      - "║   REGISTRY VERIFICATION              ║"
      - "╠══════════════════════════════════════╣"
      - "║ Host   : {{ registry_hostname }}"
      - "║ Port   : {{ registry_ext_port }}"
      - "║ SSL    : {{ '✅ Enabled' if registry_ssl_enabled else '❌ Disabled' }}"
      - "║ Auth   : {{ '✅ Enabled' if registry_auth_enabled else '❌ Disabled' }}"
      - "╠══════════════════════════════════════╣"
      - "║ CHECKS:                              ║"
      - "║ API    : {{ '✅ OK' if not reg_api_check.failed  else '❌ Failed' }}"
      - "║ SSL    : {{ '✅ OK' if not reg_ssl_check.failed  else '❌ Failed' }}"
      - "║ Auth   : {{ '✅ OK' if not reg_auth_check.failed else '❌ N/A'    }}"
      - "║ Push   : {{ '✅ OK' if not push_test.failed      else '❌ Failed' }}"
      - "║ Pull   : {{ '✅ OK' if not pull_test.failed      else '❌ Failed' }}"
      - "╠══════════════════════════════════════╣"
      - "║ Catalog: {{ catalog.json.repositories | default([]) | join(', ') | default('empty') }}"
      - "╚══════════════════════════════════════╝"
EOF

handlers/main.yml

cat > ~/ansible/roles/registry/handlers/main.yml << 'EOF'
---
- name: Restart registry
  ansible.builtin.command:
    cmd: >
      docker compose
      -f {{ registry_base_dir }}/docker-compose.yml
      -p {{ registry_name }}
      restart registry
  ignore_errors: "{{ ansible_check_mode }}"

- name: Reload nginx
  ansible.builtin.command:
    cmd: >
      docker compose
      -f {{ registry_base_dir }}/docker-compose.yml
      -p {{ registry_name }}
      exec -T nginx nginx -s reload
  ignore_errors: "{{ ansible_check_mode }}"
EOF

Vault Variables ÎłÎčα Registry

cat > /tmp/registry_vault.yml << 'EOF'
---
vault_registry_http_secret: "RegistrySecret@2026#SuperLong!"
vault_registry_admin_pass:  "AdminPass@2026!"
vault_registry_user_pass:   "UserPass@2026!"
EOF

# Î ÏÎżÏƒÎžÎźÎșη ÏƒÏ„Îż Ï…Ï€ÎŹÏÏ‡ÎżÎœ vault
ansible-vault edit \
    ~/ansible/inventory/group_vars/all/vault.yml
# → ΠρόσΞΔσΔ τα Ï€Î±ÏÎ±Ï€ÎŹÎœÏ‰

Deployment Playbook

cat > ~/ansible/playbooks/private-registry.yml << 'EOF'
---
# ============================================================
# Private Docker Registry Deployment
# ============================================================

- name: Deploy Private Docker Registry
  hosts: "{{ target | default('all_managed') }}"
  become: true
  gather_facts: true

  pre_tasks:

    - name: Registry deployment info
      ansible.builtin.debug:
        msg:
          - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
          - "Private Docker Registry Deployment"
          - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
          - "Host: {{ inventory_hostname }}"
          - "IP  : {{ ansible_facts['default_ipv4']['address'] }}"
          - "Port: {{ registry_ext_port | default(443) }}"
          - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
      tags: always

    - name: Open firewall port (Debian/ferm)
      ansible.builtin.lineinfile:
        path:   /etc/ferm/ferm.conf
        regexp: ".*{{ registry_ext_port | default(443) }}.*"
        line:   "  proto tcp dport {{ registry_ext_port | default(443) }} ACCEPT;"
        insertafter: "chain INPUT"
      when:
        - ansible_facts['os_family'] == "Debian"
      notify: Restart ferm
      ignore_errors: true

  roles:
    - role: registry
      vars:
        registry_hostname:    "{{ inventory_hostname }}"
        registry_ext_port:    443
        registry_auth_enabled: true
        registry_users:
          - username: admin
            password: "{{ vault_registry_admin_pass }}"
          - username: deploy
            password: "{{ vault_registry_user_pass }}"
        registry_ssl_self_signed: true
        registry_gc_enabled:  true
      tags: registry

  post_tasks:

    - name: Registry Access Info
      ansible.builtin.debug:
        msg:
          - "╔══════════════════════════════════════╗"
          - "║   REGISTRY DEPLOYMENT COMPLETE! 🎉   ║"
          - "╠══════════════════════════════════════╣"
          - "║ Registry URL:                        ║"
          - "║ {{ inventory_hostname }}:443"
          - "╠══════════════════════════════════════╣"
          - "║ Usage:                               ║"
          - "║ docker login {{ inventory_hostname }}"
          - "║ docker tag myapp:1.0 {{ inventory_hostname }}/myapp:1.0"
          - "║ docker push {{ inventory_hostname }}/myapp:1.0"
          - "╠══════════════════════════════════════╣"
          - "║ Note: Self-signed cert → add to      ║"
          - "║ /etc/docker/certs.d/ on clients!     ║"
          - "╚══════════════════════════════════════╝"
      tags: always

  handlers:
    - name: Restart ferm
      ansible.builtin.service:
        name:  ferm
        state: restarted
      when: ansible_facts['os_family'] == "Debian"
      ignore_errors: "{{ ansible_check_mode }}"
EOF

ΕÎșτέλΔση

# ── Syntax check ──────────────────────────────
ansible-playbook playbooks/private-registry.yml \
    --syntax-check

# ── Dry run ───────────────────────────────────
ansible-playbook playbooks/private-registry.yml \
    --check \
    --limit nextcloud

# ── Full deployment ───────────────────────────
ansible-playbook playbooks/private-registry.yml \
    --limit nextcloud \
    -v

# ── ÎœÏŒÎœÎż setup ────────────────────────────────
ansible-playbook playbooks/private-registry.yml \
    --tags setup \
    --limit nextcloud

# ── ÎœÏŒÎœÎż verify ───────────────────────────────
ansible-playbook playbooks/private-registry.yml \
    --tags verify \
    --limit nextcloud

Client Configuration

# ΣΔ ÎșΏΞΔ Docker client Ï€ÎżÏ… χρησÎčÎŒÎżÏ€ÎżÎčΔί Ï„Îż registry:

# ── Copy certificate ──────────────────────────
sudo mkdir -p /etc/docker/certs.d/nextcloud:443
sudo scp ansible@nextcloud:/opt/registry/certs/registry.crt \
    /etc/docker/certs.d/nextcloud:443/ca.crt

# ── Login ─────────────────────────────────────
docker login nextcloud:443
# Username: admin
# Password: ***

# ── Push ──────────────────────────────────────
docker tag nginx:alpine nextcloud:443/nginx:alpine
docker push nextcloud:443/nginx:alpine

# ── Pull ──────────────────────────────────────
docker pull nextcloud:443/nginx:alpine

ÎŁÏÎœÎżÏˆÎ· D5.3

Private Registry ΌΔ Ansible
├── Components:
│   ├── registry:2  → image storage (internal)
│   └── nginx       → SSL proxy (external :443)
│
├── Security:
│   ├── SSL: self-signed certificate
│   ├── Auth: htpasswd (bcrypt)
│   └── Internal network (registry_net)
│
├── Setup:
│   ├── Directories: data, auth, certs, logs
│   ├── SSL cert: openssl req -newkey rsa:4096
│   └── htpasswd: htpasswd -Bbc
│
├── Features:
│   ├── Garbage collection (cron)
│   ├── Storage delete enabled
│   └── Pull-through cache ready
│
└── Client:
    ├── Copy cert → /etc/docker/certs.d/
    ├── docker login
    └── docker push/pull