Apache via Docker con Ansible
Imparerai a usare Ansible per deployare il container ufficiale Apache (httpd) su
deb1.lan, con configurazione personalizzata montata come volume — SSL, proxy,
compressione gzip e security headers inclusi. Nessuna compilazione, avvio in pochi secondi.
Prerequisiti
Prima di iniziare assicurati di avere:
- Control node: Ansible installato sulla tua macchina locale
- Host remoto
deb1.lanraggiungibile via SSH con utentesudo - Docker installato su
deb1.lan(il playbook lo installa automaticamente se assente) - Chiave SSH già copiata sull'host (
ssh-copy-id [email protected]) - Certificati SSL già presenti su
deb1.lanin/etc/ssl/certs/deb1.lan-cert.peme/etc/ssl/certs/deb1.lan-key.pem
Con Docker il container ufficiale httpd viene scaricato da Docker Hub già
pronto. L'intero deploy — dal pull al container attivo — richiede meno di un minuto (escluso
il download iniziale dell'immagine).
Verifica la connettività Ansible prima di procedere:
ansible webservers -m ping --ask-vault-pass
Struttura del progetto
Creiamo la directory di lavoro sul control node:
mkdir -p ansible-apache/{inventory,templates,group_vars/webservers}
cd ansible-apache
ansible-apache/
├── inventory/
│ └── hosts.ini
├── templates/
│ └── httpd.conf.j2 # configurazione Apache con variabili Jinja2
├── group_vars/
│ └── webservers/
│ └── vault.yml # variabili cifrate (sudo password)
├── ansible.cfg
└── apache.yml # playbook principale
Inventario e configurazione
[webservers]
deb1.lan
[webservers:vars]
ansible_user=ansible
ansible_become=true
ansible_become_method=sudo
[defaults]
inventory = inventory/hosts.ini
remote_user = ansible
host_key_checking = False
stdout_callback = ansible.builtin.default
result_format = yaml
interpreter_python = /usr/bin/python3
[privilege_escalation]
become = True
become_method = sudo
Crea il vault con la password sudo:
ansible-vault create group_vars/webservers/vault.yml
Dentro al vault scrivi:
ansible_become_password: "la_tua_password_sudo"
Template httpd.conf
Il template Jinja2 genera la configurazione Apache dall'interno del container. Le variabili tra
{{ }} vengono sostituite dalle vars del playbook a deploy time. La
configurazione è minimale: carica solo i moduli necessari e attiva proxy, compressione gzip, SSL e
security headers.
# ============================================================
# httpd.conf — generato da Ansible, non modificare manualmente
# ============================================================
ServerRoot "/usr/local/apache2"
ServerName {{ apache_server_name }}
ServerAdmin {{ apache_server_admin }}
PidFile logs/httpd.pid
Listen {{ apache_http_port }}
Listen {{ apache_https_port }}
Timeout 60
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5
# ── Utente/Gruppo ────────────────────────────────────────────
User daemon
Group daemon
# ── Moduli ───────────────────────────────────────────────────
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule mime_module modules/mod_mime.so
LoadModule dir_module modules/mod_dir.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule alias_module modules/mod_alias.so
# Proxy
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule slotmem_shm_module modules/mod_slotmem_shm.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
# Compressione
LoadModule deflate_module modules/mod_deflate.so
LoadModule filter_module modules/mod_filter.so
LoadModule setenvif_module modules/mod_setenvif.so
# SSL
LoadModule ssl_module modules/mod_ssl.so
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
# Riscrittura e headers
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule headers_module modules/mod_headers.so
# ── MIME types ───────────────────────────────────────────────
TypesConfig conf/mime.types
AddType application/x-compress .Z
AddType application/x-gzip .gz .tgz
# ── Document root ────────────────────────────────────────────
DocumentRoot "/usr/local/apache2/htdocs"
<Directory "/usr/local/apache2/htdocs">
Options FollowSymLinks
AllowOverride All
Require all granted
DirectoryIndex index.html
</Directory>
<Directory />
Require all denied
</Directory>
# ── Compressione deflate ─────────────────────────────────────
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml \
text/css text/javascript \
application/javascript application/json \
application/xml application/xhtml+xml
DeflateCompressionLevel 6
SetEnvIfNoCase Request_URI \.(?:gif|jpg|jpeg|png|zip|gz|bz2|rar)$ no-gzip
</IfModule>
# ── Proxy ────────────────────────────────────────────────────
<IfModule mod_proxy.c>
ProxyRequests Off
ProxyPreserveHost On
ProxyErrorOverride Off
ProxyTimeout 60
{% if apache_proxy_pass is defined %}
ProxyPass {{ apache_proxy_path }} {{ apache_proxy_pass }}
ProxyPassReverse {{ apache_proxy_path }} {{ apache_proxy_pass }}
{% endif %}
</IfModule>
# ── Security headers ─────────────────────────────────────────
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always unset Server
</IfModule>
ServerTokens Prod
ServerSignature Off
# ── Log ──────────────────────────────────────────────────────
LogLevel warn
ErrorLog "logs/error.log"
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
CustomLog "logs/access.log" combined
# ── SSL globale ───────────────────────────────────────────────
<IfModule mod_ssl.c>
SSLSessionCache shmcb:logs/ssl_scache(512000)
SSLSessionCacheTimeout 300
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite HIGH:!aNULL:!MD5:!3DES
SSLHonorCipherOrder On
</IfModule>
# ── VirtualHost HTTP — redirect a HTTPS ──────────────────────
<VirtualHost *:{{ apache_http_port }}>
ServerName {{ apache_server_name }}
DocumentRoot /usr/local/apache2/htdocs
ErrorLog logs/{{ apache_server_name }}-error.log
CustomLog logs/{{ apache_server_name }}-access.log combined
RewriteEngine On
RewriteRule ^ https://%{HTTP_HOST}:{{ apache_https_port }}%{REQUEST_URI} [R=301,L]
</VirtualHost>
# ── VirtualHost HTTPS ─────────────────────────────────────────
<VirtualHost *:{{ apache_https_port }}>
ServerName {{ apache_server_name }}
DocumentRoot /usr/local/apache2/htdocs
ErrorLog logs/{{ apache_server_name }}-ssl-error.log
CustomLog logs/{{ apache_server_name }}-ssl-access.log combined
SSLEngine On
SSLCertificateFile /etc/ssl/certs/{{ apache_server_name }}-cert.pem
SSLCertificateKeyFile /etc/ssl/certs/{{ apache_server_name }}-key.pem
<Directory "/usr/local/apache2/htdocs">
Options FollowSymLinks
AllowOverride All
Require all granted
</Directory>
{% if apache_proxy_pass is defined %}
ProxyPass {{ apache_proxy_path }} {{ apache_proxy_pass }}
ProxyPassReverse {{ apache_proxy_path }} {{ apache_proxy_pass }}
{% endif %}
</VirtualHost>
L'immagine ufficiale httpd usa daemon/daemon come utente interno,
non www-data. Il ServerRoot è /usr/local/apache2 — path fisso
dell'immagine Docker.
Playbook apache.yml
Il playbook si occupa di tre cose: installare Docker sull'host se assente, distribuire la configurazione generata dal template, e avviare il container con i volumi e le porte corrette.
---
- name: Deploy Apache via Docker su deb1.lan
hosts: webservers
become: true
# ── Variabili ─────────────────────────────────────────────────
vars:
apache_server_name: deb1.lan
apache_server_admin: [email protected]
apache_http_port: 8071
apache_https_port: 7443
apache_conf_dir: /sw/apache/conf
apache_htdocs_dir: /sw/apache/htdocs
apache_logs_dir: /sw/apache/logs
apache_ssl_dir: /etc/ssl/certs
apache_image: httpd:latest
apache_container_name: apache-sw
# Proxy opzionale — decommenta per attivare:
# apache_proxy_path: /api/
# apache_proxy_pass: http://127.0.0.1:8000/
# ── Tasks ─────────────────────────────────────────────────────
tasks:
# ── BLOCCO 1: Docker ──────────────────────────────────────────
- name: Installa dipendenze Docker
ansible.builtin.apt:
name:
- ca-certificates
- curl
- gnupg
state: present
update_cache: true
- name: Aggiungi la GPG key di Docker
ansible.builtin.apt_key:
url: https://download.docker.com/linux/debian/gpg
state: present
- name: Aggiungi il repository Docker
ansible.builtin.apt_repository:
repo: "deb [arch=amd64] https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable"
state: present
- name: Installa Docker Engine
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
state: present
- name: Assicura che Docker sia avviato e abilitato
ansible.builtin.systemd:
name: docker
state: started
enabled: true
# ── BLOCCO 2: directory e configurazione ──────────────────────
- name: Crea le directory su host
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0755"
loop:
- "{{ apache_conf_dir }}"
- "{{ apache_htdocs_dir }}"
- "{{ apache_logs_dir }}"
- name: Distribuisce httpd.conf dal template Jinja2
ansible.builtin.template:
src: templates/httpd.conf.j2
dest: "{{ apache_conf_dir }}/httpd.conf"
mode: "0644"
notify: Riavvia container Apache
- name: Crea la pagina index di benvenuto
ansible.builtin.copy:
content: |
<!DOCTYPE html>
<html><head><meta charset="utf-8">
<title>{{ apache_server_name }}</title></head>
<body><h1>Apache su {{ apache_server_name }}</h1>
<p>Container: {{ apache_container_name }}</p></body></html>
dest: "{{ apache_htdocs_dir }}/index.html"
mode: "0644"
# ── BLOCCO 3: container ───────────────────────────────────────
- name: Pull dell'immagine httpd ufficiale
community.docker.docker_image:
name: "{{ apache_image }}"
source: pull
- name: Avvia il container Apache
community.docker.docker_container:
name: "{{ apache_container_name }}"
image: "{{ apache_image }}"
state: started
restart_policy: unless-stopped
ports:
- "{{ apache_http_port }}:{{ apache_http_port }}"
- "{{ apache_https_port }}:{{ apache_https_port }}"
volumes:
- "{{ apache_conf_dir }}/httpd.conf:/usr/local/apache2/conf/httpd.conf:ro"
- "{{ apache_htdocs_dir }}:/usr/local/apache2/htdocs:ro"
- "{{ apache_logs_dir }}:/usr/local/apache2/logs"
- "{{ apache_ssl_dir }}:/etc/ssl/certs:ro"
# ── Handlers ──────────────────────────────────────────────────
handlers:
- name: Riavvia container Apache
community.docker.docker_container:
name: "{{ apache_container_name }}"
state: started
restart: true
Il playbook usa i moduli community.docker. Se non è installata, esegui sul
control node: ansible-galaxy collection install community.docker
Esecuzione del playbook
Installa la collection Docker se non ancora presente, poi esegui il playbook:
# Una tantum: installa la collection Docker
ansible-galaxy collection install community.docker
# Esegui il playbook
ansible-playbook apache.yml --ask-vault-pass
Output atteso:
PLAY RECAP *****************************************************
deb1.lan : ok=11 changed=4 unreachable=0 failed=0
Rieseguire il playbook è sicuro: i task già applicati risultano ok. Se
httpd.conf è cambiato, l'handler riavvia automaticamente il container per
applicare la nuova configurazione.
Verifica
Controlla che il container sia in esecuzione:
ansible deb1.lan --ask-vault-pass -m command -a "docker ps --filter name=apache-sw"
Verifica la configurazione Apache dall'interno del container:
ansible deb1.lan --ask-vault-pass -m command \
-a "docker exec apache-sw httpd -t"
Test HTTP e HTTPS dalla tua macchina locale:
curl -I http://deb1.lan:8071/
curl -Ik https://deb1.lan:7443/
Segui i log in tempo reale:
ansible deb1.lan --ask-vault-pass -m command \
-a "docker logs -f apache-sw"
Se curl -Ik https://deb1.lan:7443/ restituisce HTTP/1.1 200 OK e
docker ps mostra il container Up, Apache è operativo. La
configurazione è in /sw/apache/conf/, i log in /sw/apache/logs/ —
entrambi sull'host, fuori dal container.