Docker for Laravel: Complete Beginner to Intermediate Guide
What is Docker?
Docker is an open-source platform that allows you to develop, ship, and run applications inside lightweight, portable containers. It simplifies application deployment by packaging everything an application needs to run, including dependencies, into a single unit.
Containers vs. Virtual Machines (VMs)
Feature | Containers (Docker) | Virtual Machines (VMs) |
---|---|---|
Isolation | Process-level isolation | Full OS-level isolation |
Size | Lightweight (MBs) | Heavy (GBs, includes OS) |
Performance | Faster startup (seconds) | Slower startup (minutes) |
Resource Usage | Shares OS kernel, less RAM | Requires a full OS, more RAM |
Portability | Runs the same everywhere | Less portable, OS-dependent |
Benefits of Using Docker
- Portability: Runs consistently across environments.
- Scalability: Easily scale apps with Compose or Kubernetes.
- Isolation: Prevents conflicts between apps.
- Efficiency: Uses fewer system resources than VMs.
Install Docker on Debian
Step 1: Install Required Packages
Update your package index and install the packages needed to securely add Docker's repository.
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release
Step 2: Add Docker’s Official GPG Key
Create a directory for Docker’s GPG key and add it to verify package authenticity.
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
Write this if you have already created the key
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker-latest.gpg
Step 3: Set Up the Docker Repository
Add Docker’s official repository to your system’s sources list so you can install Docker from it.
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/debian \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Step 4: Update Package Index Again
Refresh the package index to include Docker packages from the newly added repository.
sudo apt update
Step 5: Install Docker Engine
Install Docker Engine, command-line tools, and essential plugins for building and managing containers.
Docker Engine is the core component of the Docker platform. It enables developers to build and run containers—lightweight, portable packages that bundle an application together with everything it needs to run, including code, runtime, libraries, and system tools. This containerization ensures that applications run consistently across different environments, simplifying development, testing, and deployment.
Docker Compose is a complementary tool used to define and manage multi-container Docker applications. Instead of starting containers individually, you can describe all your services (such as a web server, database, or cache) in a single docker-compose.yml
file and launch them simultaneously with a single command. Docker Compose streamlines the orchestration of complex applications, making it especially useful for local development and testing.
To install Docker Engine along with the CLI and Docker Compose plugin, run the following command:
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Step 6: Verify Installation
Check that Docker is installed correctly by verifying its version and running a test container. If the test container runs successfully, Docker is installed and ready to use.
sudo docker --version
sudo docker run hello-world
Dockerize a Laravel APP
laravel-cms/
├── app/ # Laravel project (will be created by Docker)
├── docker/
│ ├── nginx/
│ │ └── default.conf # NGINX config (you’ll create this)
│ └── php/
│ └── Dockerfile # PHP + Composer environment
├── docker-compose.yml # Orchestration file
docker-compose.yml
Create this file in your laravel-cms/
root:
services:
app:
build:
context: ./docker/php
container_name: laravel-app
volumes:
- ./app:/var/www/html
depends_on:
- db
web:
image: nginx:stable-alpine
container_name: nginx
ports:
- "80:80"
volumes:
- ./app:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
db:
image: mariadb:10.6
container_name: mariadb
restart: always
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: laravel
MYSQL_USER: laravel
MYSQL_PASSWORD: secret
volumes:
- db_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin/phpmyadmin
container_name: phpmyadmin
ports:
- "8080:80"
environment:
PMA_HOST: db
MYSQL_ROOT_PASSWORD: root
depends_on:
- db
volumes:
db_data:
Dockerfiles: Writing and Optimising Docker Images
What is a Dockerfile?
A Dockerfile is a script that defines how a Docker image is built.
Key Dockerfile Directives
Directive | Description |
FROM | Base image (e.g., php:8.3-fpm) |
COPY | Copies files into image |
WORKDIR | Sets the working directory |
RUN | Executes shell commands |
CMD | Default container command |
ENTRYPOINT | Fixed command always run |
EXPOSE | Port the container listens on |
laravel-cms/docker/php/Dockerfile
This builds the Laravel-ready PHP environment:
FROM php:8.3-fpm
# Install dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libpng-dev \
libjpeg62-turbo-dev \
libfreetype6-dev \
libonig-dev \
libxml2-dev \
zip unzip git curl
# Install PHP extensions
RUN docker-php-ext-install pdo pdo_mysql mbstring exif pcntl bcmath gd
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
docker/nginx/default.conf
This is the NGINX config for Laravel:
server {
listen 80;
index index.php index.html;
server_name localhost;
root /var/www/html/public;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~ /\.ht {
deny all;
}
}
Build and Start the Project
In your laravel-cms/
folder, run:
docker compose up -d --build
Then, enter the app container and create Laravel:
docker exec -it laravel-app bash
composer create-project laravel/laravel .
exit
Now visit:
http://localhost
→ Laravel apphttp://localhost:8080
→ phpMyAdmin
Host: db
, User: root
, Password: root
Note: Only use docker run
when testing a single image quickly (e.g., trying out nginx:alpine
alone). Other than that, inside the Laravel directory, you can write:
docker compose up -d # Start all services
docker compose stop # Stop all services
docker compose start # Start stopped services
docker compose ps # See status of services
Fixing Laravel Permission Issues in Docker
Ensure Proper Ownership and Permissions
chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache
Connect to The Database
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=secret
Proper Permissions for the Laravel Docker app Container
chmod -R 777 /var/www/html
Note: Strongly not recommended for production
Basic Docker Commands
Running and Stopping Containers
Run a container from a specific image (e.g., nginx
, mysql
, or your custom image). This pulls the image (if not already present) and starts a new container from it.
Stop a running container by its name or container ID, This gracefully shuts down the container.
Start a previously stopped container, This resumes the container without creating a new one.
docker run <image_name> docker stop <container_name>
docker start <container_name>
Checking Running Containers
docker ps # List running containers
docker ps -a # List all containers (including stopped)
Executing Commands Inside a Container
docker exec -it <container_id> bash
Viewing Container Logs
docker logs <container_id>
Deleting Containers
docker rm <container_id>
docker rm -f <container_id> # Force remove running container
Managing Images
docker pull nginx
docker build -t my-laravel-app.
Purpose Comparison
Dockerfile | docker-compose.yml | |
What it does | Builds app image | Orchestrates services (app, db, etc.) |
Optimization | Build time, caching | Service dependencies, networks |
Docker Networking
You're Using Docker Networking (Even Without Declaring It). When you run: docker compose up -d
Docker Compose automatically creates a default network for your project, usually named like: laravel-cms_default
Create a Custom Bridge Network
docker network create my-net
docker run --network my-net --name web nginx
docker run --network my-net --name app php
Built-in Network Drivers
Driver | Use Case |
bridge | Default for single-host containers |
host | Shares host network stack |
none | Fully isolated |
overlay | Multi-host networking in Swarm |
This default Compose network isolates your containers from other running containers on your system. This is good for security and predictability. To inspect your Compose network:
docker network ls # Look for something like laravel-cms_default
docker network inspect laravel-cms_default
Create and Use Custom Network
You can declare the network yourself like this (optional):
services:
app:
networks:
- laravel
web:
networks:
- laravel
db:
networks:
- laravel
phpmyadmin:
networks:
- laravel
networks:
laravel:
driver: bridge
Docker Volumes: Persistent Data
Docker volumes are used to persist data. Unlike regular containers, volumes:
- Survive container restarts/removal
- Are managed by Docker
- Are ideal for databases (e.g., MySQL, MariaDB)
Types of Volumes
Type | Description |
Named | Docker-managed (good for DBs) |
Bind Mount | Maps a host path (ideal for source code) |
Commands
docker volume create laravel-db
docker volume ls
docker volume rm laravel-db
Test Persistence
Try stopping and removing the container:
docker compose down
Then bring it back up:
docker compose up -d
Mounting Volumes
Part | Meaning |
---|---|
docker run |
Runs a new Docker container. |
-v laravel-db:/var/lib/mysql |
Mounts a named volume called laravel-db to the path /var/lib/mysql inside the container. |
mariadb |
Uses the official MariaDB image from Docker Hub. |
docker run -v laravel-db:/var/lib/mysql mariadb
# Bind mount (current directory) docker run -v $(pwd):/var/www my-laravel-app
Why Use /var/lib/mysql
That’s where MariaDB stores DB files. Named volume ensures data is kept even if the container is deleted.
In Practical
services:
app:
build:
context: .
volumes:
- .:/var/www
- laravel-storage:/var/www/storage
mysql:
image: mariadb:latest
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel
volumes:
- db-data:/var/lib/mysql
volumes:
db-data:
laravel-storage:
Docker Compose
What is Docker Compose?
Docker Compose allows you to define and manage multi-container applications using a docker-compose.yml
file. It simplifies launching, scaling, and connecting services (like Laravel, MySQL, NGINX) with one command.
Installing Docker Compose
Docker Desktop (Windows/macOS): Comes pre-installed.
Linux: It will be auto-installed if you are using Docker version 20.10+
Key Directives
Directive | Purpose |
---|---|
services | Define containers |
build/image | Build from Dockerfile or pull from registry |
volumes | Mount persistent or bind data |
depends_on | Control service start order |
networks | Internal communication between services |
Docker with PHP/Laravel
Config Files
Dockerfile
– Defines the PHP application environment.docker-compose.yml
– Orchestrates the services.nginx/default.conf
– Routes HTTP traffic to PHP.
Laravel in Docker
- Use a Dockerfile and
docker-compose.yml
. - Services: PHP-FPM, MySQL, NGINX.
- Configured with
nginx/default.conf
.
Running Artisan Commands
docker compose exec app php artisan migrate
docker compose exec app php artisan config:cache
docker compose exec app php artisan queue:work
Caching Composer Dependencies
Docker caches layers. If composer.json and composer.lock don’t change, Docker will reuse this layer without re-running composer install.
In Dockerfile:
COPY composer.json composer.lock ./
RUN composer install --no-dev --prefer-dist --optimize-autoloader
Note: Placing composer steps early helps cache this layer and speed up builds.
Docker Registries
Docker registries are storage and distribution systems for Docker images. There are two main types:
Type | Description |
---|---|
Public (e.g., Docker Hub) | Share with the world or authenticated users |
Private (e.g., self-hosted) | Keep images internal, often for security or compliance |
Using Docker Hub (Public or Private Repos)
Login First:
docker login
Tag Your Image
First, check if the image exists in your Docker. To check the docker images docker images
Assume your image is named laravel-cms:
docker tag laravel-cms your-dockerhub-username/my-laravel-app:latest
docker tag laravel-cms-app skarnov/laravel-cms-app:latest
Push the Image
docker push skarnov/laravel-cms-app:latest
Pull the Image
To pull this image on any system:
docker pull skarnov/laravel-cms-app:latest
Docker Swarm
What is Docker Swarm?
Docker Swarm is a container orchestration tool built into Docker that lets you manage a cluster of machines (nodes) running containers. It supports scaling, load balancing, and rolling updates, using a deployment file similar to docker-compose.yml.
Feature | Docker Swarm | Kubernetes |
Complexity | Simple | Complex |
Setup | Quick | Longer |
Learning Curve | Lower | Steeper |
Integration | Native with Docker | Broad Ecosystem |
Docker Swarm lets you manage a cluster of Docker engines as a single virtual Docker host. It adds features like:
- Service replication
- Load balancing
- High availability
Initialize the Swarm
Run this on your main (manager) node: docker swarm init
Here is the modified version of your Compose file that is compatible with Docker Swarm:
docker-compose.yml
services:
app:
build:
context: ./docker/php
volumes:
- ./app:/var/www/html
deploy:
replicas: 1
restart_policy:
condition: on-failure
depends_on:
- db # This is ignored in Swarm, kept here just for clarity
web:
image: nginx:stable-alpine
ports:
- "80:80"
volumes:
- ./app:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
deploy:
replicas: 1
restart_policy:
condition: on-failure
db:
image: mariadb:10.6
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: laravel
MYSQL_USER: laravel
MYSQL_PASSWORD: secret
volumes:
- db_data:/var/lib/mysql
deploy:
replicas: 1
restart_policy:
condition: on-failure
phpmyadmin:
image: phpmyadmin/phpmyadmin
ports:
- "8080:80"
environment:
PMA_HOST: db
MYSQL_ROOT_PASSWORD: root
deploy:
replicas: 1
restart_policy:
condition: on-failure
volumes:
db_data:
Let's initialise Docker Swarm in our existing laravel-cms
docker build -t skarnov/laravel-cms-app:latest ./docker/php
Push it to Docker Hub
docker push skarnov/laravel-cms-app:latest
For Swarm production-ready orchestration Update docker-compose.yml: Replace this section:
app:
build:
context: ./docker/php
With:
app:
image: skarnov/laravel-cms-app:latest
Redeploy the stack:
docker stack deploy -c docker-compose.yml laravel_stack
Load Balancing in Docker Swarm
When you deploy a service with multiple replicas, Docker Swarm automatically:
- Distributes traffic across all replicas
- Uses the internal DNS and overlay network
- Uses Round Robin to balance requests
To enable load balancing in Docker Swarm using your Laravel CMS setup, you need to: Remove unsupported fields like build and container_name (not supported in Swarm mode). Push the custom image (skarnov/laravel-cms-app:latest) to Docker Hub so Swarm can pull it. Scale the app service in Swarm (e.g., to 3 replicas). Make sure your NGINX config uses the service name (app) for upstream.
version: '3.8'
services:
app:
image: skarnov/laravel-cms-app:latest
volumes:
- ./app:/var/www/html
deploy:
replicas: 3
restart_policy:
condition: on-failure
web:
image: nginx:stable-alpine
ports:
- "80:80"
volumes:
- ./app:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
deploy:
placement:
constraints: [node.role == manager]
db:
image: mariadb:10.6
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: laravel
MYSQL_USER: laravel
MYSQL_PASSWORD: secret
volumes:
- db_data:/var/lib/mysql
deploy:
restart_policy:
condition: on-failure
phpmyadmin:
image: phpmyadmin/phpmyadmin
ports:
- "8080:80"
environment:
PMA_HOST: db
MYSQL_ROOT_PASSWORD: root
depends_on:
- db
deploy:
restart_policy:
condition: on-failure
volumes:
db_data:
NGINX Load Balancing Config
Ensure your default.conf
uses the service name app
, and NGINX will round-robin requests across the 3 replicas:
upstream laravel-upstream {
server app:9000;
}
server {
listen 80;
index index.php index.html;
root /var/www/html/public;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass laravel-upstream;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_index index.php;
}
location ~ /\.ht {
deny all;
}
}
Ensure the image is built & pushed
docker build -t skarnov/laravel-cms-app:latest ./docker/php
docker push skarnov/laravel-cms-app:latest
Deploy the stack
docker swarm init # if not already initialized
docker stack deploy -c docker-compose.yml laravel_stack
Verify Load Balancing
docker service ps laravel_stack_app
Routing Mesh in Docker Swarm
Routing Mesh is Docker Swarm’s built-in load balancer. It routes incoming traffic to available container replicas, no matter which node (server) the traffic comes to.
Key Features:
Feature | Description |
---|---|
Listens on all nodes | The mesh opens the target port (e.g., 80 ) on every Swarm node, not just the node running the service. |
Routes to healthy tasks | It keeps track of which containers (tasks) are healthy and only routes traffic to those. |
Handles failovers automatically | If a container crashes, the Swarm engine spins up a new one and routes traffic there automatically. |
Example:
You have 3 nodes: Node A, Node B, Node C Your nginx container is only running on Node A, A user hits Node C:80 (via browser). Routing Mesh forwards that request to the container on Node A, transparently.
Note: No need to expose each container manually or know where it runs — Routing Mesh handles it.
Orchestration
Automated management of containers across multiple machines. Think of it like a smart manager that knows how to:
- Deploy
- Scale
- Monitor
- Restart
- Route traffic
What It Manages
Responsibility | Description |
---|---|
Deployment | Launch containers across available nodes |
Scaling | Increase or decrease replicas of a service |
Health Checks | Detect crashed containers and restart or reschedule |
Traffic Routing | Send traffic only to healthy, running containers (via Routing Mesh) |
Tools That Do Orchestration
Tool | Notes |
---|---|
Docker Swarm | Native to Docker, simple and easy to use |
Kubernetes (K8s) | Powerful, complex, industry standard |
Nomad (by HashiCorp) | Lightweight alternative, works with other HashiCorp tools |
Rancher | UI and management layer on top of Swarm or K8s |