D5.2 Docker Hub Management 🐳

Τι είναι το Docker Hub

Docker Hub = https://hub.docker.com
├── Μεγαλύτερο public container registry
├── 8+ εκατομμύρια images
├── Official images: nginx, postgres, redis...
├── Rate limits (σημαντικό για production!)
└── Free tier: 1 private repo, rate limits

Rate Limits — Πολύ Σημαντικό!

┌─────────────────────────────────────────────────────────┐
│  Docker Hub Rate Limits (pulls/6 ώρες):                 │
│                                                         │
│  Anonymous   : 100 pulls / 6h / IP                      │
│  Free account: 200 pulls / 6h / account                 │
│  Pro account : Unlimited                                │
│  Team account: Unlimited                                │
│                                                         │
│  Πρόβλημα:                                              │
│  10 servers × 20 pulls = 200 pulls → LIMIT! ❌          │
│                                                         │
│  Λύση:                                                  │
│  ✅ Authenticated pulls (200/6h per account)            │
│  ✅ Private registry mirror (unlimited)                 │
│  ✅ Pull once → push to private registry                │
└─────────────────────────────────────────────────────────┘

Docker Hub Authentication

# tasks/docker_hub.yml
---
# ============================================================
# Docker Hub Authentication & Management
# ============================================================

# ── Login με Access Token (ασφαλέστερο!) ──────
- name: Login στο Docker Hub
  community.docker.docker_login:
    registry_url: "https://index.docker.io/v1/"
    username:     "{{ docker_hub_username }}"
    password:     "{{ vault_docker_hub_token }}"
    # ↑ ΠΑΝΤΑ Access Token, όχι password!
    reauthorize:  true
  no_log: true
  register: hub_login

- name: Login status
  ansible.builtin.debug:
    msg: "Docker Hub login: {{ '✅ OK' if not hub_login.failed else '❌ Failed' }}"

# ── Logout ────────────────────────────────────
- name: Logout από Docker Hub
  community.docker.docker_login:
    registry_url: "https://index.docker.io/v1/"
    username:     "{{ docker_hub_username }}"
    state:        absent
  no_log: true

💡 Access Token vs Password:

❌ Password → αν εκτεθεί, full account access
✅ Access Token → περιορισμένα permissions
                 ανακλητό ανά πάσα στιγμή

Δημιουργία token:
Docker Hub → Account Settings
  → Security → New Access Token

Pull από Docker Hub

tasks:

  # ── Authenticated pull ────────────────────
  - name: Login πριν pull
    community.docker.docker_login:
      username: "{{ docker_hub_username }}"
      password: "{{ vault_docker_hub_token }}"
    no_log: true

  # ── Pull public images ────────────────────
  - name: Pull official images
    community.docker.docker_image:
      name:   "{{ item.name }}"
      tag:    "{{ item.tag }}"
      source: pull
    loop:
      - { name: nginx,    tag: "1.25.3-alpine" }
      - { name: postgres, tag: "15.4-alpine"   }
      - { name: redis,    tag: "7.2-alpine"    }
      - { name: python,   tag: "3.12-slim"     }
    loop_control:
      label: "{{ item.name }}:{{ item.tag }}"

  # ── Pull private image ────────────────────
  - name: Pull private image από Docker Hub
    community.docker.docker_image:
      name:   "{{ docker_hub_username }}/myapp"
      tag:    "{{ app_version }}"
      source: pull

  # ── Logout μετά ───────────────────────────
  - name: Logout από Docker Hub
    community.docker.docker_login:
      username: "{{ docker_hub_username }}"
      state:    absent
    no_log: true

Push στο Docker Hub

tasks:

  # ── Build image ───────────────────────────
  - name: Build application image
    community.docker.docker_image:
      name:   "{{ app_name }}"
      tag:    "{{ app_version }}"
      source: build
      build:
        path: "{{ app_build_dir }}"
        args:
          APP_VERSION: "{{ app_version }}"
          BUILD_DATE:  "{{ ansible_facts['date_time']['date'] }}"

  # ── Tag για Docker Hub ────────────────────
  - name: Tag image για Docker Hub
    community.docker.docker_image:
      name:       "{{ app_name }}:{{ app_version }}"
      repository: "{{ docker_hub_username }}/{{ app_name }}"
      tag:        "{{ app_version }}"
      source:     local

  # ── Tag ως latest ─────────────────────────
  - name: Tag ως latest
    community.docker.docker_image:
      name:       "{{ app_name }}:{{ app_version }}"
      repository: "{{ docker_hub_username }}/{{ app_name }}"
      tag:        latest
      source:     local

  # ── Login ─────────────────────────────────
  - name: Login στο Docker Hub
    community.docker.docker_login:
      username: "{{ docker_hub_username }}"
      password: "{{ vault_docker_hub_token }}"
    no_log: true

  # ── Push ──────────────────────────────────
  - name: Push image στο Docker Hub
    community.docker.docker_image:
      name:   "{{ docker_hub_username }}/{{ app_name }}"
      tag:    "{{ item }}"
      source: local
      push:   true
    loop:
      - "{{ app_version }}"
      - latest
    loop_control:
      label: "{{ docker_hub_username }}/{{ app_name }}:{{ item }}"

  # ── Logout ────────────────────────────────
  - name: Logout
    community.docker.docker_login:
      username: "{{ docker_hub_username }}"
      state:    absent
    no_log: true

