Architecting Laravel Environments with Docker and Sail: From Local Development to Production Readiness

The modernization of PHP application deployment has shifted decisively toward containerization, with Laravel leading the charge through its integrated tooling. At the core of this ecosystem is the synergy between Laravel and Docker, a relationship that allows developers to encapsulate the entire application stack—including the PHP runtime, web server, database, and caching layers—into isolated units. This architectural approach eliminates the "it works on my machine" phenomenon by ensuring that the development environment is a mirror image of the production environment. Central to this experience is Laravel Sail, an official Docker-based development environment that abstracts the complexities of Docker Compose, providing a streamlined interface for managing containers without requiring deep expertise in container orchestration.

The Fundamentals of Laravel Sail and Docker Integration

Laravel Sail serves as a lightweight, command-line interface for interacting with Docker Compose. Rather than requiring the developer to manually write complex YAML files for every project, Sail provides a wrapper that simplifies the orchestration of the laravel.test container, which is the primary application container responsible for serving the web application.

The technical implementation of Sail relies on Docker Compose to manage the lifecycle of multiple containers. When a developer initializes Sail, it generates a compose.yaml (or docker-compose.yml) file that defines the services required for the application to function. This includes the PHP runtime, a database such as MySQL, and potentially other services like Redis or Meilisearch.

The impact of using Sail is a significant reduction in onboarding time for new developers. Instead of spending hours installing PHP, MySQL, and Node.js locally on a host OS—which often leads to version conflicts—a developer can simply clone a repository and run a single command to spin up the entire stack.

Contextually, while other unofficial environments like Laradock exist, Sail is the official recommendation because it is deeply integrated into the Laravel ecosystem, ensuring that the runtimes are optimized for the specific versions of PHP and Laravel being used.

Implementing Dockerization for Existing Applications

When transforming an existing Laravel application—such as the Student CRUD application built on Laravel 8—into a Dockerized environment, a specific sequence of operations must be followed to ensure the application is properly linked to its containerized services.

The process begins with the preparation of the environment and the installation of the Sail dependency. For an existing project, the following steps are executed:

  1. Navigate into the project directory:
    cd laravel-crud-app

  2. Prepare the environment configuration by copying the example file:
    cp .env.example .env

  3. Install the Sail package and initialize the configuration:
    composer require laravel/sail --dev && php artisan sail:install

During the sail:install process, the user is prompted to select the required services. For a standard Laravel 8 application using MySQL, the user must select the option corresponding to MySQL (typically option 0). This action triggers the creation of the docker-compose.yml file at the root of the project.

Once the configuration is set, the build process utilizes Docker BuildKit to optimize the creation of images. BuildKit is a build enhancement available in modern Docker versions that improves the speed and efficiency of the build process through better caching and parallel execution. The command to build the containers using BuildKit is:

COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 ./vendor/bin/sail build

This process typically takes between 5 to 10 minutes, depending on the available internet bandwidth. Following the build, the containers are launched:

./vendor/bin/sail up

For users who prefer to run the containers in the background without occupying the terminal session, the "detached" mode is utilized:

sail up -d

Configuration and Post-Deployment Synchronization

Launching the containers is only the first step; the application must be configured to communicate with the containerized services and secure its internal state.

The primary requirement after the containers are running is the generation of the application key, which is critical for encryption and session security:

./vendor/bin/sail artisan key:generate

Furthermore, the .env file must be modified to align with the internal Docker network. In a Docker Compose environment, the DB_HOST should not be 127.0.0.1 (which refers to the container itself) but rather the name of the service defined in the YAML file. For a MySQL service named mysql, the configuration is as follows:

Variable Value Description
DB_CONNECTION mysql Specifies the database driver
DB_HOST mysql The service name of the MySQL container
DB_PORT 3306 The standard MySQL port
DB_DATABASE laravel The name of the database schema
DB_USERNAME root Default administrative user
DB_PASSWORD Default empty password for root

After the environment variables are correctly mapped, the database schema must be initialized. This is achieved by running the migrations:

./vendor/bin/sail artisan migrate --force

The --force flag is often necessary when running migrations in environments that the system perceives as production-like or when ensuring the command executes without interactive prompts. Once these steps are completed, the application becomes accessible at http://localhost.

Analyzing the Gap Between Development and Production

