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