Docker Hub Mirror — Λύση για Rate Limits

# daemon.json με Docker Hub mirror
docker_daemon_config:
  registry-mirrors:
    - "https://mirror.example.com"    # ← private mirror
  log-driver: json-file
  log-opts:
    max-size: "10m"
    max-file: "3"
  storage-driver: overlay2
  live-restore: true
# tasks/configure_mirror.yml
---
# ── Deploy daemon config με mirror ────────────
- name: Configure Docker Hub mirror
  ansible.builtin.copy:
    dest:    /etc/docker/daemon.json
    content: "{{ docker_daemon_config | to_nice_json }}"
    mode:    '0644'
  notify: Restart Docker

# ── Verify mirror ─────────────────────────────
- name: Verify mirror configuration
  ansible.builtin.command:
    cmd: docker info --format "{{ '{{' }}.RegistryConfig.Mirrors{{ '}}' }}"
  register:     mirror_check
  changed_when: false

- name: Mirror status
  ansible.builtin.debug:
    msg: "Registry mirrors: {{ mirror_check.stdout }}"

Pull-Through Cache — Τοπικό Mirror

# Στήσε τοπικό Docker Hub mirror
# (pull-through cache)

# tasks/hub_mirror.yml
---
- name: Create registry data directory
  ansible.builtin.file:
    path:  /opt/registry-mirror/data
    state: directory
    mode:  '0755'

- name: Deploy registry mirror config
  ansible.builtin.copy:
    dest:  /opt/registry-mirror/config.yml
    mode:  '0644'
    content: |
      version: 0.1
      log:
        fields:
          service: registry-mirror
      storage:
        cache:
          blobdescriptor: inmemory
        filesystem:
          rootdirectory: /var/lib/registry
      http:
        addr: :5001
        headers:
          X-Content-Type-Options: [nosniff]
      proxy:
        remoteurl: https://registry-1.docker.io
        username:  {{ docker_hub_username }}
        password:  {{ vault_docker_hub_token }}

- name: Run Docker Hub mirror
  community.docker.docker_container:
    name:           registry_mirror
    image:          registry:2
    state:          started
    restart_policy: unless-stopped
    ports:
      - "127.0.0.1:5001:5001"
    volumes:
      - /opt/registry-mirror/data:/var/lib/registry
      - /opt/registry-mirror/config.yml:/etc/docker/registry/config.yml:ro
    memory: "256m"
    cpus:   "0.5"
    log_driver: json-file
    log_options:
      max-size: "10m"
      max-file: "3"
  no_log: true

# ── Configure servers να χρησιμοποιούν mirror ─
- name: Configure Docker to use local mirror
  ansible.builtin.copy:
    dest:    /etc/docker/daemon.json
    content: |
      {
        "registry-mirrors": ["http://127.0.0.1:5001"],
        "log-driver": "json-file",
        "log-opts": {
          "max-size": "10m",
          "max-file": "3"
        },
        "storage-driver": "overlay2",
        "live-restore": true
      }
    mode: '0644'
  notify: Restart Docker

Πλήρες Docker Hub Workflow

cat > ~/ansible/playbooks/docker-hub-workflow.yml << 'EOF'
---
# ============================================================
# Docker Hub Workflow
# Build → Tag → Push → Pull → Deploy
# ============================================================

