LAMP Stack with Ansible & Docker
📋 Executive Summary
Section titled “📋 Executive Summary”This project modernizes the deployment of Legacy PHP applications, evolving from manual configuration to Immutable Infrastructure. Unlike basic container deployments, this solution addresses real-world challenges: compiling custom PHP extensions (mysqli), managing credentials securely outside the codebase, and standardizing the development environment.
🎯 Engineering Objectives
Section titled “🎯 Engineering Objectives”- Immutability: On-demand custom Docker image builds to inject system dependencies during deployment.
- Security (12-Factor App): Full decoupling of credentials. Source code contains no secrets; they are injected as environment variables.
- Standardization: Implementation of a
Makefilewrapper to abstract Python/Ansible complexity and ensure zero-friction onboarding.
🏗️ Solution Architecture
Section titled “🏗️ Solution Architecture”The workflow implements a remote Build & Deploy pattern orchestrated by Ansible. The control node handles configuration and secrets, while the remote host builds and runs the containers.
graph LR
%% --- Style Definitions ---
classDef ansibleNode fill:#fff9c4,stroke:#fbc02d,stroke-width:2px,color:#000000;
classDef dockerNode fill:#e0f7fa,stroke:#0288d1,stroke-width:2px,color:#000000;
classDef secretNode fill:#ffcdd2,stroke:#c62828,stroke-width:2px,color:#000000;
subgraph Control_Node ["Control Node (Dev)"]
style Control_Node fill:#eceff1,stroke:#cfd8dc,color:#000000
Make[("Makefile Wrapper")]:::ansibleNode
Ansible[("Ansible Playbook")]:::ansibleNode
Secrets[("secrets.yml<br/>(GitIgnored)")]:::secretNode
Make --> Ansible
Secrets -.-> Ansible
end
subgraph Remote_Host ["App Server (Ubuntu 24.04)"]
style Remote_Host fill:#0288d1,stroke:#cfd8dc,stroke-width:2px,color:#000000
subgraph Docker_Runtime ["Docker Engine"]
style Docker_Runtime fill:#eceff1,stroke:#455a64,color:#000000
Build[("Docker Build<br/>(PHP + Mysqli)")]:::dockerNode
App[("Container Web<br/>Apache")]:::dockerNode
DB[("Container DB<br/>MariaDB")]:::dockerNode
Ansible -- "1. Upload Dockerfile" --> Build
Build -- "2. Image Created" --> App
Ansible -- "3. Inject ENV Vars" --> App
App -- "Internal DNS" --> DB
end
end
linkStyle default stroke:#333,stroke-width:2px,fill:none;
💻 Technical Implementation
Section titled “💻 Technical Implementation”The solution uses a professional structure separating configuration, secrets, and deployment logic.
-
Execution Wrapper (Makefile)
To avoid versioning and virtual environment issues, we encapsulate commands in a
Makefile. This ensures every developer uses the exact same tooling.Makefile setup:python3 -m venv .venv.venv/bin/pip install ansible-core docker.venv/bin/ansible-galaxy install -r requirements.ymldeploy:.venv/bin/ansible-playbook playbook/deploy-lamp.yml -
Secret Management (Override Pattern)
Ansible loads public variables (
all.yml) first, then overwrites them with a local secret file that is excluded from Git (.gitignore).playbook/deploy-lamp.yml - hosts: app_serversvars_files:- "../group_vars/all.yml" # Public Structure (Placeholders)- "../group_vars/secrets.yml" # Local Secrets (Real Passwords) -
Playbook Logic (Build & Run)
The playbook not only pulls images but uploads a
Dockerfileand compiles a custom image to supportmysqli.playbook/deploy-lamp.yml tasks:- name: Build Custom PHP Imagecommunity.docker.docker_image:name: "{{ custom_image_name }}"tag: latestbuild:path: "{{ remote_project_path }}" # Remote path to Dockerfilesource: buildforce_source: true- name: Deploy Web Containercommunity.docker.docker_container:name: "{{ project_name }}-web"image: "{{ custom_image_name }}:latest" # Use the freshly built imageenv:# Secure injection of environment variablesDB_HOST: "{{ project_name }}-db"DB_USER: "{{ mysql_user }}"DB_PASSWORD: "{{ mysql_pass }}" -
Agnostic Code (PHP)
The source code (
src/index.php) contains no credentials. It reads the environment configuration, complying with The Twelve-Factor App principles.src/index.php <?php// Secure reading from Environment Variables$servername = getenv('DB_HOST');$username = getenv('DB_USER');$password = getenv('DB_PASSWORD');$conn = new mysqli($servername, $username, $password, $dbname);if ($conn->connect_error) {die("❌ Connection failed: " . $conn->connect_error);}echo "✅ Successfully connected to MariaDB.";?> -
Standardized Execution
Thanks to the Makefile, deployment is reduced to two commands, regardless of the underlying complexity.
Ventana de terminal # Initialization (First time only)make setup# Idempotent Deploymake deploy
🔍 Value Analysis
Section titled “🔍 Value Analysis”| Feature | Traditional Bash Script | Ansible + Docker Pro |
|---|---|---|
| Credentials | Hardcoded in files (.php) | Injected in Memory (ENV) |
| Dependencies | Manual installation (apt-get) | Automatic Docker Build |
| Portability | ”Works on my machine” | Standardized via Makefile |
Next Steps (Roadmap AZ-104)
Section titled “Next Steps (Roadmap AZ-104)”This project serves as the baseline for cloud migration. We have documented this process in the Cloud Operations section:
-
IaaS Migration: Replicate this architecture using Azure Virtual Machines.
-
Azure Container Registry (ACR): Move the build process to the cloud.
-
Azure Key Vault: Centralized secret management.