Ansible LAMP Stack Capstone
This is the flagship project of the course — the one you will feature in your portfolio and walk through in job interviews. You are going to automate the complete deployment of a LAMP stack (Linux, Apache/Nginx, MySQL/MariaDB, PHP) using everything you have learned: roles, templates, Vault, handlers, conditionals, loops, and error handling. The result is a production-grade, idempotent, version-controlled deployment pipeline that works with a single command.
Project Architecture
The LAMP stack deployment targets your three-node lab environment:
- web01 and web02: Nginx as a reverse proxy, PHP-FPM for application processing, a simple PHP application
- db01: MariaDB database server with an application database and user
- All nodes: Security baseline hardening, monitoring agent, firewall rules
Project Directory Structure
lamp-deployment/
site.yml # Master playbook
ansible.cfg
inventory/
hosts.ini
group_vars/
all/
vars.yml # Global non-sensitive variables
vault.yml # Encrypted secrets
webservers.yml # Web tier variables
databases.yml # Database tier variables
host_vars/
web01.yml # web01-specific overrides
roles/
common/ # Base OS configuration (all nodes)
nginx/ # Nginx web server (web nodes)
php/ # PHP-FPM (web nodes)
mariadb/ # MariaDB (database nodes)
firewall/ # UFW firewall rules (all nodes)
myapp/ # Application deployment (web nodes)
requirements.yml
.vault_pass # In .gitignore
README.mdStep 1: Define Variables and Secrets
In group_vars/all/vars.yml:
app_name: lampapp
app_user: www-data
app_directory: /var/www/lampapp
app_domain: lampapp.local
php_version: "8.1"
mariadb_version: "10.6"
db_name: "{{ app_name }}"
db_user: "{{ app_name }}_user"
db_password: "{{ vault_db_password }}" # Points to vault variable
db_host: "{{ groups['databases'][0] }}" # First database server's hostnameIn group_vars/all/vault.yml (encrypted with ansible-vault):
vault_db_password: "{{ encrypted_string_here }}"
vault_db_root_password: "{{ encrypted_string_here }}"Step 2: The common Role
The common role runs on every node and establishes a consistent baseline:
# roles/common/tasks/main.yml - import_tasks: packages.yml - import_tasks: users.yml - import_tasks: security.yml - import_tasks: timezone.yml
# roles/common/tasks/packages.yml
- name: Update apt cache and upgrade system packages
apt:
update_cache: true
upgrade: safe
cache_valid_time: 3600
- name: Install common utilities
apt:
name:
- curl
- vim
- htop
- git
- unzip
- python3-pip
state: presentStep 3: The mariadb Role
# roles/mariadb/tasks/main.yml
- name: Install MariaDB
apt:
name:
- mariadb-server
- python3-pymysql
state: present
- name: Start and enable MariaDB
service:
name: mariadb
state: started
enabled: true
- name: Set MariaDB root password
mysql_user:
name: root
password: "{{ vault_db_root_password }}"
login_unix_socket: /var/run/mysqld/mysqld.sock
state: present
no_log: true # Prevents password from appearing in logs
- name: Create application database
mysql_db:
name: "{{ db_name }}"
state: present
login_user: root
login_password: "{{ vault_db_root_password }}"
- name: Create application database user
mysql_user:
name: "{{ db_user }}"
password: "{{ db_password }}"
priv: "{{ db_name }}.*:ALL"
host: "{{ item }}"
state: present
login_user: root
login_password: "{{ vault_db_root_password }}"
loop:
- localhost
- "{{ hostvars[0]]['ansible_host'] }}"
- "{{ hostvars[1]]['ansible_host'] }}"
no_log: trueStep 4: The php Role
# roles/php/defaults/main.yml
php_version: "8.1"
php_packages:
- "php{{ php_version }}-fpm"
- "php{{ php_version }}-mysql"
- "php{{ php_version }}-curl"
- "php{{ php_version }}-json"
- "php{{ php_version }}-mbstring"
php_fpm_pool_name: "{{ app_name }}"
php_fpm_listen: "/run/php/php{{ php_version }}-fpm-{{ app_name }}.sock"Step 5: The myapp Role
# roles/myapp/tasks/main.yml
- name: Create application directory
file:
path: "{{ app_directory }}"
state: directory
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: '0755'
- name: Deploy application files
copy:
src: files/app/
dest: "{{ app_directory }}/"
owner: "{{ app_user }}"
mode: '0644'
notify: Restart PHP-FPM
- name: Deploy application configuration
template:
src: config.php.j2
dest: "{{ app_directory }}/config.php"
owner: "{{ app_user }}"
mode: '0640'
notify: Restart PHP-FPMStep 6: The Master Playbook (site.yml)
---
- name: Apply common configuration to all servers
hosts: all
become: true
roles:
- common
- firewall
- name: Configure database servers
hosts: databases
become: true
roles:
- mariadb
- name: Configure web servers
hosts: webservers
become: true
roles:
- php
- nginx
- myapp
- name: Verify deployment
hosts: webservers
tasks:
- name: Verify application responds with HTTP 200
uri:
url: "http://{{ ansible_host }}/health"
status_code: 200
register: health_check
- name: Confirm database connectivity
uri:
url: "http://{{ ansible_host }}/db-check"
status_code: 200
- name: Deployment summary
debug:
msg: "Deployment complete on {{ inventory_hostname }} — app is healthy"Running the Deployment
# Full deployment ansible-playbook -i inventory/ site.yml --vault-password-file .vault_pass # Dry run first (always do this in production) ansible-playbook -i inventory/ site.yml --vault-password-file .vault_pass --check --diff # Deploy only database changes ansible-playbook -i inventory/ site.yml --vault-password-file .vault_pass --tags mariadb # Deploy only app code ansible-playbook -i inventory/ site.yml --vault-password-file .vault_pass --tags myapp # Limit to one host for testing ansible-playbook -i inventory/ site.yml --vault-password-file .vault_pass --limit web01
Portfolio Documentation Requirements
Your project README should include:
- Architecture diagram showing the three-node setup
- Prerequisites list (Ansible version, Python dependencies, required collections)
- Step-by-step deployment instructions
- Variable reference table documenting every configurable parameter
- Secrets management instructions (how to create and use the vault)
- Idempotency verification instructions
- A link to a screen recording of the deployment running end to end
Try This: Add Rolling Deployment
Modify the web tier play to use serial: 1 and max_fail_percentage: 0. This implements a rolling deployment — web servers are updated one at a time, and the deployment stops immediately if any server fails. This pattern ensures the application is never fully offline during deployment, simulating a real production rolling update strategy.
Summary
The LAMP stack capstone project applies every skill from the course in a coherent, production-realistic scenario. The project structure follows professional conventions with separated roles, encrypted secrets, group variables, and a master playbook that orchestrates the full deployment. The verification play confirms the application is healthy after deployment. Tag-based partial runs and the rolling deployment pattern demonstrate operational maturity. This project is the centrepiece of your Ansible portfolio.
