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', 'john@example.com'), ('Jane Smith', 'jane@example.com'), ('Bob Johnson', 'bob@example.com')"); } // 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)