Setting up a consistent development environment can be challenging, especially when working across different machines or with team members. Docker Compose solves this problem by allowing us to define and run multi-container applications with a single command. In this guide, we'll create a complete LAMP (Linux, Apache, MySQL, PHP) stack that's perfect for web development.
Why Docker Compose for LAMP Development?
Before diving into the setup, let's understand why Docker Compose is ideal for LAMP stack development:
- Consistency: Same environment across all machines
- Isolation: No conflicts with host system packages
- Easy setup: One command to start everything
- Version control: Infrastructure as code
- Team collaboration: Share exact environment configurations
Project Structure
Let's start by creating our project structure:
lamp-docker/
├── docker-compose.yml
├── apache/
│ └── Dockerfile
├── php/
│ └── Dockerfile
├── www/
│ └── index.php
├── mysql/
│ └── init.sql
└── .env
The Docker Compose Configuration
Create a docker-compose.yml
file in your project root:
version: '3.8'
services:
# Apache Web Server
apache:
build: ./apache
container_name: lamp_apache
ports:
- "8080:80"
volumes:
- ./www:/var/www/html
depends_on:
- php
- mysql
networks:
- lamp_network
# PHP-FPM Service
php:
build: ./php
container_name: lamp_php
volumes:
- ./www:/var/www/html
networks:
- lamp_network
# MySQL Database
mysql:
image: mysql:8.0
container_name: lamp_mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./mysql:/docker-entrypoint-initdb.d
networks:
- lamp_network
# phpMyAdmin
phpmyadmin:
image: phpmyadmin/phpmyadmin
container_name: lamp_phpmyadmin
environment:
PMA_HOST: mysql
PMA_PORT: 3306
PMA_USER: ${MYSQL_USER}
PMA_PASSWORD: ${MYSQL_PASSWORD}
ports:
- "8081:80"
depends_on:
- mysql
networks:
- lamp_network
volumes:
mysql_data:
networks:
lamp_network:
driver: bridge
Environment Variables
Create a .env
file to store sensitive information:
# MySQL Configuration
MYSQL_ROOT_PASSWORD=rootpassword123
MYSQL_DATABASE=lamp_db
MYSQL_USER=lamp_user
MYSQL_PASSWORD=lamp_password123
Apache Configuration
Create apache/Dockerfile
:
FROM httpd:2.4
# Enable mod_rewrite and mod_proxy
RUN sed -i 's/#LoadModule rewrite_module/LoadModule rewrite_module/' /usr/local/apache2/conf/httpd.conf
RUN sed -i 's/#LoadModule proxy_module/LoadModule proxy_module/' /usr/local/apache2/conf/httpd.conf
RUN sed -i 's/#LoadModule proxy_fcgi_module/LoadModule proxy_fcgi_module/' /usr/local/apache2/conf/httpd.conf
# Configure Apache to work with PHP-FPM
RUN echo "DirectoryIndex index.php index.html" >> /usr/local/apache2/conf/httpd.conf
RUN echo "ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://php:9000/var/www/html/\$1" >> /usr/local/apache2/conf/httpd.conf
# Enable .htaccess files
RUN sed -i '/<Directory "\/usr\/local\/apache2\/htdocs">/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /usr/local/apache2/conf/httpd.conf
PHP Configuration
Create php/Dockerfile
:
FROM php:8.2-fpm
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
zip \
unzip
# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /var/www/html
# Change ownership of our applications
RUN chown -R www-data:www-data /var/www/html
Sample Application
Create www/index.php
to test our setup:
<?php
$servername = "mysql";
$username = "lamp_user";
$password = "lamp_password123";
$dbname = "lamp_db";
echo "<h1>LAMP Stack with Docker Compose</h1>";
// Test PHP
echo "<h2>PHP Information</h2>";
echo "<p>PHP Version: " . phpversion() . "</p>";
echo "<p>Server Time: " . date('Y-m-d H:i:s') . "</p>";
// Test MySQL Connection
echo "<h2>Database Connection</h2>";
try {
$pdo = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "<p style='color: green;'>✅ Connected to MySQL successfully!</p>";
// Create a test table and insert data
$pdo->exec("CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
// Insert sample data if table is empty
$stmt = $pdo->query("SELECT COUNT(*) FROM users");
if ($stmt->fetchColumn() == 0) {
$pdo->exec("INSERT INTO users (name, email) VALUES
('John Doe', '[email protected]'),
('Jane Smith', '[email protected]'),
('Bob Johnson', '[email protected]')");
}
// Display users
echo "<h3>Sample Users:</h3>";
$stmt = $pdo->query("SELECT * FROM users");
echo "<table border='1' cellpadding='5'>";
echo "<tr><th>ID</th><th>Name</th><th>Email</th><th>Created At</th></tr>";
while ($row = $stmt->fetch()) {
echo "<tr>";
echo "<td>" . $row['id'] . "</td>";
echo "<td>" . $row['name'] . "</td>";
echo "<td>" . $row['email'] . "</td>";
echo "<td>" . $row['created_at'] . "</td>";
echo "</tr>";
}
echo "</table>";
} catch(PDOException $e) {
echo "<p style='color: red;'>❌ Connection failed: " . $e->getMessage() . "</p>";
}
// Test Apache
echo "<h2>Server Information</h2>";
echo "<p>Server Software: " . $_SERVER['SERVER_SOFTWARE'] . "</p>";
echo "<p>Document Root: " . $_SERVER['DOCUMENT_ROOT'] . "</p>";
echo "<h2>Access Points</h2>";
echo "<ul>";
echo "<li><a href='http://localhost:8080' target='_blank'>Web Application (Port 8080)</a></li>";
echo "<li><a href='http://localhost:8081' target='_blank'>phpMyAdmin (Port 8081)</a></li>";
echo "</ul>";
?>
Database Initialization
Create mysql/init.sql
for initial database setup:
-- Create database if it doesn't exist
CREATE DATABASE IF NOT EXISTS lamp_db;
USE lamp_db;
-- Create a sample table
CREATE TABLE IF NOT EXISTS posts (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT,
author VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Insert sample data
INSERT INTO posts (title, content, author) VALUES
('Welcome to LAMP Stack', 'This is your first post in the LAMP stack setup.', 'Admin'),
('Docker Compose Benefits', 'Using Docker Compose makes development environment setup incredibly easy.', 'Developer'),
('Database Connection Test', 'If you can see this, your MySQL connection is working perfectly!', 'System');
-- Create additional sample tables
CREATE TABLE IF NOT EXISTS categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO categories (name, description) VALUES
('Technology', 'Posts about technology and programming'),
('Tutorial', 'Step-by-step guides and tutorials'),
('News', 'Latest news and updates');
Getting Started
Now let's launch our LAMP stack:
- Clone or create the project structure:
mkdir lamp-docker && cd lamp-docker
# Create all the files as shown above
- Build and start the services:
docker-compose up -d --build
-
Access your applications:
- Web Application: http://localhost:8080
- phpMyAdmin: http://localhost:8081
Useful Docker Compose Commands
Here are essential commands for managing your LAMP stack:
# Start services
docker-compose up -d
# Stop services
docker-compose down
# View logs
docker-compose logs
# View logs for specific service
docker-compose logs apache
# Rebuild services
docker-compose up -d --build
# Execute commands in containers
docker-compose exec php bash
docker-compose exec mysql mysql -u root -p
# View running containers
docker-compose ps
Development Workflow
Your typical development workflow will be:
-
Start the environment:
docker-compose up -d
-
Edit files: Make changes in the
www/
directory - View changes: Refresh your browser at http://localhost:8080
- Database management: Use phpMyAdmin at http://localhost:8081
-
Stop when done:
docker-compose down
Adding SSL/HTTPS (Optional)
To add SSL support for development, modify your apache/Dockerfile
:
FROM httpd:2.4
# Enable SSL module
RUN sed -i 's/#LoadModule ssl_module/LoadModule ssl_module/' /usr/local/apache2/conf/httpd.conf
# Enable other required modules
RUN sed -i 's/#LoadModule rewrite_module/LoadModule rewrite_module/' /usr/local/apache2/conf/httpd.conf
RUN sed -i 's/#LoadModule proxy_module/LoadModule proxy_module/' /usr/local/apache2/conf/httpd.conf
RUN sed -i 's/#LoadModule proxy_fcgi_module/LoadModule proxy_fcgi_module/' /usr/local/apache2/conf/httpd.confRUN sed -i 's/#Include conf\/extra\/httpd-ssl.conf/Include conf\/extra\/httpd-ssl.conf/' /usr/local/apache2/conf/httpd.conf
# Generate self-signed certificate for development
RUN openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /usr/local/apache2/conf/server.key \
-out /usr/local/apache2/conf/server.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"
# Configure Apache for PHP-FPM
RUN echo "DirectoryIndex index.php index.html" >> /usr/local/apache2/conf/httpd.conf
RUN echo "ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://php:9000/var/www/html/\$1" >> /usr/local/apache2/conf/httpd.conf
Then add port 443 to your Apache service in docker-compose.yml
:
apache:
build: ./apache
container_name: lamp_apache
ports:
- "8080:80"
- "8443:443" # Add HTTPS port
# ... rest of configuration
Performance Optimization Tips
- Use named volumes for better performance:
volumes:
- ./www:/var/www/html:cached # On macOS for better performance
-
Add PHP OPcache configuration in
php/Dockerfile
:
RUN docker-php-ext-install opcache
COPY opcache.ini /usr/local/etc/php/conf.d/opcache.ini
-
Configure MySQL for development by adding to
docker-compose.yml
:
mysql:
# ... existing config
command: --innodb-buffer-pool-size=256M --max-connections=100
Troubleshooting Common Issues
Port conflicts: If ports 8080 or 8081 are already in use, change them in docker-compose.yml
Permission issues: On Linux, you might need to adjust file permissions:
sudo chown -R $USER:$USER www/
MySQL connection refused: Ensure the MySQL container is fully started before PHP tries to connect. Add a health check:
mysql:
# ... existing config
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
Conclusion
You now have a fully functional LAMP stack running in Docker containers! This setup provides a consistent, isolated development environment that can be easily shared with team members or replicated across different machines.
The beauty of this approach is that you can customize each component according to your project's needs, add additional services like Redis or Elasticsearch, and maintain everything in version control.
This Docker Compose setup eliminates the "it works on my machine" problem and makes onboarding new developers incredibly simple. Just clone the repository and run docker-compose up -d
!
Next Steps
Consider exploring:
- Adding Redis for caching
- Including Node.js for asset compilation
- Setting up automated testing with PHPUnit
- Implementing CI/CD pipelines
- Adding monitoring with tools like Prometheus
Happy coding! 🚀
Did this guide help you set up your LAMP stack? Share your experience in the comments below, and don't forget to follow for more Docker and web development content!
Top comments (0)