Deploying Laravel with Docker: Configuration, Production, and Debugging
Deploying a Laravel application involves more than copying PHP files to a server. A production environment needs a compatible PHP runtime, required extensions, a web server, database connectivity, writable storage, secure configuration, and a reliable deployment process.
Docker packages an application's runtime and dependencies into containers. This helps developers create more consistent environments across local development, testing, and production. It does not automatically make an application secure or highly available, but it provides a repeatable foundation for deployment.
In this guide, we will outline a practical Docker setup for Laravel, explain the production considerations, and cover common debugging scenarios.
Why Use Docker for Laravel?
Without containers, one developer might use a different PHP version or extension set from the production server. These differences can cause applications to work locally but fail after deployment.
Docker lets you define the PHP runtime, required extensions, and application startup process explicitly. Docker Compose can also connect the application to services such as MySQL and Redis in a local environment.
Containers are especially useful when a project requires multiple services or when a team wants to reproduce production-like conditions during development.
Step 1: Create a Dockerfile
The following example uses PHP-FPM and installs a few common extensions. It is a starting point, not a complete production image. Choose a PHP version supported by your Laravel version and application dependencies, and review the official PHP image documentation before deploying.
FROM php:8.3-fpm
RUN apt-get update && apt-get install -y \
git unzip libzip-dev libicu-dev libonig-dev \
&& docker-php-ext-install pdo_mysql mbstring zip intl opcache \
&& rm -rf /var/lib/apt/lists/*
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
COPY composer.json composer.lock ./
RUN composer install \
--no-dev \
--prefer-dist \
--no-interaction \
--no-scripts
COPY . .
RUN composer dump-autoload --optimize
CMD ["php-fpm"]
This example is intentionally simple. Some applications require additional PHP extensions, frontend assets, or Composer scripts. If your application needs extensions such as GD, BCMath, or Redis, install and configure them explicitly.
For a production image, consider a multi-stage build, a non-root runtime user, dependency scanning, and minimizing packages in the final image. Run the application using a compatible web server configuration rather than exposing PHP-FPM directly to the public internet.
Step 2: Add a Docker Compose Setup
For local development, Docker Compose can run the application, web server, database, and Redis as separate services. The following example is a simplified development setup; it is not intended to be copied directly into production without security and persistence adjustments.
services:
app:
build:
context: .
working_dir: /var/www/html
volumes:
- ./:/var/www/html
depends_on:
- db
- redis
web:
image: nginx:stable
ports:
- "8080:80"
volumes:
- ./:/var/www/html:ro
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- app
db:
image: mysql:8.0
environment:
MYSQL_DATABASE: app
MYSQL_USER: app
MYSQL_PASSWORD: local-password
MYSQL_ROOT_PASSWORD: local-root-password
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:alpine
volumes:
mysql_data:
Use development-only credentials in local environments. For production, use strong secrets managed outside source control, private networking, persistent storage where needed, and appropriate backup and recovery procedures.
The Nginx configuration should serve Laravel's public directory as the document root and pass PHP requests to the PHP-FPM service. Do not expose the project root, because it contains application code and configuration files.
Step 3: Configure Laravel Environment Variables
Laravel uses environment variables to configure database connections, cache drivers, mail, queues, and other services. In Docker Compose, service names typically act as hostnames on the internal network.
For example, Laravel might use the following values inside the Compose network:
APP_ENV=production
APP_DEBUG=false
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=app
DB_USERNAME=app
DB_PASSWORD=replace-with-a-secure-secret
CACHE_STORE=redis
REDIS_HOST=redis
REDIS_PORT=6379
These are illustrative values. In production, the database hostname may be a managed database endpoint, and Redis may use authentication or TLS. Use the environment variable names supported by your installed Laravel version and configuration files.
Never commit your production .env file, API keys, or database passwords to Git. Keep APP_DEBUG=false in production so exceptions do not expose sensitive details to users.
Step 4: Build and Start the Containers
For a local Compose project, you can build and start the services with:
docker compose build
docker compose up -d
docker compose ps
Check the logs if a service fails to start:
docker compose logs --tail=100 app
docker compose logs --tail=100 web
docker compose logs --tail=100 db
To execute an Artisan command inside the running application container:
docker compose exec app php artisan about
The service name app must match the name in your Compose file. If you use Laravel Sail, the corresponding commands and service names may differ.
Step 5: Prepare Laravel for Production
Before directing traffic to a new release, make sure dependencies and configuration are ready. A typical deployment may include these steps:
- Build an immutable application image from a specific Git commit.
- Install production dependencies and compile frontend assets where required.
- Provide secure environment variables and verify database connectivity.
- Run the required database migrations in a controlled release step.
- Cache Laravel configuration and routes when compatible with the application.
- Ensure storage and cache directories have the required permissions.
- Restart or reload PHP workers and queue workers as appropriate.
- Run health checks before sending traffic to the new release.
For example, common optimization commands include:
php artisan config:cache
php artisan route:cache
php artisan view:cache
Route caching requires routes that are compatible with Laravel's route cache. Test these commands during your build or release process before relying on them in production.
Database Migrations and Safe Releases
Run database migrations deliberately rather than starting them independently in every application container. In a deployment with multiple replicas, having every replica attempt to migrate can cause unnecessary contention or operational problems.
Prefer backward-compatible migrations when old and new application versions may run simultaneously. For example, add a new nullable column first, deploy code that writes and reads it safely, backfill existing data if necessary, and only remove obsolete fields in a later release.
Always take appropriate backups before risky schema changes and test restoration procedures. A database backup is useful only if it can be restored successfully.
Queue Workers and Scheduled Tasks
If your Laravel application uses queued emails, notifications, or other background jobs, those workers must be running in production. A web container being healthy does not prove that queue processing is working.
Run queue workers as managed processes or dedicated services, configure retry and timeout behavior, and monitor failed jobs. After deploying new code, restart workers so they load the updated application code.
Laravel's scheduler also needs a reliable trigger, commonly a scheduled command or a platform scheduler that invokes php artisan schedule:run at the required interval.
Common Docker Deployment Problems
Database connection refused
Check the database service status and Laravel's environment variables. Inside a container, localhost refers to that same container. Use the database service name when connecting to another service in the Compose network.
HTTP 502 Bad Gateway
A 502 response often means Nginx cannot reach PHP-FPM or the upstream service is unavailable. Inspect both Nginx and application logs, confirm the upstream hostname and port, and verify that PHP-FPM is listening correctly.
Storage permission errors
Laravel needs appropriate write access to storage and bootstrap/cache. Configure ownership and permissions for the actual runtime user. Avoid solving permission errors by making the entire application directory world-writable.
Changes do not appear after deployment
Check whether the new image was built and deployed, whether the running container uses the expected image, and whether old queue workers or caches remain active. An immutable image and a clear release process make these problems easier to diagnose.
Container exits immediately
Inspect the container logs and exit code. Common causes include invalid configuration, missing environment variables, failing startup commands, and unavailable dependencies. Avoid repeatedly restarting a failing service without first understanding the error.
Security and Reliability Checklist
- Keep production secrets outside source control and container images.
- Use supported base images and regularly rebuild security updates.
- Expose only required ports and keep databases on private networks.
- Use HTTPS at the appropriate ingress or reverse proxy.
- Configure health checks, application logs, and error monitoring.
- Back up persistent data and test recovery procedures.
- Use resource limits and monitor CPU, memory, disk, and worker saturation.
- Test releases and database changes before deploying to production.
Final Thoughts
Docker makes Laravel deployments more repeatable, but production reliability comes from the complete operating process: secure configuration, predictable builds, safe migrations, managed background workers, monitoring, and recovery planning.
Start with a minimal container setup, verify it locally, and then adapt it to your hosting environment. Avoid treating a development Compose file as a production architecture without reviewing security, networking, storage, and deployment requirements.