D4.4 Πρακτικό Full Stack με Docker Compose 🚀
Τι θα φτιάξουμε
┌─────────────────────────────────────────────────────────┐
│ Full Stack: WordPress + MariaDB + Redis + Nginx │
│ │
│ Internet :80 │
│ │ │
│ ▼ │
│ ┌─────────┐ │
│ │ Nginx │ ← Reverse proxy + SSL termination │
│ └────┬────┘ │
│ │ :9000 (PHP-FPM) │
│ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │WordPress│───►│ MariaDB │ │ Redis │ │
│ │ (PHP) │ │ :3306 │ │ :6379 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Γιατί WordPress; │
│ ✅ Real-world παράδειγμα │
│ ✅ PHP + MySQL + Redis — κλασικό stack │
│ ✅ Γνωστό σε όλους │
└─────────────────────────────────────────────────────────┘
Δομή Project
# Δημιουργία δομής
mkdir -p ~/ansible/roles/wordpress/{tasks,templates,defaults,handlers}
tree ~/ansible/roles/wordpress/
# roles/wordpress/
# ├── defaults/
# │ └── main.yml
# ├── handlers/
# │ └── main.yml
# ├── tasks/
# │ ├── main.yml
# │ ├── setup.yml
# │ ├── deploy.yml
# │ └── verify.yml
# └── templates/
# ├── docker-compose.yml.j2
# ├── nginx.conf.j2
# └── wp.env.j2
defaults/main.yml
cat > ~/ansible/roles/wordpress/defaults/main.yml << 'EOF'
---
# ============================================================
# WordPress Stack Defaults
# ============================================================
# ── Stack Info ────────────────────────────────
stack_name: wordpress
stack_version: "1.0.0"
app_env: production
# ── Paths ─────────────────────────────────────
stack_base_dir: "/opt/{{ stack_name }}"
stack_config_dir: "/opt/{{ stack_name }}/config"
stack_data_dir: "/opt/{{ stack_name }}/data"
stack_logs_dir: "/opt/{{ stack_name }}/logs"
# ── WordPress ─────────────────────────────────
wp_image: wordpress:6.4-php8.2-fpm-alpine
wp_site_url: "http://{{ ansible_facts['default_ipv4']['address'] }}"
wp_site_title: "My WordPress Site"
wp_admin_user: admin
wp_admin_email: "admin@example.com"
# ── MariaDB ───────────────────────────────────
db_image: mariadb:10.11
db_name: wordpress_db
db_user: wordpress_user
db_port: 3306
# ── Redis ─────────────────────────────────────
redis_image: redis:7.2-alpine
redis_port: 6379
redis_maxmem: "128mb"
# ── Nginx ─────────────────────────────────────
nginx_image: nginx:1.25.3-alpine
nginx_port: 80
# ── Resource Limits ───────────────────────────
nginx_memory: "64m"
nginx_cpus: "0.25"
wp_memory: "256m"
wp_cpus: "0.5"
db_memory: "512m"
db_cpus: "0.5"
redis_memory: "128m"
redis_cpus: "0.25"
EOF
templates/docker-compose.yml.j2
cat > ~/ansible/roles/wordpress/templates/docker-compose.yml.j2 << 'EOF'
{# WordPress Full Stack — docker-compose.yml #}
{# Managed by Ansible — DO NOT EDIT MANUALLY! #}
{# Generated: {{ ansible_facts['date_time']['iso8601'] }} #}
{# Host: {{ inventory_hostname }} #}
version: "3.8"
services:
# ══════════════════════════════════════════
# NGINX — Reverse Proxy
# ══════════════════════════════════════════
nginx:
image: {{ nginx_image }}
container_name: {{ stack_name }}_nginx
restart: unless-stopped
ports:
- "{{ nginx_port }}:80"
volumes:
- {{ stack_config_dir }}/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- wp_files:/var/www/html:ro
- {{ stack_logs_dir }}/nginx:/var/log/nginx
- /etc/localtime:/etc/localtime:ro
networks:
- frontend
depends_on:
wordpress:
condition: service_started
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", "http://localhost/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
labels:
stack: {{ stack_name }}
component: nginx
env: {{ app_env }}
# ══════════════════════════════════════════
# WORDPRESS — PHP-FPM
# ══════════════════════════════════════════
wordpress:
image: {{ wp_image }}
container_name: {{ stack_name }}_wordpress
restart: unless-stopped
env_file:
- {{ stack_config_dir }}/wp.env
volumes:
- wp_files:/var/www/html
- {{ stack_config_dir }}/php/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini:ro
- /etc/localtime:/etc/localtime:ro
networks:
- frontend
- backend
depends_on:
mariadb:
condition: service_healthy
redis:
condition: service_healthy
deploy:
resources:
limits:
memory: {{ wp_memory }}
cpus: "{{ wp_cpus }}"
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
healthcheck:
test: ["CMD-SHELL", "php-fpm -t || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
labels:
stack: {{ stack_name }}
component: wordpress
version: "{{ stack_version }}"
# ══════════════════════════════════════════
# MARIADB — Database
# ══════════════════════════════════════════
mariadb:
image: {{ db_image }}
container_name: {{ stack_name }}_mariadb
restart: unless-stopped
environment:
MYSQL_DATABASE: {{ db_name }}
MYSQL_USER: {{ db_user }}
MYSQL_PASSWORD: {{ vault_db_password }}
MYSQL_ROOT_PASSWORD: {{ vault_db_root_password }}
MYSQL_COLLATION: utf8mb4_unicode_ci
MYSQL_CHARACTER_SET: utf8mb4
volumes:
- db_data:/var/lib/mysql
- {{ stack_config_dir }}/mysql/custom.cnf:/etc/mysql/conf.d/custom.cnf:ro
- /etc/localtime:/etc/localtime:ro
networks:
- backend
deploy:
resources:
limits:
memory: {{ db_memory }}
cpus: "{{ db_cpus }}"
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
start_period: 60s
labels:
stack: {{ stack_name }}
component: mariadb
# ══════════════════════════════════════════
# REDIS — Cache
# ══════════════════════════════════════════
redis:
image: {{ redis_image }}
container_name: {{ stack_name }}_redis
restart: unless-stopped
command: >
redis-server
--maxmemory {{ redis_maxmem }}
--maxmemory-policy allkeys-lru
--appendonly yes
--requirepass {{ vault_redis_password }}
volumes:
- redis_data:/data
- /etc/localtime:/etc/localtime:ro
networks:
- backend
deploy:
resources:
limits:
memory: {{ redis_memory }}
cpus: "{{ redis_cpus }}"
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
healthcheck:
test: ["CMD", "redis-cli", "--no-auth-warning", "-a", "{{ vault_redis_password }}", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
labels:
stack: {{ stack_name }}
component: redis
# ══════════════════════════════════════════
# VOLUMES
# ══════════════════════════════════════════
volumes:
wp_files:
name: {{ stack_name }}_wp_files
labels:
stack: {{ stack_name }}
env: {{ app_env }}
db_data:
name: {{ stack_name }}_db_data
labels:
stack: {{ stack_name }}
env: {{ app_env }}
redis_data:
name: {{ stack_name }}_redis_data
labels:
stack: {{ stack_name }}
env: {{ app_env }}
# ══════════════════════════════════════════
# NETWORKS
# ══════════════════════════════════════════
networks:
frontend:
name: {{ stack_name }}_frontend
driver: bridge
backend:
name: {{ stack_name }}_backend
driver: bridge
internal: true
EOF
templates/wp.env.j2
cat > ~/ansible/roles/wordpress/templates/wp.env.j2 << 'EOF'
{# WordPress Environment Variables #}
{# Managed by Ansible Vault — DO NOT EDIT! #}
# ── Database ──────────────────────────────────
WORDPRESS_DB_HOST=mariadb:{{ db_port }}
WORDPRESS_DB_NAME={{ db_name }}
WORDPRESS_DB_USER={{ db_user }}
WORDPRESS_DB_PASSWORD={{ vault_db_password }}
WORDPRESS_TABLE_PREFIX=wp_
# ── WordPress Auth Keys (από Vault) ───────────
WORDPRESS_AUTH_KEY={{ vault_wp_auth_key }}
WORDPRESS_SECURE_AUTH_KEY={{ vault_wp_secure_auth_key }}
WORDPRESS_LOGGED_IN_KEY={{ vault_wp_logged_in_key }}
WORDPRESS_NONCE_KEY={{ vault_wp_nonce_key }}
WORDPRESS_AUTH_SALT={{ vault_wp_auth_salt }}
WORDPRESS_SECURE_AUTH_SALT={{ vault_wp_secure_auth_salt }}
WORDPRESS_LOGGED_IN_SALT={{ vault_wp_logged_in_salt }}
WORDPRESS_NONCE_SALT={{ vault_wp_nonce_salt }}
# ── WordPress Config ──────────────────────────
WORDPRESS_DEBUG={{ 'false' if app_env == 'production' else 'true' }}
WORDPRESS_CONFIG_EXTRA=
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', {{ redis_port }});
define('WP_REDIS_PASSWORD', '{{ vault_redis_password }}');
define('WP_REDIS_DATABASE', 0);
define('WP_CACHE', true);
define('WP_HOME', '{{ wp_site_url }}');
define('WP_SITEURL', '{{ wp_site_url }}');
define('WP_MEMORY_LIMIT', '256M');
EOF
templates/nginx.conf.j2
cat > ~/ansible/roles/wordpress/templates/nginx.conf.j2 << 'EOF'
{# Nginx config for WordPress #}
{# Managed by Ansible — DO NOT EDIT! #}
user nginx;
worker_processes {{ ansible_facts['processor_vcpus'] | default(1) }};
pid /var/run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# ── Logging ───────────────────────────────
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
# ── Gzip ──────────────────────────────────
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain text/css text/xml
application/json application/javascript
application/xml application/xml+rss
text/javascript image/svg+xml;
# ── WordPress Server ──────────────────────
server {
listen 80;
server_name {{ inventory_hostname }} _;
root /var/www/html;
index index.php index.html;
client_max_body_size 64M;
# ── Security headers ──────────────────
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# ── WordPress permalinks ──────────────
location / {
try_files $uri $uri/ /index.php?$args;
}
# ── PHP via PHP-FPM ───────────────────
location ~ \.php$ {
fastcgi_pass wordpress:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_connect_timeout 60s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
}
# ── Static files ──────────────────────
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
log_not_found off;
access_log off;
}
# ── WordPress specific ────────────────
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
# ── Block sensitive files ─────────────
location ~* /\. {
deny all;
}
location ~* /(?:uploads|files)/.*\.php$ {
deny all;
}
# ── Block xmlrpc ──────────────────────
location = /xmlrpc.php {
deny all;
access_log off;
log_not_found off;
}
# ── wp-admin protection ───────────────
location /wp-admin/ {
location ~ \.php$ {
fastcgi_pass wordpress:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
}
}
EOF
tasks/main.yml
cat > ~/ansible/roles/wordpress/tasks/main.yml << 'EOF'
---
# ============================================================
# WordPress Stack — Main Entry Point
# ============================================================
- name: Setup directories and configs
ansible.builtin.import_tasks:
file: setup.yml
tags: [setup, wordpress]
- name: Deploy WordPress stack
ansible.builtin.import_tasks:
file: deploy.yml
tags: [deploy, wordpress]
- name: Verify deployment
ansible.builtin.import_tasks:
file: verify.yml
tags: [verify, wordpress]
EOF
tasks/setup.yml
cat > ~/ansible/roles/wordpress/tasks/setup.yml << 'EOF'
---
# ============================================================
# Setup — Directories & Configuration Files
# ============================================================
# ── Directories ───────────────────────────────
- name: Create directory structure
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: '0755'
owner: root
group: docker
loop:
- "{{ stack_base_dir }}"
- "{{ stack_config_dir }}"
- "{{ stack_config_dir }}/nginx"
- "{{ stack_config_dir }}/mysql"
- "{{ stack_config_dir }}/php"
- "{{ stack_data_dir }}"
- "{{ stack_logs_dir }}"
- "{{ stack_logs_dir }}/nginx"
# ── Docker Compose ────────────────────────────
- name: Render docker-compose.yml
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ stack_base_dir }}/docker-compose.yml"
mode: '0644'
register: compose_changed
# ── Environment file (secrets) ────────────────
- name: Render WordPress env file
ansible.builtin.template:
src: wp.env.j2
dest: "{{ stack_config_dir }}/wp.env"
mode: '0600'
owner: root
no_log: true
register: env_changed
# ── Nginx config ──────────────────────────────
- name: Render nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: "{{ stack_config_dir }}/nginx/nginx.conf"
mode: '0644'
register: nginx_changed
# ── PHP config ────────────────────────────────
- name: Deploy PHP uploads config
ansible.builtin.copy:
dest: "{{ stack_config_dir }}/php/uploads.ini"
mode: '0644'
content: |
; PHP Upload Configuration
file_uploads = On
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
max_execution_time = 300
max_input_vars = 3000
# ── MariaDB config ────────────────────────────
- name: Deploy MariaDB configuration
ansible.builtin.copy:
dest: "{{ stack_config_dir }}/mysql/custom.cnf"
mode: '0644'
content: |
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
innodb_buffer_pool_size = {{ (ansible_facts['memtotal_mb'] * 0.25) | int }}M
innodb_log_file_size = 32M
max_connections = 100
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/slow-query.log
long_query_time = 2
[client]
default-character-set = utf8mb4
# ── Validate compose file ─────────────────────
- name: Validate docker-compose.yml
ansible.builtin.command:
cmd: docker compose -f {{ stack_base_dir }}/docker-compose.yml config --quiet
chdir: "{{ stack_base_dir }}"
changed_when: false
- name: Setup complete
ansible.builtin.debug:
msg:
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- "✅ Setup complete"
- "Compose : {{ 'Changed' if compose_changed.changed else 'OK' }}"
- "Env file : {{ 'Changed' if env_changed.changed else 'OK' }}"
- "Nginx : {{ 'Changed' if nginx_changed.changed else 'OK' }}"
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
EOF
tasks/deploy.yml
cat > ~/ansible/roles/wordpress/tasks/deploy.yml << 'EOF'
---
# ============================================================
# Deploy — Docker Compose Stack
# ============================================================
# ── Pull images ───────────────────────────────
- name: Pull all Docker images
ansible.builtin.command:
cmd: docker compose -f {{ stack_base_dir }}/docker-compose.yml pull
chdir: "{{ stack_base_dir }}"
register: pull_result
changed_when: "'Pull complete' in pull_result.stdout or 'Downloaded' in pull_result.stdout"
# ── Deploy stack ──────────────────────────────
- name: Deploy WordPress stack
community.docker.docker_compose_v2:
project_src: "{{ stack_base_dir }}"
project_name: "{{ stack_name }}"
state: present
pull: missing
register: stack_result
no_log: true
- name: Stack deployment result
ansible.builtin.debug:
msg: "Stack {{ stack_name }}: {{ 'Changed ✅' if stack_result.changed else 'Already up-to-date ✅' }}"
# ── Wait for MariaDB ──────────────────────────
- name: Wait for MariaDB to be healthy
community.docker.docker_container_info:
name: "{{ stack_name }}_mariadb"
register: mariadb_info
until: >
mariadb_info.container is defined and
mariadb_info.container.State.Health.Status == 'healthy'
retries: 18
delay: 10
- name: MariaDB status
ansible.builtin.debug:
msg: "✅ MariaDB: {{ mariadb_info.container.State.Health.Status }}"
# ── Wait for Redis ────────────────────────────
- name: Wait for Redis to be healthy
community.docker.docker_container_info:
name: "{{ stack_name }}_redis"
register: redis_info
until: >
redis_info.container is defined and
redis_info.container.State.Health.Status == 'healthy'
retries: 6
delay: 5
- name: Redis status
ansible.builtin.debug:
msg: "✅ Redis: {{ redis_info.container.State.Health.Status }}"
# ── Wait for WordPress ────────────────────────
- name: Wait for WordPress to start
community.docker.docker_container_info:
name: "{{ stack_name }}_wordpress"
register: wp_info
until: >
wp_info.container is defined and
wp_info.container.State.Running == true
retries: 12
delay: 5
- name: WordPress status
ansible.builtin.debug:
msg: "✅ WordPress: {{ wp_info.container.State.Status }}"
# ── Wait for Nginx ────────────────────────────
- name: Wait for Nginx to be healthy
community.docker.docker_container_info:
name: "{{ stack_name }}_nginx"
register: nginx_info
until: >
nginx_info.container is defined and
nginx_info.container.State.Running == true
retries: 6
delay: 5
- name: Nginx status
ansible.builtin.debug:
msg: "✅ Nginx: {{ nginx_info.container.State.Status }}"
EOF
tasks/verify.yml
cat > ~/ansible/roles/wordpress/tasks/verify.yml << 'EOF'
---
# ============================================================
# Verify — Επαλήθευση Deployment
# ============================================================
# ── All containers status ─────────────────────
- name: Get all stack containers
ansible.builtin.command:
cmd: >
docker compose
-f {{ stack_base_dir }}/docker-compose.yml
-p {{ stack_name }}
ps --format "table {{.Name}}\t{{.Status}}\t{{.Health}}"
register: stack_ps
changed_when: false
- name: Stack containers
ansible.builtin.debug:
msg: "{{ stack_ps.stdout_lines }}"
# ── HTTP check ────────────────────────────────
- name: HTTP health check
ansible.builtin.uri:
url: "http://localhost:{{ nginx_port }}"
status_code: [200, 301, 302]
timeout: 30
register: http_check
retries: 5
delay: 10
ignore_errors: true
# ── WordPress check ───────────────────────────
- name: WordPress wp-login.php check
ansible.builtin.uri:
url: "http://localhost:{{ nginx_port }}/wp-login.php"
status_code: 200
timeout: 30
register: wp_check
retries: 3
delay: 5
ignore_errors: true
# ── Database check ────────────────────────────
- name: Check MariaDB connectivity
ansible.builtin.command:
cmd: >
docker compose
-f {{ stack_base_dir }}/docker-compose.yml
-p {{ stack_name }}
exec -T mariadb
mysqladmin ping
-u root
-p{{ vault_db_root_password }}
register: db_check
changed_when: false
no_log: true
ignore_errors: true
# ── Redis check ───────────────────────────────
- name: Check Redis connectivity
ansible.builtin.command:
cmd: >
docker compose
-f {{ stack_base_dir }}/docker-compose.yml
-p {{ stack_name }}
exec -T redis
redis-cli --no-auth-warning
-a {{ vault_redis_password }}
ping
register: redis_check
changed_when: false
no_log: true
ignore_errors: true
# ── Resource usage ────────────────────────────
- name: Get container resource usage
ansible.builtin.command:
cmd: >
docker stats --no-stream
--format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
{{ stack_name }}_nginx
{{ stack_name }}_wordpress
{{ stack_name }}_mariadb
{{ stack_name }}_redis
register: stats
changed_when: false
ignore_errors: true
# ── Disk usage ────────────────────────────────
- name: Check Docker disk usage
ansible.builtin.command:
cmd: docker system df
register: disk_usage
changed_when: false
# ── Final report ──────────────────────────────
- name: Deployment Verification Report
ansible.builtin.debug:
msg:
- "╔══════════════════════════════════════════╗"
- "║ WORDPRESS STACK VERIFICATION ║"
- "╠══════════════════════════════════════════╣"
- "║ Host : {{ inventory_hostname }}"
- "║ URL : {{ wp_site_url }}"
- "╠══════════════════════════════════════════╣"
- "║ SERVICES: ║"
- "║ Nginx : {{ '✅ Running' if nginx_info.container.State.Running else '❌ Stopped' }}"
- "║ WordPress: {{ '✅ Running' if wp_info.container.State.Running else '❌ Stopped' }}"
- "║ MariaDB : {{ '✅ Healthy' if mariadb_info.container.State.Health.Status == 'healthy' else '❌ Unhealthy' }}"
- "║ Redis : {{ '✅ Healthy' if redis_info.container.State.Health.Status == 'healthy' else '❌ Unhealthy' }}"
- "╠══════════════════════════════════════════╣"
- "║ CHECKS: ║"
- "║ HTTP : {{ '✅ OK' if not http_check.failed else '❌ Failed' }}"
- "║ WP Login : {{ '✅ OK' if not wp_check.failed else '❌ Failed' }}"
- "║ Database : {{ '✅ OK' if not db_check.failed else '❌ Failed' }}"
- "║ Redis : {{ '✅ OK' if not redis_check.failed else '❌ Failed' }}"
- "╠══════════════════════════════════════════╣"
- "║ RESOURCES: ║"
- "{{ stats.stdout_lines | default(['N/A']) | join('\n') }}"
- "╚══════════════════════════════════════════╝"
EOF
handlers/main.yml
cat > ~/ansible/roles/wordpress/handlers/main.yml << 'EOF'
---
- name: Reload nginx
ansible.builtin.command:
cmd: >
docker compose
-f {{ stack_base_dir }}/docker-compose.yml
-p {{ stack_name }}
exec -T nginx nginx -s reload
ignore_errors: "{{ ansible_check_mode }}"
- name: Restart wordpress
ansible.builtin.command:
cmd: >
docker compose
-f {{ stack_base_dir }}/docker-compose.yml
-p {{ stack_name }}
restart wordpress
ignore_errors: "{{ ansible_check_mode }}"
EOF
Vault Variables
# Δημιουργία vault secrets
cat > /tmp/wp_vault.yml << 'EOF'
---
# WordPress Secrets
vault_db_password: "WPDbPass@2026!"
vault_db_root_password: "WPRootPass@2026!"
vault_redis_password: "WPRedis@2026!"
# WordPress Security Keys (πρέπει να είναι τυχαία!)
vault_wp_auth_key: "put-random-string-here-1"
vault_wp_secure_auth_key: "put-random-string-here-2"
vault_wp_logged_in_key: "put-random-string-here-3"
vault_wp_nonce_key: "put-random-string-here-4"
vault_wp_auth_salt: "put-random-string-here-5"
vault_wp_secure_auth_salt: "put-random-string-here-6"
vault_wp_logged_in_salt: "put-random-string-here-7"
vault_wp_nonce_salt: "put-random-string-here-8"
EOF
cp /tmp/wp_vault.yml \
~/ansible/inventory/group_vars/all/vault.yml
ansible-vault encrypt \
~/ansible/inventory/group_vars/all/vault.yml
rm /tmp/wp_vault.yml
Deployment Playbook
cat > ~/ansible/playbooks/wordpress-stack.yml << 'EOF'
---
# ============================================================
# WordPress Full Stack Deployment
# ============================================================
- name: Deploy WordPress Stack
hosts: "{{ target | default('all_managed') }}"
become: true
gather_facts: true
pre_tasks:
- name: Pre-deployment info
ansible.builtin.debug:
msg:
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- "WordPress Stack Deployment"
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- "Host : {{ inventory_hostname }}"
- "IP : {{ ansible_facts['default_ipv4']['address'] }}"
- "OS : {{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}"
- "RAM : {{ (ansible_facts['memtotal_mb'] / 1024) | round(1) }}GB"
- "CPUs : {{ ansible_facts['processor_vcpus'] }}"
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
tags: always
- name: Verify Docker
ansible.builtin.service_facts:
- name: Assert Docker running
ansible.builtin.assert:
that:
- ansible_facts.services['docker.service'].state == 'running'
fail_msg: "❌ Docker is not running!"
tags: always
roles:
- role: wordpress
vars:
wp_site_url: "http://{{ ansible_facts['default_ipv4']['address'] }}"
wp_site_title: "My WordPress Site"
wp_admin_user: admin
app_env: production
tags: wordpress
post_tasks:
- name: WordPress Access Info
ansible.builtin.debug:
msg:
- "╔══════════════════════════════════════════╗"
- "║ WORDPRESS DEPLOYMENT COMPLETE! 🎉 ║"
- "╠══════════════════════════════════════════╣"
- "║ Site URL : http://{{ ansible_facts['default_ipv4']['address'] }}"
- "║ Admin URL : http://{{ ansible_facts['default_ipv4']['address'] }}/wp-admin/"
- "║ Admin User : {{ wp_admin_user | default('admin') }}"
- "╠══════════════════════════════════════════╣"
- "║ Stack Commands: ║"
- "║ Status: docker compose -p wordpress ps ║"
- "║ Logs : docker compose -p wordpress logs║"
- "║ Stop : docker compose -p wordpress stop║"
- "╚══════════════════════════════════════════╝"
tags: always
EOF
Εκτέλεση
# ── Syntax check ──────────────────────────────
ansible-playbook playbooks/wordpress-stack.yml \
--syntax-check
# ── Dry run ───────────────────────────────────
ansible-playbook playbooks/wordpress-stack.yml \
--check \
--limit nextcloud
# ── Full deployment ───────────────────────────
ansible-playbook playbooks/wordpress-stack.yml \
--limit nextcloud \
-v
# ── Μόνο setup ────────────────────────────────
ansible-playbook playbooks/wordpress-stack.yml \
--tags setup \
--limit nextcloud
# ── Μόνο deploy ───────────────────────────────
ansible-playbook playbooks/wordpress-stack.yml \
--tags deploy \
--limit nextcloud
# ── Μόνο verify ───────────────────────────────
ansible-playbook playbooks/wordpress-stack.yml \
--tags verify \
--limit nextcloud
Χρήσιμες Ad-hoc εντολές
# ── Stack status ──────────────────────────────
ansible nextcloud -m ansible.builtin.command \
-a "docker compose -p wordpress ps" \
--become
# ── Logs WordPress ────────────────────────────
ansible nextcloud -m ansible.builtin.command \
-a "docker logs wordpress_wordpress --tail 30" \
--become
# ── Logs MariaDB ──────────────────────────────
ansible nextcloud -m ansible.builtin.command \
-a "docker logs wordpress_mariadb --tail 20" \
--become
# ── Resource usage ────────────────────────────
ansible nextcloud -m ansible.builtin.command \
-a "docker stats --no-stream" \
--become
# ── Stop stack ────────────────────────────────
ansible nextcloud -m community.docker.docker_compose_v2 \
-a "project_src=/opt/wordpress state=stopped" \
--become
# ── Remove stack ──────────────────────────────
ansible nextcloud -m community.docker.docker_compose_v2 \
-a "project_src=/opt/wordpress state=absent remove_volumes=true" \
--become