Docker lets you run Laravel and its supporting services—such as PHP, Nginx, MySQL, Redis, and Node.js—inside isolated containers.
This gives every developer a consistent environment and reduces the common “it works on my machine” problem.
Laravel itself is straightforward to install. However, a real Laravel application rarely needs only PHP. It may depend on:
-
A specific PHP version
-
PHP extensions
-
Composer
-
MySQL or PostgreSQL
-
Redis
-
Queue workers
-
Node.js and npm
-
Nginx or Apache
Installing and maintaining everything directly on every developer’s computer can become difficult. It becomes even more complicated when different Laravel projects require different versions.
That is the problem Docker helps solve.
I have used Docker with Laravel applications that combine PHP-FPM, Nginx, MySQL, phpMyAdmin, Node.js, queues, and background services. The main benefit is not that Docker improves your Laravel code. The benefit is that your project environment becomes repeatable, visible, and easier to manage.
In this beginner guide, you will learn:
-
What Docker is
-
Why Laravel developers use it
-
The essential Docker concepts
-
How to run Laravel using Laravel Sail
-
How Docker networking and volumes work
-
Useful Docker and Sail commands
-
How to troubleshoot common problems
-
Why a development Docker setup is not automatically production-ready
What Is Docker?
Docker is a platform for building, distributing, and running applications inside containers.
A container is an isolated process that includes the runtime, system libraries, and configuration required by a service.
For example, a Dockerized Laravel project might run:
-
PHP in one container
-
MySQL in another container
-
Redis in another container
-
Nginx in another container
-
A queue worker in another container
Docker connects these containers through a private network.
Simple explanation: A Docker image is a reusable blueprint. A container is a running instance created from that blueprint. A Compose file defines how multiple containers work together.
Why Laravel Developers Use Docker
Without Docker, every developer’s computer becomes a manually configured server.
One developer may use PHP 8.3, while another uses PHP 8.4. One machine may have the Redis extension installed, while another does not.
The code may work correctly on one computer but fail on another because the environments are different.
Docker reduces this problem by keeping infrastructure requirements close to the project.
1. Consistent versions
A Docker project can define the PHP, MySQL, Node.js, Redis, and other service versions it requires.
Everyone working on the project can use the same versions.
2. Project isolation
You may have one Laravel project that needs PHP 8.3 and another that needs PHP 8.4.
Docker allows both projects to use their required versions without replacing the PHP installation on your computer.
3. Easier onboarding
A new developer can start the declared Docker environment instead of manually installing and configuring every service.
4. Replaceable services
Containers can be stopped, rebuilt, or replaced without reinstalling your entire development machine.
5. Better CI/CD consistency
The same Docker build instructions can support local development, automated testing, and deployment pipelines.
6. Visible infrastructure
Ports, services, networks, volumes, and environment variables are defined in configuration files instead of existing only on one developer’s computer.
Important: Docker can make your environment consistent, but it cannot fix bad Laravel code, unsafe migrations, missing backups, weak architecture, or incorrect configuration.
Six Docker Terms Every Laravel Developer Should Know
| Term | Meaning |
|---|---|
| Image | A reusable template used to create containers, such as a PHP or MySQL image. |
| Container | A running service created from an image. |
| Dockerfile | A file containing instructions for building a custom Docker image. |
| Compose file | A YAML file that defines multiple services, networks, ports, and volumes. |
| Volume | Storage used to preserve data or mount files into containers. |
| Network | A private connection that allows containers to communicate with one another. |
Docker Image vs Container
This difference confuses many beginners.
An image is the blueprint. A container is the running instance created from that blueprint.
You can compare it with object-oriented programming:
-
An image is similar to a class.
-
A container is similar to an object created from that class.
The image describes what should exist. The container is the running process with a name, network address, filesystem layer, and current state.
Rebuilding a container does not automatically mean your database must disappear. Important data should be stored in a persistent volume or an external database service.
Docker vs Virtual Machine
A virtual machine normally contains a complete guest operating system.
Containers share the host operating system’s kernel while isolating application processes. This generally makes containers:
-
Faster to start
-
Smaller
-
Less resource-intensive
-
Easier to replace
However, containers are not automatic security boundaries. Images, exposed ports, mounted files, secrets, permissions, and Docker daemon access must still be managed carefully.
What a Dockerized Laravel Project Looks Like
A common Laravel Docker architecture looks like this:
Browser
│
▼
Nginx container
│
▼
PHP-FPM / Laravel container
│
├── MySQL container
├── Redis container
└── Queue worker container
│
▼
Persistent volumes
Each container has a clear responsibility.
Laravel does not normally connect to MySQL using the same host address you use in your browser. Inside Docker’s private network, Laravel usually connects to the database using its service name, such as mysql.
Laravel Sail or Custom Docker Compose?
Laravel developers normally start with one of two approaches.
| Option | Best for | Trade-off |
|---|---|---|
| Laravel Sail | Beginners and local Laravel development | Easy to start, but offers less initial control. |
| Custom Docker Compose | Teams needing specific PHP, Nginx, queue, or deployment architecture | More control, but you must maintain everything yourself. |
Laravel Sail is Laravel’s official command-line interface for its default Docker development environment.
It is a good starting point because it lets you learn Docker through familiar Laravel commands.
A custom Docker setup becomes useful when you need:
-
A separate Nginx container
-
A specialized PHP image
-
Multiple queue workers
-
Custom Node.js handling
-
Different development and production images
-
More control over security and deployment
Recommendation: If you are new to Docker, begin with Laravel Sail. After becoming comfortable with it, inspect the generated
compose.yamlfile and learn how its services work.
Install Docker
Before using Laravel Sail, install Docker.
You can use:
-
Docker Desktop on Windows or macOS
-
Docker Engine with the Compose plugin on Linux
Always follow the official Docker installation documentation because supported operating systems and installation commands can change.
After installation, verify Docker:
docker --version
docker compose version
docker run --rm hello-world
If these commands work, Docker and Docker Compose are available.
The modern command is:
docker compose
The older command was:
docker-compose
The standalone docker-compose command is now considered the legacy form and may not exist on a current installation.
A note for Linux users
If Docker only works with sudo, follow Docker’s official post-installation instructions carefully.
Adding your account to the docker group gives it powerful access to the host system. Treat this as an administrative permission, not a harmless shortcut.
Run an Existing Laravel Project with Sail
The following steps assume your Laravel project already runs locally and Composer is available.
Before continuing, commit or safely back up your current changes. The Sail installer creates a compose.yaml file and modifies environment settings.
Step 1: Install Laravel Sail
Run:
composer require laravel/sail --dev
This installs Sail as a development dependency.
Step 2: Generate the Docker configuration
Run:
php artisan sail:install
The installer asks which services your application needs.
For a basic project, you may choose:
-
MySQL
-
Redis
Select only the services you actually use. Every additional service consumes memory and adds more configuration.
Step 3: Start the containers
Run:
./vendor/bin/sail up -d
The -d option starts the containers in detached mode. They continue running in the background.
You can normally access the application at:
http://localhost
Step 4: Check the container status
Run:
./vendor/bin/sail ps
This shows the containers associated with the project.
If a container immediately exits, it usually has a startup or configuration error. Instead of repeatedly running up, inspect the container logs.
Step 5: Run Laravel setup commands
Run Artisan commands through Sail:
./vendor/bin/sail artisan migrate
Install frontend dependencies:
./vendor/bin/sail npm install
Start the Vite development server:
./vendor/bin/sail npm run dev
When Sail manages your environment, its command wrappers ensure PHP, Composer, Artisan, and Node.js commands run inside the expected container.
Create a Shorter Sail Command
Typing ./vendor/bin/sail repeatedly can become inconvenient.
Laravel’s documentation recommends creating a shell alias:
alias sail='sh $([ -f sail ] && echo sail || echo vendor/bin/sail)'
After adding the alias to your shell configuration, you can use:
sail up -d
sail artisan migrate
sail npm run dev
sail test
Only add the alias after understanding what it executes.
Useful Laravel Sail Commands
| Task | Command |
|---|---|
| Start containers | ./vendor/bin/sail up -d |
| Show container status | ./vendor/bin/sail ps |
| Follow logs | ./vendor/bin/sail logs -f |
| Run Artisan | ./vendor/bin/sail artisan <command> |
| Run Composer | ./vendor/bin/sail composer <command> |
| Run npm | ./vendor/bin/sail npm <command> |
| Open the application shell | ./vendor/bin/sail shell |
| Run tests | ./vendor/bin/sail test |
| Stop containers | ./vendor/bin/sail stop |
| Stop and remove containers | ./vendor/bin/sail down |
Data-loss warning: Do not casually run
docker compose down -v. The-voption removes the project’s named volumes and may delete your local database data.
How Docker Networking Changes Laravel .env
One of the most common beginner mistakes is using 127.0.0.1 for every service.
Inside a container, 127.0.0.1 refers to that same container. It does not automatically mean your MySQL container or your host computer.
Containers on the same Docker network normally communicate using service names.
A Laravel Sail database configuration may look like this:
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=sail
DB_PASSWORD=password
REDIS_HOST=redis
REDIS_PORT=6379
From inside the Laravel container, mysql resolves to the MySQL container.
However, a desktop database application may connect using:
127.0.0.1
and the MySQL port published on your host computer.
These are two different network paths:
-
Container to container: service name and container port
-
Host to container: host address and published port
How Docker Volumes Affect Laravel Data
Containers should be replaceable.
Your database and other important data should not depend only on a container’s temporary writable filesystem.
Two volume types are commonly used in Laravel development.
Bind mounts
A bind mount connects a folder on your computer to a path inside a container.
Laravel source code is commonly bind-mounted so that file changes appear inside the container immediately.
Named volumes
A named volume is storage managed by Docker.
Named volumes are commonly used for:
-
MySQL data
-
PostgreSQL data
-
Redis data
-
Other persistent service data
Stopping or replacing a container normally does not delete its named volume.
However, commands that explicitly remove volumes can delete the stored data. This includes:
docker compose down -v
Docker pruning and manual volume deletion should be treated with the same care as deleting a local database directory.
Laravel File treated with the same care as deleting a local database directory.
Permission Problems in Docker
Laravel must be able to write to:
storage
bootstrap/cache
Docker adds another layer because the process the inside the container may use a different user ID from the user who owns files on the host computer.
Common permission symptoms include:
-
Laravel cannot append to its log file.
-
Composer creates files owned by
root. -
Vite cannot update generated files.
-
The web server cannot write sessions or cache files.
-
File uploads fail.
The proper solution normally involves:
-
Aligning the host and container users
-
Running commands through the correct container
-
Correcting ownership on specific writable directories
-
Avoiding unnecessary root execution
Do not use this as a general solution:
chmod -R 777 .
It hides the ownership problem and creates unnecessary security risks.
A later article in this series—Fix Docker Permission Issues in Laravel—will cover UID/GID mapping, root-owned files, bind mounts, and safe permission recovery in detail.
Common Laravel Docker Problems and Fixes
1. Cannot connect to the Docker daemon
Run:
docker info
If the command fails, Docker may not be running.
On Linux, check the Docker service. If you use Docker Desktop, confirm that the application has completed its startup process.
Also verify that you are using the expected Docker context.
2. Port is already allocated
This means another application is already using the host port.
For example, Apache or Nginx may already be using port 80, or a locally installed MySQL service may already be using port 3306.
Check running Compose services:
docker compose ps
On Linux, inspect port 80:
sudo ss -ltnp | grep ':80'
Stop the conflicting application or change the published host port.
Do not randomly change the internal container port. Understand which side of the port mapping belongs to the host and which belongs to the container.
3. Laravel cannot connect to MySQL
Check the services:
docker compose ps
Read the database logs:
docker compose logs mysql
Inspect Laravel’s environment:
./vendor/bin/sail artisan about
Confirm:
-
DB_HOSTmatches the database service name. -
The database credentials are correct.
-
The MySQL container is running.
-
MySQL has completed its initialization.
-
Laravel’s configuration cache is not using old values.
A Laravel container may start before MySQL is ready to accept connections. Wait until the database becomes healthy before retrying.
4. Permission denied in storage/logs
Open the application container:
./vendor/bin/sail shell
Check the current user:
whoami
Inspect the writable directories:
ls -ld storage bootstrap/cache
Correct the ownership for the intended host and container user.
Avoid using universal write permissions as the default response.
5. Code changes do not appear
Check the resolved Compose configuration:
docker compose config
Clear Laravel’s caches:
./vendor/bin/sail artisan optimize:clear
If your Docker image copies the project source during its build instead of using a bind mount, rebuild it:
docker compose build
docker compose up -d
You normally need to rebuild when:
-
The Dockerfile changes
-
Installed system packages change
-
PHP extensions change
-
Build arguments change
-
Files copied during the image build change
6. CSS or JavaScript is missing
Check whether the Node.js or Vite process is running.
Development normally uses:
./vendor/bin/sail npm run dev
Production requires compiled assets:
npm run build
Also inspect:
-
Published Vite ports
-
Browser console errors
-
Network requests
-
The Laravel
APP_URL -
The generated
public/builddirectory
For production troubleshooting, read Laravel Vite Assets Not Loading After Deployment.
7. A container keeps restarting or exits
Show all container states:
docker compose ps -a
Read the failing service’s logs:
docker compose logs --tail=200 <service-name>
Look for the first meaningful error instead of focusing only on the final exit message.
Common causes include:
-
Invalid environment variables
-
Missing configuration files
-
Failed database initialization
-
Incorrect entrypoint commands
-
Permission problems
-
Incompatible image versions
-
Port conflicts
A Reliable Docker Debugging Order
When a Laravel Docker environment stops working, use this sequence:
-
Confirm Docker is running with
docker info. -
Validate the Compose configuration with
docker compose config. -
Check every container using
docker compose ps -a. -
Read the logs of the failing service.
-
Check port conflicts and container health.
-
Verify Laravel’s
.envservice names and credentials. -
Enter the application container and test from inside it.
-
Clear only the relevant Laravel caches.
-
Rebuild only when the Dockerfile or image configuration has changed.
This process separates:
-
Docker Engine problems
-
Compose configuration errors
-
Container startup failures
-
Networking mistakes
-
Laravel application errors
It also prevents random rebuilds and unsafe permission changes.
Development Docker Is Not Automatically Production-Ready
Laravel Sail is designed to provide a convenient local development environment.
Do not copy a local Sail configuration to a public server and assume it is secure or optimized.
A production Docker setup normally requires decisions about:
-
Small and reproducible images
-
Pinned image versions
-
Multi-stage builds
-
Non-root runtime users
-
Minimum Linux capabilities
-
Secure secret injection
-
PHP OPcache
-
Production
php.inisettings -
Separate queue worker services
-
Laravel scheduler execution
-
Health checks
-
Restart policies
-
Centralized logs
-
Monitoring and alerts
-
Database backups
-
Tested restoration procedures
-
TLS and firewall configuration
-
Limited exposed ports
Docker can improve deployment consistency, but production reliability still depends on proper system design and operational discipline.
Docker Best Practices for Laravel Beginners
-
Commit Docker configuration files to Git.
-
Never commit your real production
.envfile. -
Do not store passwords directly in a Dockerfile.
-
Pin important image versions instead of blindly using
latest. -
Create a
.dockerignorefile. -
Exclude Git history, secrets, caches, and unnecessary files from the build context.
-
Use service names for container-to-container connections.
-
Store persistent data in named volumes or managed external services.
-
Run Composer, Artisan, and npm through the intended container.
-
Read logs before rebuilding or changing permissions.
-
Know what a cleanup command removes before running it.
-
Keep development and production configurations separate.
-
Document the project’s start, stop, test, migrate, backup, and recovery commands.
Frequently Asked Questions
Do I need Docker to develop Laravel applications?
No. You can run Laravel by installing PHP, Composer, a database, Node.js, and other dependencies directly on your computer.
Docker becomes useful when you need repeatable environments, project isolation, multiple services, or easier team onboarding.
Is Laravel Sail the same as Docker?
No.
Docker is the container platform. Laravel Sail is Laravel’s command-line wrapper and default Docker development environment.
Sail uses Docker and Docker Compose behind the scenes.
Should a Laravel beginner learn Sail or Docker Compose first?
Start with Sail if your immediate goal is running a Laravel project.
After you become comfortable with Sail, inspect its compose.yaml file and learn about services, images, networks, ports, and volumes.
These concepts transfer directly to custom Docker Compose projects.
Why is DB_HOST set to mysql instead of 127.0.0.1?
Inside the Laravel container, 127.0.0.1 refers to the Laravel container itself.
The service name mysql resolves to the MySQL container through Docker’s private network.
Will rebuilding a container delete my database?
Not if your database data is stored in a named volume and you only replace the container.
However, commands that explicitly remove volumes can delete your local database.
Be especially careful with:
docker compose down -v
Can I use Docker for Laravel production deployment?
Yes, but a development configuration is not automatically production-ready.
Production requires secure images, secrets management, non-root users, backups, health checks, monitoring, optimized PHP settings, and a controlled deployment process.
Why do Docker-created files become owned by root?
The container process may run with a different user or user ID from your host account.
Align the host and container users where possible. Run project commands using the intended container user, and avoid solving ownership problems with 777 permissions.
Does Docker make Laravel slower?
Docker introduces some overhead, and bind-mount performance can vary by operating system.
Performance can also decrease when you run:
-
Too many containers
-
Xdebug continuously
-
Large bind mounts
-
Multiple databases
-
Unused background services
-
Heavy Node.js processes
On Linux, Docker’s overhead is often modest. Run only the services your project needs.
Final Thoughts
Docker becomes easier when you stop treating it as one large and complicated tool.
It is mainly a collection of connected concepts:
-
Images create containers.
-
A Compose file coordinates services.
-
Networks connect containers.
-
Volumes preserve important data.
-
Logs explain why services fail.
For Laravel developers, the practical goal is consistency.
PHP, MySQL, Redis, queues, Node.js, and the web server should behave predictably without requiring every developer to build a separate local server manually.
Start with Laravel Sail. Learn how to inspect container status and logs. Understand the difference between localhost and a Compose service name. Be careful with volumes and file permissions.
Once these foundations are clear, a custom Laravel Docker architecture stops looking mysterious. It becomes a set of engineering decisions that you can understand and explain.
What’s Next?
The next article in this Laravel Docker series will be:
Docker Compose for Laravel Projects
It will explain how to create a custom Laravel environment using PHP-FPM, Nginx, MySQL, Redis, Node.js, Docker networks, persistent volumes, and health checks.