A critical distinction exists between the images generated by Laravel Sail and those suitable for a production environment. An analysis of the Sail runtime (specifically for PHP 8.0) reveals significant overhead and security concerns that make it unsuitable for live deployment.

The Sail image is built on top of a full Ubuntu image rather than a lean PHP-specific image. It includes a variety of development tools that are unnecessary in production, such as:
- Node.js 15 (which is not an LTS version)
- Yarn
- Composer

The most concerning aspect is the web server implementation. A deep dive into the Dockerfile located at ./vendor/laravel/sail/runtimes/8.0/Dockerfile shows that the supervisor file utilizes the following command:

command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80 user=sail

This configuration uses php artisan serve, which is a built-in PHP development server. This server is single-threaded and not designed for the high-concurrency, security, and stability requirements of a production web server.

The impact of this is twofold: image size and performance. The Sail image is approximately 732 MB, which increases deployment time and storage costs. In contrast, a custom production-ready image can be reduced to approximately 457 MB by stripping away development dependencies and using more efficient base images.

Constructing a Production-Ready Docker Environment

To transition a Laravel application to production, the development-centric docker-compose.yml must be sidelined. The first step is renaming the development file to avoid confusion:

mv docker-compose.yml docker-compose-dev-sail.yml

When the development environment needs to be restarted using this renamed file, the command must explicitly reference the file:

./vendor/bin/sail -f docker-compose-dev-sail.yml up

For the actual production setup, a new Dockerfile and docker-compose file are created based on specific production assumptions. A primary assumption for production is the use of a managed database service, such as AWS RDS or Google Cloud SQL, rather than running a database container alongside the application.

To maintain a clean image, a .dockerignore file is implemented. This prevents unnecessary files from being baked into the image, which would otherwise increase the image size and potentially leak sensitive information. A standard production .dockerignore contains:

  • .git
  • .env

The build process for the production container is executed as follows:

COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose build

Once the build is complete, the image size can be verified using:

docker images | grep app

To launch the production-ready containers, the standard command is used:

docker-compose up

Technical Comparison of Container Runtimes and Ecosystems

While Docker is the most dominant player in the containerization space—often compared to AWS in terms of market share—it is not the only option. The container ecosystem is governed by the Open Container Initiative (OCI), which ensures that container runtimes are standardized across different platforms.

An alternative to Docker is Rocket, which is positioned similarly to Vultr in the analogy of cloud providers—offering a different approach to container management while adhering to the same fundamental goals of isolation and portability.

The choice between using a monolithic image (like the one described in this guide) and a split architecture (using Nginx and PHP-FPM in separate containers) depends on the scale of the application. While the single-container approach is simpler to manage, using Alpine-based images with FPM and Nginx would further reduce the image size below 457 MB and improve performance through professional-grade request handling.

Summary of Operational Commands

The following table summarizes the essential commands used throughout the Laravel Docker lifecycle.

Phase Command Purpose
Installation composer require laravel/sail --dev Adds Sail to the project
Initialization php artisan sail:install Configures Docker services
Build ./vendor/bin/sail build Creates the Docker images
Start sail up -d Starts containers in background
Stop sail stop Halts container execution
Maintenance ./vendor/bin/sail artisan key:generate Secures the application
Migration ./vendor/bin/sail artisan migrate --force Sets up database structure

Conclusion

The transition from a local Laravel installation to a Dockerized environment via Sail represents a significant upgrade in developmental maturity. By utilizing Sail, developers can ensure that their environment is isolated and reproducible. However, the technical audit of Sail's default images reveals a clear boundary between "development-ready" and "production-ready." The reliance on artisan serve and the inclusion of non-LTS Node.js versions in the Sail image make it an inappropriate choice for production.

The path to production requires a deliberate shift: stripping the image of development tools, utilizing .dockerignore to maintain a lean footprint, and moving toward managed database services. By reducing the image size from 732 MB to 457 MB and moving away from the Ubuntu-based development runtime, architects can achieve a more secure and performant deployment. Ultimately, the use of Docker and Sail allows Laravel developers to move fluidly between their local machines and the cloud, provided they understand the underlying architectural differences between a wrapper designed for convenience and a container designed for scale.

Sources

  1. Honeybadger Blog: Laravel Docker PHP
  2. Laravel Documentation: Sail

Related Posts