Docker for Laravel: Complete Beginner to Intermediate Guide

Master Docker for Laravel development with this complete beginner-to-intermediate guide. Learn to build, configure, and scale Laravel apps using Docker, Dockerfiles, and docker-compose.

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 app
http://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