- name: Docker Hub Workflow
  hosts: "{{ target | default('all_managed') }}"
  become: true
  gather_facts: true

  vars:
    docker_hub_username: "{{ vault_docker_hub_username }}"
    app_name:            myapp
    app_version:         "{{ app_version | default('1.0.0') }}"
    app_build_dir:       "/opt/{{ app_name }}/docker"

  tasks:

    # ══════════════════════════════════════════
    # BUILD
    # ══════════════════════════════════════════
    - name: Build application image
      community.docker.docker_image:
        name:   "{{ app_name }}"
        tag:    "{{ app_version }}"
        source: build
        build:
          path:    "{{ app_build_dir }}"
          pull:    true
          nocache: false
          args:
            APP_VERSION: "{{ app_version }}"
            BUILD_DATE:  "{{ ansible_facts['date_time']['date'] }}"
      tags: [build]

    # ══════════════════════════════════════════
    # TAG
    # ══════════════════════════════════════════
    - name: Tag image για Docker Hub
      community.docker.docker_image:
        name:       "{{ app_name }}:{{ app_version }}"
        repository: "{{ docker_hub_username }}/{{ app_name }}"
        tag:        "{{ item }}"
        source:     local
      loop:
        - "{{ app_version }}"
        - latest
      loop_control:
        label: "{{ docker_hub_username }}/{{ app_name }}:{{ item }}"
      tags: [tag]

    # ══════════════════════════════════════════
    # PUSH
    # ══════════════════════════════════════════
    - name: Login στο Docker Hub
      community.docker.docker_login:
        username: "{{ docker_hub_username }}"
        password: "{{ vault_docker_hub_token }}"
      no_log: true
      tags: [push]

    - name: Push image στο Docker Hub
      community.docker.docker_image:
        name:   "{{ docker_hub_username }}/{{ app_name }}"
        tag:    "{{ item }}"
        source: local
        push:   true
      loop:
        - "{{ app_version }}"
        - latest
      loop_control:
        label: "{{ docker_hub_username }}/{{ app_name }}:{{ item }}"
      tags: [push]

    - name: Logout από Docker Hub
      community.docker.docker_login:
        username: "{{ docker_hub_username }}"
        state:    absent
      no_log: true
      tags: [push]

    # ══════════════════════════════════════════
    # DEPLOY
    # ══════════════════════════════════════════
    - name: Login για pull
      community.docker.docker_login:
        username: "{{ docker_hub_username }}"
        password: "{{ vault_docker_hub_token }}"
      no_log: true
      tags: [deploy]

    - name: Pull latest image
      community.docker.docker_image:
        name:         "{{ docker_hub_username }}/{{ app_name }}"
        tag:          "{{ app_version }}"
        source:       pull
        force_source: true
      tags: [deploy]

    - name: Deploy container
      community.docker.docker_container:
        name:           "{{ app_name }}"
        image:          "{{ docker_hub_username }}/{{ app_name }}:{{ app_version }}"
        state:          started
        restart_policy: unless-stopped
        ports:
          - "8080:8080"
        recreate: true
      tags: [deploy]

    - name: Logout
      community.docker.docker_login:
        username: "{{ docker_hub_username }}"
        state:    absent
      no_log: true
      tags: [deploy]

    # ══════════════════════════════════════════
    # VERIFY
    # ══════════════════════════════════════════
    - name: Verify deployment
      ansible.builtin.uri:
        url:         "http://localhost:8080/health"
        status_code: 200
      register:      check
      retries:       5
      delay:         5
      ignore_errors: true
      tags: [verify]

    - name: Workflow Report
      ansible.builtin.debug:
        msg:
          - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
          - "✅ Docker Hub Workflow Complete"
          - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
          - "Image  : {{ docker_hub_username }}/{{ app_name }}:{{ app_version }}"
          - "Deploy : {{ '✅ OK' if not check.failed else '❌ Check manually' }}"
          - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
      tags: always
EOF
# Εκτέλεση
ansible-playbook playbooks/docker-hub-workflow.yml \
    --limit nextcloud \
    -e "app_version=1.0.0" \
    -v

# Μόνο push
ansible-playbook playbooks/docker-hub-workflow.yml \
    --limit nextcloud \
    --tags push \
    -e "app_version=1.0.0"

# Μόνο deploy
ansible-playbook playbooks/docker-hub-workflow.yml \
    --limit nextcloud \
    --tags deploy \
    -e "app_version=1.0.0"

group_vars για Docker Hub

# inventory/group_vars/all/vars.yml
---
# ── Docker Hub Settings ───────────────────────
docker_hub_registry: "https://index.docker.io/v1/"
docker_hub_username: "{{ vault_docker_hub_username }}"

# ── References to vault ───────────────────────
docker_hub_token: "{{ vault_docker_hub_token }}"
# inventory/group_vars/all/vault.yml (κρυπτογραφημένο)
---
vault_docker_hub_username: "myusername"
vault_docker_hub_token:    "dckr_pat_xxxxxxxxxxxx"

Σύνοψη D5.2

Docker Hub Management
├── Authentication:
│   ├── Access Token (όχι password!) ✅
│   ├── docker_login module + no_log
│   └── Logout μετά από κάθε operation
│
├── Rate Limits:
│   ├── Anonymous: 100/6h
│   ├── Free: 200/6h
│   └── Λύση: authenticated pulls ή mirror
│
├── Pull:
│   ├── Login → pull → logout
│   └── Explicit tags (όχι latest)
│
├── Push:
│   ├── Build → tag → login → push → logout
│   └── Push: version tag + latest
│
├── Mirror (Rate limit solution):
│   ├── Pull-through cache (registry:2)
│   └── daemon.json: registry-mirrors
│
└── Secrets:
    ├── vault_docker_hub_username
    └── vault_docker_hub_token