Docker provides an easy way to set up a LEMP or LAMP stack for local PHP web development using containers. In this comprehensive guide, I‘ll walk you through best practices for installing Docker and Docker Compose on Ubuntu 20.04 and using them to create a LAMP server from scratch.
Why Docker?
Before jumping into the technical details, let‘s discuss why Docker is worth learning and using for local development:
-
Portability – Docker containers can run unchanged on any OS or infrastructure. This prevents environment inconsistencies.
-
Isolation – Containers isolate dependencies and configs for each service. This improves organization for complex stacks.
-
Speed – Containers start almost instantly compared to VMs. Saving major development time.
-
Scalability -Docker sets up apps ready for horizontal scaling out to multiple hosts.
And as evidence of these benefits, Docker adoption has rapidly grown:
- Over 5 million Docker hosts active globally
- 67% of companies running Docker in production
- Container market size to reach $17 billion by 2027
With powerful capabilities like these, it‘s no wonder Docker is a must-learn skill for devops engineers and full-stack developers.
Now let‘s get hands-on with building Docker services for PHP…
Step 0 – Project Structure Best Practices
Before installing and running services, let‘s discus how to structure Docker projects:
project-name
├── docker-compose.yml
├── Dockerfile
└── app
├── src
└── config
Some guidelines to follow:
- Have explicit Dockerfiles for custom images
- Abstract app code into a subdirectory
- Separate app config from source code
- .dockerignore unneeded directories in builds
This setup improves organizing, reuse, and encapsulation of Docker apps.
Now we‘ll follow these best practices for our LAMP project…
Step 1 – Install Docker Engine
Docker is now included in Ubuntu‘s standard repositories, making installation easy.
First, update your package index:
sudo apt update
Then install Docker:
sudo apt install docker.io
After the installation completes, verify Docker is running:
sudo systemctl status docker
You should see active (running) in green text indicating the service is running properly.
Finally, add your user to the docker group to avoid using sudo for every docker command:
sudo usermod -aG docker ${USER}
Log out and back in for the changes to take effect.
You can now run docker commands without sudo. Verify this worked by running:
docker version
And you should see output without needing sudo.
Step 2 – Install Docker Compose
While Docker handles containers, Docker Compose handles multi-container applications and environments. So we‘ll need it to coordinate our LAMP containers.
Start by installing the dependencies needed for compiling python packages:
sudo apt install python3-pip libffi-dev python3-dev build-essential
Next install Docker Compose from pip, the Python package manager:
sudo pip3 install docker-compose
Check that Docker Compose is installed correctly:
docker-compose --version
Take note of the version number installed for your records.
Step 3 – Define Project Layout
Now we‘ll set up directories and files based on best practices:
lamp-project
├── docker-compose.yml
├── Dockerfile
└── app
├── src
└── config
First, make the project root and app dirs:
mkdir -p lamp-project/app/{src,config}
src will store our PHP code later.
config will store application config files.
This keeps our web app code separate from Docker config for clean organization.
Step 4 – Create Docker Compose File
Here we‘ll create a docker-compose.yml file that defines our LAMP stack containers and settings:
nano docker-compose.yml
Then define services:
version: ‘3‘
services:
apache:
image: httpd:2.4
container_name: apache
ports:
- "8080:80"
volumes:
- ./app/src:/var/www/html
depends_on:
- php
- db
php:
build: ./Dockerfile
container_name: php
volumes:
- ./app/src:/var/www/html
db:
image: mysql:8.0
container_name: db
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: examplepassword
MYSQL_DATABASE: exampledb
volumes:
- dbdata:/var/lib/mysql
volumes:
dbdata:
Let‘s analyze what this Compose file does:
- Uses official images from Docker Hub for Apache and MySQL
- The PHP service builds from a custom Dockerfile
- Containers are named for easy identification
- Ports are exposed for web traffic (Apache) and database traffic (MySQL)
- Volumes mount to sync our host code into the containers
- MySQL credentials and database name are defined
This gives us a good starting point to now extend and customize!
Step 5 – Create PHP Dockerfile
The docker-compose.yml file points to a local Dockerfile to build a custom PHP image.
Create this now:
nano Dockerfile
Add the following, which installs extensions we need:
FROM php:7.4-apache
RUN docker-php-ext-install pdo pdo_mysql mysqli
Let‘s explain the purpose of each line:
- Base off the official 7.4-apache image
- Enable pdo, pdo_mysql for MySQL connectivity from PHP
- Also enable the mysqli driver for redundancy
This gives our PHP container crucial database access.
Step 6 – Configure Environment Variables
Hard-coding credentials and settings directly in config files is an anti-pattern.
A better approach is using environment variables.
First, let‘s create an env file to store variables:
nano app/config/.env
Add the following contents:
MYSQL_ROOT_PASSWORD=strongpassword
MYSQL_DATABASE=mydatabase
Then reference these variables in docker-compose.yml:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
Now credentials are abstracted out of the compose file, adding security.
The .env file offers easy central storage for any other variables needed.
Step 7 – Networking: Attached vs Bridged
There are two main networking models in Docker:
Attached: Containers share the host‘s network stack. Simpler setup.
Bridged: Containers sit on their own subnet(s). More isolation.
The choice depends on app requirements. Our LAMP stack works fine sharing the host‘s networking.
Verify this is enabled in Docker settings:
/etc/docker/daemon.json
With contents:
{
"default-address-pools": [
{
"base":"172.17.0.0/12",
"size": 16
}
]
}
Restart Docker after updating daemon settings.
Attached networking will now suffice for our needs.
Step 8 – Launch Containers
Our configuration is complete. Let‘s start the LAMP stack:
docker-compose up -d
This launches and links the defined containers detached.
Verify they are running:
docker-compose ps
You should see:
Name Command State Ports
-----------------------------------------------------------------------------------------
apache /usr/sbin/httpd -DFOREGROUND Up 0.0.0.0:8080->80/tcp
db docker-entrypoint.sh --def ... Up 0.0.0.0:3306->3306/tcp
php docker-php-entrypoint php-fpm Up 9000/tcp
If any container fails to start, run:
docker-compose logs
And inspect the output for errors.
Step 9 – Create PHP Script
With containers operational, let‘s create a test script.
In app/src, make a PHP file:
// test.php
<?php
$host = ‘db‘;
$user = ‘root‘;
$pass = getenv(‘MYSQL_ROOT_PASSWORD‘);
$db = getenv(‘MYSQL_DATABASE‘);
$conn = new mysqli($host, $user, $pass, $db);
if ($conn->connect_error) {
die("Connection failed");
}
echo "Connected successfully";
?>
This connects to our MySQL container using credentials defined in .env.
If it prints Connected successfully – our LAMP app works!
Step 10 – Load Balancing for Scaling
Our project now has a functioning local LAMP stack. But in production, a single PHP/Apache instance creates scaling issues under high traffic.
The Dockerized architecture sets us up nicely to run replicated containers across multiple hosts.
Then a load balancer divides requests between them:

As traffic grows, we continue adding container replicas behind the load balancer to handle more demand. No modifications required to application code!
For even bigger scale, we could combine Docker with Kubernetes clusters and orchestration. But load balancing provides an easy first step towards scalability behind a production domain.
Next Steps
There are lots of directions you could take this basic LAMP stack setup:
- Add containers like phpMyAdmin, Redis, ElasticSearch
- Try out alternate web servers like Nginx
- Create custom Dockerfiles for extended MySQL configuration
- Rebuild images using Debian, CentOS base distros
- Add code and deploy a PHP app like Laravel
- Implement CI/CD pipelines to automate testing and deploy updates
- Enhance logging, monitoring and security
I encourage you to utilize this Docker LAMP foundation for your own PHP projects. Please leave any questions below!


