D4.1 Τι είναι Docker Compose για Ansible Users 🐳
Docker Compose vs Individual Containers
┌─────────────────────────────────────────────────────────┐
│ Individual Containers (D3) │
│ ├── Ένα task ανά container │
│ ├── Πολύς κώδικας για πολλά containers │
│ └── Δύσκολο να διαχειριστείς dependencies │
├─────────────────────────────────────────────────────────┤
│ Docker Compose │
│ ├── Ένα αρχείο για ΟΛΑ τα containers │
│ ├── Αυτόματη διαχείριση dependencies │
│ ├── Built-in networking │
│ └── Εύκολο up/down ολόκληρου stack │
└─────────────────────────────────────────────────────────┘
📝 Αναλογία:
Individual containers ≈ Tasks σε playbook Docker Compose ≈ Role (ομαδοποιεί τα πάντα)
Η Δομή ενός docker-compose.yml
# docker-compose.yml — βασική δομή
version: "3.8" # ← compose file format version
services: # ← τα containers
web: # ← όνομα service
image: nginx:alpine
ports:
- "80:80"
depends_on:
- app # ← ξεκινά ΜΕΤΑ το app
app:
image: myapp:1.0
environment:
DB_HOST: db
depends_on:
- db
db:
image: postgres:15-alpine
volumes:
- db_data:/var/lib/postgresql/data
volumes: # ← named volumes
db_data:
networks: # ← custom networks (optional)
frontend:
backend:
docker_compose_v2 Module
tasks:
# ── Βασικό deploy ─────────────────────────
- name: Deploy stack
community.docker.docker_compose_v2:
project_src: /opt/myapp # ← φάκελος με docker-compose.yml
state: present # ← present=up, absent=down
# ── Με pull πριν deploy ───────────────────
- name: Deploy με pull
community.docker.docker_compose_v2:
project_src: /opt/myapp
pull: always # ← always/missing/never
state: present
# ── Με custom compose file ────────────────
- name: Deploy με custom file
community.docker.docker_compose_v2:
project_src: /opt/myapp
files:
- docker-compose.yml
- docker-compose.prod.yml # ← override file
state: present
# ── Remove stack ──────────────────────────
- name: Remove stack
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: absent
remove_volumes: true # ← διέγραψε volumes
# ── Με project name ───────────────────────
- name: Deploy με custom project name
community.docker.docker_compose_v2:
project_src: /opt/myapp
project_name: myapp_prod # ← default: folder name
state: present
# ── Recreate services ─────────────────────
- name: Force recreate
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: present
recreate: always # ← always/never/auto
Compose vs docker_container — Πότε τι
┌──────────────────────┬────────────────┬────────────────────┐
│ Χαρακτηριστικό │ docker_ │ docker_ │
│ │ container │ compose_v2 │
├──────────────────────┼────────────────┼────────────────────┤
│ Ένα container │ ✅ Ιδανικό │ Overkill │
│ Πολλά containers │ Πολύς κώδικας │ ✅ Ιδανικό │
│ Dependencies │ Χειροκίνητα │ ✅ Αυτόματα │
│ Dev workflow │ ⚠️ │ ✅ docker compose │
│ Production │ ✅ Fine-grained│✅ Εύκολο │
│ Flexibility │ ✅ Μέγιστη │ ⚠️ Compose syntax │
│ Portability │ ⚠️ Ansible │ ✅ Παντού │
└──────────────────────┴────────────────┴────────────────────┘
Compose File Versions
# Version 3.x — για Docker Swarm & standalone
version: "3.8" # ← τελευταία 3.x
# Version 2.x — μόνο standalone
version: "2.4"
# Χωρίς version — νέο format (Docker Compose v2+)
# (απλά παραλείπεις το version field)
services:
web:
image: nginx
💡 Σύσταση: Χρησιμοποίησε
version: "3.8"ή νεότερο για καλύτερη συμβατότητα.
Σημαντικά Compose Keywords
services:
myservice:
# ── Image ─────────────────────────────────
image: nginx:1.25.3-alpine
# ── Build (αντί για image) ────────────────
build:
context: ./docker
dockerfile: Dockerfile
args:
VERSION: "1.0"
# ── Ports ─────────────────────────────────
ports:
- "80:80"
- "127.0.0.1:8080:8080" # ← bind σε specific IP
# ── Environment ───────────────────────────
environment:
NODE_ENV: production
PORT: 3000
env_file:
- .env # ← από αρχείο
# ── Volumes ───────────────────────────────
volumes:
- db_data:/var/lib/postgresql/data
- ./config:/app/config:ro
# ── Networks ──────────────────────────────
networks:
- frontend
- backend
# ── Dependencies ──────────────────────────
depends_on:
db:
condition: service_healthy # ← περίμενε health check
# ── Health check ──────────────────────────
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
# ── Restart ───────────────────────────────
restart: unless-stopped
# ── Resources ─────────────────────────────
deploy:
resources:
limits:
memory: 512m
cpus: "0.5"
# ── Labels ────────────────────────────────
labels:
app: myapp
version: "1.0"
# ── Logging ───────────────────────────────
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
depends_on — Σειρά εκκίνησης
services:
nginx:
image: nginx:alpine
depends_on:
app:
condition: service_healthy # ← περίμενε healthy
app:
image: myapp:1.0
depends_on:
db:
condition: service_healthy
redis:
condition: service_started # ← μόνο started
db:
image: postgres:15-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 5
redis:
image: redis:alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 3
# ── Σειρά εκκίνησης: ──────────────────────────
# 1. db (ξεκινά πρώτο)
# 2. redis (ξεκινά πρώτο)
# 3. app (αφού db + redis healthy)
# 4. nginx (αφού app healthy)
Ansible + Compose — Workflow
┌─────────────────────────────────────────────────────────┐
│ Ansible Controller │
│ ├── docker-compose.yml.j2 (Jinja2 template) │
│ ├── .env.j2 (secrets template) │
│ └── playbook.yml │
│ │ │
│ │ 1. Render templates │
│ │ 2. Copy to server │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Server │ │
│ │ /opt/myapp/ │ │
│ │ ├── docker-compose.yml (rendered) │ │
│ │ └── .env (rendered, no_log) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ docker_compose_v2: state=present │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ Stack: myapp │ │ │
│ │ │ nginx → app → postgres → redis │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Βασικό παράδειγμα
---
# playbooks/compose-basic.yml
- name: Basic Docker Compose Deploy
hosts: "{{ target | default('all_managed') }}"
become: true
gather_facts: true
vars:
app_name: myapp
app_version: "1.0.0"
app_dir: "/opt/{{ app_name }}"
tasks:
# ── Δημιουργία directory ───────────────────
- name: Create app directory
ansible.builtin.file:
path: "{{ app_dir }}"
state: directory
mode: '0755'
# ── Deploy compose file ───────────────────
- name: Deploy docker-compose.yml
ansible.builtin.copy:
dest: "{{ app_dir }}/docker-compose.yml"
mode: '0644'
content: |
version: "3.8"
services:
web:
image: nginx:1.25.3-alpine
ports:
- "80:80"
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost/"]
interval: 30s
timeout: 10s
retries: 3
# ── Deploy stack ──────────────────────────
- name: Deploy stack
community.docker.docker_compose_v2:
project_src: "{{ app_dir }}"
project_name: "{{ app_name }}"
state: present
register: compose_result
# ── Status ────────────────────────────────
- name: Stack status
ansible.builtin.debug:
msg:
- "Stack: {{ app_name }}"
- "Changed: {{ compose_result.changed }}"
# ── Verify ────────────────────────────────
- name: Verify deployment
ansible.builtin.uri:
url: http://localhost
status_code: 200
retries: 5
delay: 5
register: http_check
ignore_errors: true
- name: Result
ansible.builtin.debug:
msg: "HTTP: {{ '✅ OK' if not http_check.failed else '❌ Failed' }}"
# Εκτέλεση
ansible-playbook playbooks/compose-basic.yml \
--limit nextcloud -v
Σύνοψη D4.1
Docker Compose για Ansible Users
├── Compose vs individual containers:
│ ├── Compose → πολλά containers, dependencies
│ └── container → fine-grained control
│
├── docker_compose_v2 module:
│ ├── project_src → φάκελος με compose file
│ ├── state: present/absent
│ ├── pull: always/missing/never
│ └── recreate: always/auto/never
│
├── Compose keywords:
│ ├── services, volumes, networks
│ ├── depends_on + condition
│ ├── healthcheck
│ └── deploy.resources (limits)
│
└── Ansible + Compose workflow:
├── Template → render → copy → compose up
└── Secrets με .env + no_log