D6: Full Stack Deployment ๐
ฮคฮน ฮธฮฑ ฯฯฮนฮฌฮพฮฟฯ ฮผฮต
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Production Full Stack โ
โ โ
โ Internet :80/:443 โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโ โ
โ โ Nginx โ โ Reverse proxy + SSL โ
โ โโโโโโฌโโโโโ โ
โ โ โ
โ โโโโโโดโโโโโโโโโโโโโโโโโโ โ
โ โผ โผ โ
โ โโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ MyApp โ โ Adminer โ โ DB management โ
โ โ (node) โ โ (web UI) โ โ
โ โโโโโโฌโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโดโโโโโโโโโโโโโโโ โ
โ โผ โผ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ Postgres โ โ Redis โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ
โ Monitoring: โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ cAdvisor โ โ Portainerโ โ Container management โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
ฮฮฟฮผฮฎ Project
# ฮฮทฮผฮนฮฟฯ
ฯฮณฮฏฮฑ ฮดฮฟฮผฮฎฯ
mkdir -p ~/ansible/roles/fullstack/{tasks,templates,defaults,handlers,files}
tree ~/ansible/roles/fullstack/
# fullstack/
# โโโ defaults/main.yml
# โโโ handlers/main.yml
# โโโ tasks/
# โ โโโ main.yml
# โ โโโ setup.yml
# โ โโโ database.yml
# โ โโโ cache.yml
# โ โโโ app.yml
# โ โโโ proxy.yml
# โ โโโ monitoring.yml
# โ โโโ verify.yml
# โโโ templates/
# โโโ docker-compose.yml.j2
# โโโ nginx.conf.j2
# โโโ app.env.j2
# โโโ postgres-init.sql.j2
defaults/main.yml
cat > ~/ansible/roles/fullstack/defaults/main.yml << 'EOF'
---
# ============================================================
# Full Stack Defaults
# ============================================================
# โโ Stack Identity โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
stack_name: mystack
stack_version: "1.0.0"
app_env: production
stack_domain: "{{ ansible_facts['default_ipv4']['address'] }}"
# โโ Paths โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
stack_dir: "/opt/{{ stack_name }}"
stack_cfg_dir: "/opt/{{ stack_name }}/config"
stack_log_dir: "/opt/{{ stack_name }}/logs"
stack_bak_dir: "/opt/{{ stack_name }}/backups"
# โโ Application โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
app_name: myapp
app_version: "{{ stack_version }}"
app_image: "nginx:1.25.3-alpine" # โ placeholder
app_port: 3000
# โโ Nginx โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
nginx_image: nginx:1.25.3-alpine
nginx_http_port: 80
nginx_https_port: 443
# โโ PostgreSQL โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
postgres_image: postgres:15.4-alpine
postgres_db: "{{ stack_name }}_db"
postgres_user: "{{ stack_name }}_user"
postgres_port: 5432
postgres_version: "15"
# โโ Redis โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
redis_image: redis:7.2-alpine
redis_port: 6379
redis_maxmem: "256mb"
# โโ Adminer (DB UI) โโโโโโโโโโโโโโโโโโโโโโโโโโโ
adminer_enabled: true
adminer_image: adminer:4-standalone
adminer_port: 8080
# โโ Monitoring โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
monitoring_enabled: true
portainer_enabled: true
portainer_port: 9000
cadvisor_port: 8081
# โโ Backup โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
backup_enabled: true
backup_schedule: "0 3 * * *" # ฮบฮฌฮธฮต ฮผฮญฯฮฑ 03:00
backup_retention: 7 # ฮบฯฮฌฯฮฑ 7 ฮผฮญฯฮตฯ
# โโ Resources โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
nginx_memory: "128m"
nginx_cpus: "0.25"
app_memory: "512m"
app_cpus: "1.0"
postgres_memory: "512m"
postgres_cpus: "0.5"
redis_memory: "256m"
redis_cpus: "0.25"
adminer_memory: "64m"
adminer_cpus: "0.1"
EOF
templates/docker-compose.yml.j2
cat > ~/ansible/roles/fullstack/templates/docker-compose.yml.j2 << 'EOF'
{# Full Stack Docker Compose #}
{# Managed by Ansible โ DO NOT EDIT! #}
{# Stack: {{ stack_name }} v{{ stack_version }} #}
{# Generated: {{ ansible_facts['date_time']['iso8601'] }} #}
version: "3.8"
{# โโ Resource calculations โโโโโโโโโโโโโโโโโโ #}
{% set total_ram = ansible_facts['memtotal_mb'] | int %}
{% set total_cpu = ansible_facts['processor_vcpus'] | int %}
services:
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# NGINX โ Reverse Proxy
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
nginx:
image: {{ nginx_image }}
container_name: {{ stack_name }}_nginx
restart: unless-stopped
ports:
- "{{ nginx_http_port }}:80"
- "{{ nginx_https_port }}:443"
volumes:
- {{ stack_cfg_dir }}/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- {{ stack_log_dir }}/nginx:/var/log/nginx
- nginx_cache:/var/cache/nginx
- /etc/localtime:/etc/localtime:ro
networks:
- frontend
depends_on:
app:
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/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
labels:
stack: {{ stack_name }}
component: nginx
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# APPLICATION
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
app:
image: {{ app_image }}
container_name: {{ stack_name }}_app
restart: unless-stopped
env_file:
- {{ stack_cfg_dir }}/app.env
volumes:
- app_data:/app/data
- app_uploads:/app/uploads
- {{ stack_log_dir }}/app:/app/logs
- /etc/localtime:/etc/localtime:ro
networks:
- frontend
- backend
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
deploy:
resources:
limits:
memory: {{ app_memory }}
cpus: "{{ app_cpus }}"
logging:
driver: json-file
options: {max-size: "10m", max-file: "3"}
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost:{{ app_port }}/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
labels:
stack: {{ stack_name }}
component: app
version: "{{ stack_version }}"
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# POSTGRESQL
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
postgres:
image: {{ postgres_image }}
container_name: {{ stack_name }}_postgres
restart: unless-stopped
environment:
POSTGRES_DB: {{ postgres_db }}
POSTGRES_USER: {{ postgres_user }}
POSTGRES_PASSWORD: {{ vault_db_password }}
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- postgres_data:/var/lib/postgresql/data
- {{ stack_cfg_dir }}/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
- {{ stack_bak_dir }}:/backups
- /etc/localtime:/etc/localtime:ro
networks:
- backend
deploy:
resources:
limits:
memory: {{ postgres_memory }}
cpus: "{{ postgres_cpus }}"
logging:
driver: json-file
options: {max-size: "10m", max-file: "3"}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U {{ postgres_user }} -d {{ postgres_db }}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
labels:
stack: {{ stack_name }}
component: postgres
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# REDIS
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
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 }}
--loglevel {{ 'debug' if app_env != 'production' else 'notice' }}
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
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# ADMINER (conditional)
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
{% if adminer_enabled %}
adminer:
image: {{ adminer_image }}
container_name: {{ stack_name }}_adminer
restart: unless-stopped
environment:
ADMINER_DEFAULT_SERVER: postgres
ADMINER_DESIGN: dracula
networks:
- frontend
- backend
depends_on:
postgres:
condition: service_healthy
deploy:
resources:
limits:
memory: {{ adminer_memory }}
cpus: "{{ adminer_cpus }}"
logging:
driver: json-file
options: {max-size: "5m", max-file: "2"}
labels:
stack: {{ stack_name }}
component: adminer
{% endif %}
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# MONITORING (conditional)
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
{% if monitoring_enabled %}
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: {{ stack_name }}_cadvisor
restart: unless-stopped
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
networks:
- monitoring
devices:
- /dev/kmsg
privileged: true
deploy:
resources:
limits:
memory: "128m"
cpus: "0.25"
logging:
driver: json-file
options: {max-size: "5m", max-file: "2"}
labels:
stack: {{ stack_name }}
component: cadvisor
{% endif %}
{% if portainer_enabled %}
portainer:
image: portainer/portainer-ce:latest
container_name: {{ stack_name }}_portainer
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- portainer_data:/data
networks:
- monitoring
deploy:
resources:
limits:
memory: "128m"
cpus: "0.25"
logging:
driver: json-file
options: {max-size: "5m", max-file: "2"}
labels:
stack: {{ stack_name }}
component: portainer
{% endif %}
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# VOLUMES
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
volumes:
postgres_data:
name: {{ stack_name }}_postgres_data
redis_data:
name: {{ stack_name }}_redis_data
app_data:
name: {{ stack_name }}_app_data
app_uploads:
name: {{ stack_name }}_app_uploads
nginx_cache:
name: {{ stack_name }}_nginx_cache
{% if portainer_enabled %}
portainer_data:
name: {{ stack_name }}_portainer_data
{% endif %}
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# NETWORKS
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
networks:
frontend:
name: {{ stack_name }}_frontend
driver: bridge
backend:
name: {{ stack_name }}_backend
driver: bridge
internal: true
monitoring:
name: {{ stack_name }}_monitoring
driver: bridge
EOF
templates/nginx.conf.j2
cat > ~/ansible/roles/fullstack/templates/nginx.conf.j2 << 'EOF'
{# Nginx Full Stack Configuration #}
{# Managed by Ansible โ DO NOT EDIT! #}
user nginx;
worker_processes {{ ansible_facts['processor_vcpus'] | default(1) }};
pid /var/run/nginx.pid;
events {
worker_connections 2048;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
keepalive_timeout 65;
client_max_body_size 64M;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# โโ Logging โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
log_format main
'$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
# โโ Rate limiting โโโโโโโโโโโโโโโโโโโโโโโโโ
limit_req_zone $binary_remote_addr zone=global:20m rate=30r/s;
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn:10m;
# โโ 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;
# โโ Upstream โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
upstream app_backend {
least_conn;
server app:{{ app_port }};
keepalive 32;
}
{% if adminer_enabled %}
upstream adminer_backend {
server adminer:8080;
}
{% endif %}
# โโ Main Server โโโโโโโโโโโโโโโโโโโโโโโโโโโ
server {
listen 80;
server_name {{ stack_domain }};
# โโ Health check โโโโโโโโโโโโโโโโโโโโโโ
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
# โโ Metrics endpoint โโโโโโโโโโโโโโโโโโ
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
# โโ API โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_conn conn 20;
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# โโ Static โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
location /static/ {
alias /var/www/static/;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# โโ Adminer โโโโโโโโโโโโโโโโโโโโโโโโโโโ
{% if adminer_enabled %}
location /adminer/ {
allow 127.0.0.1;
{% for ip in admin_allowed_ips | default(['127.0.0.1']) %}
allow {{ ip }};
{% endfor %}
deny all;
proxy_pass http://adminer_backend/;
proxy_set_header Host $host;
}
{% endif %}
# โโ Application โโโโโโโโโโโโโโโโโโโโโโโ
location / {
limit_req zone=global burst=50 nodelay;
limit_conn conn 50;
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# โโ 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;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
}
{% if monitoring_enabled %}
# โโ Monitoring Server โโโโโโโโโโโโโโโโโโโโโ
server {
listen 8081;
server_name {{ stack_domain }};
location /cadvisor/ {
proxy_pass http://cadvisor:{{ cadvisor_port }}/;
}
location /portainer/ {
proxy_pass http://portainer:{{ portainer_port }}/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
{% endif %}
}
EOF
templates/app.env.j2
cat > ~/ansible/roles/fullstack/templates/app.env.j2 << 'EOF'
{# Application Environment #}
{# Managed by Ansible Vault โ DO NOT EDIT! #}
# โโ App โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
NODE_ENV={{ app_env }}
APP_VERSION={{ stack_version }}
PORT={{ app_port }}
LOG_LEVEL={{ 'debug' if app_env != 'production' else 'info' }}
# โโ Database โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
DB_HOST=postgres
DB_PORT={{ postgres_port }}
DB_NAME={{ postgres_db }}
DB_USER={{ postgres_user }}
DB_PASSWORD={{ vault_db_password }}
DATABASE_URL=postgresql://{{ postgres_user }}:{{ vault_db_password }}@postgres:{{ postgres_port }}/{{ postgres_db }}
DB_POOL_MIN=2
DB_POOL_MAX=10
# โโ Redis โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
REDIS_HOST=redis
REDIS_PORT={{ redis_port }}
REDIS_PASSWORD={{ vault_redis_password }}
REDIS_URL=redis://:{{ vault_redis_password }}@redis:{{ redis_port }}/0
REDIS_TTL=3600
# โโ Security โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
SECRET_KEY={{ vault_app_secret }}
JWT_SECRET={{ vault_jwt_secret | default(vault_app_secret) }}
JWT_EXPIRY=24h
# โโ Feature flags โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
CACHE_ENABLED={{ 'true' if app_env == 'production' else 'false' }}
DEBUG={{ 'false' if app_env == 'production' else 'true' }}
# โโ System โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
TZ=Europe/Athens
SERVER_HOST={{ inventory_hostname }}
SERVER_IP={{ ansible_facts['default_ipv4']['address'] }}
EOF
templates/postgres-init.sql.j2
cat > ~/ansible/roles/fullstack/templates/postgres-init.sql.j2 << 'EOF'
-- PostgreSQL Initialization Script
-- Managed by Ansible โ DO NOT EDIT!
-- Stack: {{ stack_name }}
-- โโ Extensions โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
CREATE EXTENSION IF NOT EXISTS "hstore";
-- โโ Database configuration โโโโโโโโโโโโโโโโโโโโ
ALTER DATABASE {{ postgres_db }}
SET timezone TO 'Europe/Athens';
ALTER DATABASE {{ postgres_db }}
SET log_min_duration_statement TO 1000;
-- โโ Grant permissions โโโโโโโโโโโโโโโโโโโโโโโโโ
GRANT ALL PRIVILEGES ON DATABASE {{ postgres_db }}
TO {{ postgres_user }};
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public
TO {{ postgres_user }};
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public
TO {{ postgres_user }};
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON TABLES TO {{ postgres_user }};
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON SEQUENCES TO {{ postgres_user }};
-- โโ Health check table โโโโโโโโโโโโโโโโโโโโโโโโ
CREATE TABLE IF NOT EXISTS _health_check (
id SERIAL PRIMARY KEY,
checked_at TIMESTAMP DEFAULT NOW()
);
INSERT INTO _health_check DEFAULT VALUES;
EOF
tasks/main.yml
cat > ~/ansible/roles/fullstack/tasks/main.yml << 'EOF'
---
# ============================================================
# Full Stack โ Main Entry Point
# ============================================================
- name: Setup infrastructure
ansible.builtin.import_tasks:
file: setup.yml
tags: [setup, fullstack]
- name: Deploy stack
ansible.builtin.import_tasks:
file: deploy.yml
tags: [deploy, fullstack]
- name: Post-deploy setup
ansible.builtin.import_tasks:
file: postdeploy.yml
tags: [postdeploy, fullstack]
- name: Verify deployment
ansible.builtin.import_tasks:
file: verify.yml
tags: [verify, fullstack]
EOF
tasks/setup.yml
cat > ~/ansible/roles/fullstack/tasks/setup.yml << 'EOF'
---
# ============================================================
# Setup โ Directories, Config Files
# ============================================================
# โโ Directories โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- name: Create directory structure
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: '0755'
owner: root
group: docker
loop:
- "{{ stack_dir }}"
- "{{ stack_cfg_dir }}"
- "{{ stack_cfg_dir }}/nginx"
- "{{ stack_cfg_dir }}/postgres"
- "{{ stack_log_dir }}"
- "{{ stack_log_dir }}/nginx"
- "{{ stack_log_dir }}/app"
- "{{ stack_bak_dir }}"
# โโ Render templates โโโโโโโโโโโโโโโโโโโโโโโโโโ
- name: Render docker-compose.yml
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ stack_dir }}/docker-compose.yml"
mode: '0644'
register: compose_changed
- name: Render nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: "{{ stack_cfg_dir }}/nginx/nginx.conf"
mode: '0644'
register: nginx_changed
- name: Render app environment
ansible.builtin.template:
src: app.env.j2
dest: "{{ stack_cfg_dir }}/app.env"
mode: '0600'
no_log: true
register: env_changed
- name: Render postgres init script
ansible.builtin.template:
src: postgres-init.sql.j2
dest: "{{ stack_cfg_dir }}/postgres/init.sql"
mode: '0644'
# โโ Validate compose file โโโโโโโโโโโโโโโโโโโโโ
- name: Validate docker-compose.yml
ansible.builtin.command:
cmd: docker compose -f {{ stack_dir }}/docker-compose.yml config --quiet
chdir: "{{ stack_dir }}"
changed_when: false
- name: Setup summary
ansible.builtin.debug:
msg:
- "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
- "โ
Setup complete"
- "Compose: {{ 'Changed' if compose_changed.changed else 'OK' }}"
- "Nginx : {{ 'Changed' if nginx_changed.changed else 'OK' }}"
- "Env : {{ 'Changed' if env_changed.changed else 'OK' }}"
- "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
EOF
tasks/deploy.yml
cat > ~/ansible/roles/fullstack/tasks/deploy.yml << 'EOF'
---
# ============================================================
# Deploy โ Docker Compose Stack
# ============================================================
# โโ Pull images โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- name: Pull all images
ansible.builtin.command:
cmd: docker compose -f {{ stack_dir }}/docker-compose.yml pull
chdir: "{{ stack_dir }}"
register: pull_result
changed_when: "'Pull complete' in pull_result.stdout"
# โโ Deploy stack โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- name: Deploy full stack
community.docker.docker_compose_v2:
project_src: "{{ stack_dir }}"
project_name: "{{ stack_name }}"
state: present
pull: missing
register: stack_result
no_log: true
- name: Deployment result
ansible.builtin.debug:
msg: "Stack {{ stack_name }}: {{ 'Changed โ
' if stack_result.changed else 'Up to date โ
' }}"
# โโ Wait for services โโโโโโโโโโโโโโโโโโโโโโโโโ
- name: Wait for PostgreSQL healthy
community.docker.docker_container_info:
name: "{{ stack_name }}_postgres"
register: pg_info
until: >
pg_info.container is defined and
pg_info.container.State.Health.Status == 'healthy'
retries: 18
delay: 10
- name: PostgreSQL โ
ansible.builtin.debug:
msg: "โ
PostgreSQL: {{ pg_info.container.State.Health.Status }}"
- name: Wait for Redis 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 โ
ansible.builtin.debug:
msg: "โ
Redis: {{ redis_info.container.State.Health.Status }}"
- name: Wait for App to start
community.docker.docker_container_info:
name: "{{ stack_name }}_app"
register: app_info
until: >
app_info.container is defined and
app_info.container.State.Running == true
retries: 12
delay: 5
- name: App โ
ansible.builtin.debug:
msg: "โ
App: {{ app_info.container.State.Status }}"
- name: Wait for Nginx to start
community.docker.docker_container_info:
name: "{{ stack_name }}_nginx"
register: nginx_info
until: >
nginx_info.container is defined and
nginx_info.container.State.Health.Status in ['healthy', 'starting']
retries: 6
delay: 5
- name: Nginx โ
ansible.builtin.debug:
msg: "โ
Nginx: {{ nginx_info.container.State.Status }}"
EOF
tasks/postdeploy.yml
cat > ~/ansible/roles/fullstack/tasks/postdeploy.yml << 'EOF'
---
# ============================================================
# Post-Deploy โ Backup, Monitoring, Cleanup
# ============================================================
# โโ Backup script โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- name: Deploy backup script
ansible.builtin.copy:
dest: /usr/local/bin/stack-backup.sh
mode: '0755'
owner: root
content: |
#!/bin/bash
# Stack Backup Script
# Managed by Ansible โ DO NOT EDIT!
STACK="{{ stack_name }}"
BACKUP_DIR="{{ stack_bak_dir }}"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION={{ backup_retention }}
echo "=== Backup Start: $DATE ==="
# PostgreSQL backup
echo "Backing up PostgreSQL..."
docker exec {{ stack_name }}_postgres \
pg_dump \
-U {{ postgres_user }} \
-d {{ postgres_db }} \
--format=custom \
--compress=9 \
> "${BACKUP_DIR}/postgres_${DATE}.dump"
if [ $? -eq 0 ]; then
echo "โ
PostgreSQL backup: postgres_${DATE}.dump"
else
echo "โ PostgreSQL backup FAILED!"
exit 1
fi
# Redis backup
echo "Backing up Redis..."
docker exec {{ stack_name }}_redis \
redis-cli --no-auth-warning \
-a "{{ vault_redis_password }}" \
BGSAVE
sleep 2
docker cp {{ stack_name }}_redis:/data/dump.rdb \
"${BACKUP_DIR}/redis_${DATE}.rdb"
echo "โ
Redis backup: redis_${DATE}.rdb"
# Cleanup old backups
echo "Cleaning up old backups (keep last ${RETENTION} days)..."
find "${BACKUP_DIR}" -name "*.dump" -mtime +${RETENTION} -delete
find "${BACKUP_DIR}" -name "*.rdb" -mtime +${RETENTION} -delete
echo "=== Backup Complete ==="
ls -lh "${BACKUP_DIR}/"
when: backup_enabled
- name: Schedule backup
ansible.builtin.cron:
name: "{{ stack_name }} stack backup"
minute: "{{ backup_schedule.split()[0] }}"
hour: "{{ backup_schedule.split()[1] }}"
day: "{{ backup_schedule.split()[2] }}"
month: "{{ backup_schedule.split()[3] }}"
weekday: "{{ backup_schedule.split()[4] }}"
user: root
job: "/usr/local/bin/stack-backup.sh >> {{ stack_log_dir }}/backup.log 2>&1"
state: present
when: backup_enabled
# โโ Health monitor โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- name: Deploy health monitor script
ansible.builtin.copy:
dest: /usr/local/bin/stack-health.sh
mode: '0755'
owner: root
content: |
#!/bin/bash
# Stack Health Monitor
STACK="{{ stack_name }}"
echo "=== {{ stack_name }} Health Check $(date) ==="
echo ""
echo "Containers:"
docker compose \
-f {{ stack_dir }}/docker-compose.yml \
-p {{ stack_name }} \
ps --format "table {{.Name}}\t{{.Status}}\t{{.Health}}"
echo ""
echo "Resources:"
docker stats --no-stream \
--format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}" \
$(docker compose -p {{ stack_name }} ps -q)
- name: Schedule health check
ansible.builtin.cron:
name: "{{ stack_name }} health check"
minute: "*/15"
user: root
job: "/usr/local/bin/stack-health.sh >> {{ stack_log_dir }}/health.log 2>&1"
state: present
EOF
tasks/verify.yml
cat > ~/ansible/roles/fullstack/tasks/verify.yml << 'EOF'
---
# ============================================================
# Verification โ Full Stack Checks
# ============================================================
# โโ Container status โโโโโโโโโโโโโโโโโโโโโโโโโโ
- name: Get stack status
ansible.builtin.command:
cmd: >
docker compose
-f {{ stack_dir }}/docker-compose.yml
-p {{ stack_name }}
ps --format json
register: stack_ps
changed_when: false
# โโ HTTP checks โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- name: HTTP health check
ansible.builtin.uri:
url: "http://localhost:{{ nginx_http_port }}/health"
status_code: 200
timeout: 30
register: http_check
retries: 5
delay: 5
ignore_errors: true
# โโ Database check โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- name: PostgreSQL connectivity
ansible.builtin.command:
cmd: >
docker exec {{ stack_name }}_postgres
pg_isready -U {{ postgres_user }} -d {{ postgres_db }}
register: pg_check
changed_when: false
ignore_errors: true
# โโ Redis check โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- name: Redis connectivity
ansible.builtin.command:
cmd: >
docker exec {{ stack_name }}_redis
redis-cli --no-auth-warning
-a {{ vault_redis_password }} ping
register: redis_check
changed_when: false
no_log: true
ignore_errors: true
# โโ Disk usage โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- name: Docker disk usage
ansible.builtin.command:
cmd: docker system df
register: disk_usage
changed_when: false
# โโ Stack report โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- name: Full Stack Verification Report
ansible.builtin.debug:
msg:
- "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
- "โ FULL STACK VERIFICATION REPORT โ"
- "โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ"
- "โ Stack : {{ stack_name }} v{{ stack_version }}"
- "โ Host : {{ inventory_hostname }}"
- "โ Domain : {{ stack_domain }}"
- "โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ"
- "โ SERVICES: โ"
- "โ Nginx : {{ 'โ
' if nginx_info.container.State.Running else 'โ' }}"
- "โ App : {{ 'โ
' if app_info.container.State.Running else 'โ' }}"
- "โ Postgres : {{ 'โ
' if pg_info.container.State.Health.Status == 'healthy' else 'โ' }}"
- "โ Redis : {{ 'โ
' if redis_info.container.State.Health.Status == 'healthy' else 'โ' }}"
- "โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ"
- "โ CHECKS: โ"
- "โ HTTP : {{ 'โ
OK' if not http_check.failed else 'โ Failed' }}"
- "โ Database : {{ 'โ
OK' if not pg_check.failed else 'โ Failed' }}"
- "โ Redis : {{ 'โ
OK' if not redis_check.failed else 'โ Failed' }}"
- "โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ"
- "โ URLs: โ"
- "โ App : http://{{ stack_domain }}"
- "{% if adminer_enabled %}"
- "โ Adminer : http://{{ stack_domain }}/adminer/"
- "{% endif %}"
- "{% if monitoring_enabled %}"
- "โ Monitor : http://{{ stack_domain }}:8081"
- "{% endif %}"
- "โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ"
- "โ DISK: โ"
- "{{ disk_usage.stdout_lines | join('\n') }}"
- "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
EOF
handlers/main.yml
cat > ~/ansible/roles/fullstack/handlers/main.yml << 'EOF'
---
- name: Reload nginx
ansible.builtin.command:
cmd: >
docker compose
-f {{ stack_dir }}/docker-compose.yml
-p {{ stack_name }}
exec -T nginx nginx -s reload
ignore_errors: "{{ ansible_check_mode }}"
- name: Restart app
ansible.builtin.command:
cmd: >
docker compose
-f {{ stack_dir }}/docker-compose.yml
-p {{ stack_name }}
restart app
ignore_errors: "{{ ansible_check_mode }}"
- name: Restart stack
community.docker.docker_compose_v2:
project_src: "{{ stack_dir }}"
project_name: "{{ stack_name }}"
state: present
recreate: always
no_log: true
ignore_errors: "{{ ansible_check_mode }}"
EOF
ฮ ฮปฮฎฯฮตฯ Deployment Playbook
cat > ~/ansible/playbooks/fullstack-deploy.yml << 'EOF'
---
# ============================================================
# Full Stack Deployment Playbook
# ============================================================
- name: Full Stack Deployment
hosts: "{{ target | default('all_managed') }}"
become: true
gather_facts: true
vars:
stack_name: mystack
stack_version: "{{ version | default('1.0.0') }}"
app_env: production
stack_domain: "{{ ansible_facts['default_ipv4']['address'] }}"
# โโ Images โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
nginx_image: nginx:1.25.3-alpine
app_image: "nginx:1.25.3-alpine" # placeholder
postgres_image: postgres:15.4-alpine
redis_image: redis:7.2-alpine
adminer_image: adminer:4-standalone
# โโ Features โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
adminer_enabled: true
monitoring_enabled: true
portainer_enabled: true
backup_enabled: true
# โโ Admin access โโโโโโโโโโโโโโโโโโโโโโโโโโ
admin_allowed_ips:
- 127.0.0.1
- 192.168.1.0/24
pre_tasks:
- name: Pre-deployment checks
ansible.builtin.assert:
that:
- ansible_facts.services['docker.service'].state == 'running'
- ansible_facts['memtotal_mb'] | int >= 1024
fail_msg: "โ Docker not running or insufficient RAM!"
vars:
ansible_facts:
services:
docker.service:
state: running
ignore_errors: true
tags: always
- name: Deployment info
ansible.builtin.debug:
msg:
- "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
- "โ FULL STACK DEPLOYMENT โ"
- "โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ"
- "โ Stack : {{ stack_name }} v{{ stack_version }}"
- "โ Host : {{ inventory_hostname }}"
- "โ IP : {{ ansible_facts['default_ipv4']['address'] }}"
- "โ RAM : {{ (ansible_facts['memtotal_mb'] / 1024) | round(1) }}GB"
- "โ CPUs : {{ ansible_facts['processor_vcpus'] }}"
- "โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ"
- "โ Services: โ"
- "โ โ
Nginx + App + Postgres + Redis โ"
- "{% if adminer_enabled %}โ โ
Adminer (DB UI) โ{% endif %}"
- "{% if monitoring_enabled %}โ โ
cAdvisor + Portainer โ{% endif %}"
- "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
tags: always
roles:
- role: fullstack
tags: fullstack
post_tasks:
- name: Access Information
ansible.builtin.debug:
msg:
- "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
- "โ DEPLOYMENT COMPLETE! ๐ โ"
- "โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ"
- "โ Main App : http://{{ ansible_facts['default_ipv4']['address'] }}"
- "{% if adminer_enabled %}"
- "โ Adminer : http://{{ ansible_facts['default_ipv4']['address'] }}/adminer/"
- "{% endif %}"
- "{% if monitoring_enabled %}"
- "โ Portainer: http://{{ ansible_facts['default_ipv4']['address'] }}:9000"
- "โ cAdvisor : http://{{ ansible_facts['default_ipv4']['address'] }}:8081"
- "{% endif %}"
- "โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ"
- "โ Management Commands: โ"
- "โ Status: docker compose -p {{ stack_name }} ps"
- "โ Logs : docker compose -p {{ stack_name }} logs -f"
- "โ Stop : docker compose -p {{ stack_name }} stop"
- "โ Backup: /usr/local/bin/stack-backup.sh"
- "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
tags: always
EOF
ฮฮบฯฮญฮปฮตฯฮท
# โโ Syntax check โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
ansible-playbook playbooks/fullstack-deploy.yml \
--syntax-check
# โโ Dry run โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
ansible-playbook playbooks/fullstack-deploy.yml \
--check \
--limit nextcloud
# โโ Full deployment โโโโโโโโโโโโโโโโโโโโโโโโโโโ
ansible-playbook playbooks/fullstack-deploy.yml \
--limit nextcloud \
-e "version=1.0.0" \
-v
# โโ ฮฯฮฝฮฟ setup โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
ansible-playbook playbooks/fullstack-deploy.yml \
--tags setup \
--limit nextcloud
# โโ Update version โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
ansible-playbook playbooks/fullstack-deploy.yml \
--limit nextcloud \
-e "version=2.0.0" \
--tags deploy
# โโ Verify โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
ansible-playbook playbooks/fullstack-deploy.yml \
--tags verify \
--limit nextcloud
# โโ Manual backup โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
ansible nextcloud -m ansible.builtin.command \
-a "/usr/local/bin/stack-backup.sh" \
--become