Speed up Laravel in Docker by moving vendor directory
Tags: laravel, docker, php
Published: 2021-09-14 07:20:41

---
title: Speed up Laravel in Docker by moving vendor directory
published: true
description: By moving Composer's vendor directory outside of the project root, you can see noticeable improvements in responsible time.
tags: laravel, docker, php
//cover_image: https://direct_url_to_image.jpg
---

I'm building a Laravel app on my MacBook Pro with Docker by mounting my project folder inside a Docker container. I was working on an API endpoint in the app that had a response time of a little more than a second. This made the interactivity in a connected React app feel _painfully_ slow.

This article will show you how to increase the speed of a containerized Laravel app during development on MacOS and Windows by moving Composer's vendor directory to inside the container.

## Why Laravel is slow in Docker

The reason behind this slow performance is two-fold, caused by the combination of the PHP request model and the latency when transferring data between Docker Desktop's Linux VM and the host MacOS machine. 

When PHP receives a request, it loads all of its dependencies on a per-request basis. Once the request finishes it discards all of the loaded data. This is different than something like Node.js, where a single thread handles all requests and each module is cached when it's first loaded.

PHP's way of loading dependencies is already inefficient compared to Node. And when you do Docker development on a non-Linux machine, you add the overhead of crossing between Docker Desktops's Linux VM and the mounted host machine folder for every single dependency file that's loaded. It's the difference between moving books from one shelf to another vs moving books to a shelf in a house down the street.

To keep Docker fast, we want to minimize the amount of times we need to cross between Docker Desktops's Linux VM and the host machine. We can do that by storing Composer's `vendor/` folder inside the container instead of in the mounted project directory.

## Moving the vendor directory 

For the rest of this post, we'll assume that you have a container where your app is stored in `/srv/app/`. We will install the Composer dependencies in `/srv/vendor/`.

In your Dockerfile, we'll install the Composer dependencies using the `RUN` command below:

```Dockerfile
# ...previous Dockerfile commands
WORKDIR /srv/app
COPY . .
RUN COMPOSER_VENDOR_DIR="/srv/vendor" composer install
```
This will install the Composer dependencies in the `/srv/vendor` directory, but Laravel can't see them: it expects its dependencies to be in the project root's `vendor/` folder. We must update the places where Laravel loads the Composer autoload file.

In `public/index.php` and `artisan`, find the following line:

```php
require __DIR__.'/vendor/autoload.php';
```
And replace it with:

```php
require __DIR__.'/../vendor/autoload.php';
```
Also update the path in `phpunit.xml`. 

Run `docker build` (or `docker-compose build` if you use Compose), and bring the container back up. When you navigate to your Laravel project in the browser, you should see noticeably faster page-load speeds. **In my project, my API request drop from about 1000ms to about 200ms.**

## Gotchcas

The faster page-load speeds are nice, but if you use IntelliSense & autocomplete, you're really going to want your dependencies in your mounted project directory so that your editor can see them.

You can install your Composer dependencies in your mounted project directory by running the following command from your host machine while the Laravel container is running:

```sh
docker exec your-container-name composer install
``` 

Another "gotcha" can happen when installing new dependencies using the `composer require` command. You may occasionally run into an error like the following:

```
Class "Laravel\Breeze\BreezeServiceProvider" not found  

Script @php artisan package:discover --ansi handling the post-autoload-dump event returned with error code 1

Installation failed, reverting ./composer.json and ./composer.lock to their original content.
```

This error occurs because when the installation completes, it tries to load the Laravel app–which is looking at the `vendor/` folder inside the container. When installing dependencies, it may be best to run the `composer require` command twice: once for the vendor folder in your container, and once for the directory that is shared with your host.

```php
# Install in the container first
COMPOSER_VENDOR_DIR="/srv/vendor" composer require [package-name]

# Then install in the directory shared with your host
composer require [package-name]
```
Maybe rebuild your image after this too: if you're using Docker Compose then your changes won't persist once you stop your container.

## Further reading

In addition to moving your dependencies into your container, you can also enable PHP's OpCache to make Laravel load even faster. [Kristoffer Högberg has a concise write-up on how to do this](https://www.hmazter.com/2019/04/speeding-up-php-docker-with-opcache/).

Michaël Perrin wrote a post called ["3 ways to get Docker for Mac faster on your Symfony app"](http://blog.michaelperrin.fr/2017/04/14/docker-for-mac-on-a-symfony-app/) that has some interesting performance measurements before and after optimization. Michaël's post was the inspiration behind this article, and it's definitely worth a read.

Alternatively, a few people have mentioned that you might be able to sidestep these Docker-related performance issues entirely by using [Octane](https://laravel.com/docs/8.x/octane), so if you need the best possible performance then take a look at that as well.

---

## Addendum

I've been using this setup for about ten days now, and I feel compelled to give a clear and unambiguous warning: **moving your vendor folder causes problems**. 

This setup caused `php artisan test` to completely stop working in my CI. Running xdebug with this setup is nearly unusable. I'm actively working around these issues so that I can enjoy the performance benefits, but if you use this configuration you will run into problems. However, you may still decide the trade-offs are worth it.