laravel docker for mac performance improvements

Paul Reaney
4 min readOct 24, 2020

After moving my laravel application over to docker I noticed a huge performance impact. One of the pages had 5 api requests on load each taking around 2s, which was about 20x slower than running the application on my local machine.

I use a mac for web development and so there is always going to be a performance cost when using docker for development. Laravel projects consist of a lot of files and these have to be kept in sync between the host machine and the container.

Disable Debugbar

This was a big win reducing the response time by ~50%. Laravel debugbar creates a log file every request and writing files is slow when running laravel application in docker.

You can disable debugbar in the .env file:

DEBUGBAR_ENABLED=false

Using the ‘delegated’ option

Volumes are the preferred way to persist data generated from and used by docker containers.

Docker for Mac allows you to use configuration options for volumes to improve the performance — by default Docker mounts each volume using a consistent option. It means that everything you see on the host machine is the same as in the container. We don’t necessarily need full consistency though — I want to see all my code changes reflected in the container immediately, but I don’t mind if the container → host direction takes longer for writing logs etc.

To achieve this behaviour, you can use the delegated option after the volume in the docker-compose.yml file:

version: "3.3"
services:
nginx:
image: project/nginx
build: ./docker/nginx
ports:
- 8080:80
volumes:
- ./:/var/www/project:delegated
networks:
- projectnetwork
php:
image: project/php
build:
context: ./
volumes:
- ./:/var/www/project:delegated
env_file:
- ./docker/php/.local.env
working_dir: /var/www/project
networks:
- projectnetwork

You will need to restart containers after this. I’d estimate this reduced my response times by ~25%. Using cached instead of delegated may also yield performance improvements, but is more strict. The differences are outlined here: https://tkacz.pro/docker-volumes-cached-vs-delegated/.

NFS

File system performance seems to be an ongoing issue on Docker for mac https://github.com/docker/for-mac/issues/1592. One option to get around this is to set up NFS and mount the volumes. I found this approach to reduce my response times around 25%, but there are downsides, such as not being able to use filesystem events.

I used the Disk Utility tool to add a new volume and I used the APFS (Encrypted) format:

Then I enabled NFS for docker using this script:

#!/usr/bin/env bashOS=`uname -s`if [ $OS != "Darwin" ]; then
echo "This script is OSX-only. Please do not run it on any other Unix."
exit 1
fi
if [[ $EUID -eq 0 ]]; then
echo "This script must NOT be run with sudo/root. Please re-run without sudo." 1>&2
exit 1
fi
echo ""
echo " +-----------------------------+"
echo " | Setup native NFS for Docker |"
echo " +-----------------------------+"
echo ""
echo "WARNING: This script will shut down running containers and prune docker volumes."
echo ""
echo -n "Do you wish to proceed? [y]: "
read decision
if [ "$decision" != "y" ]; then
echo "Exiting. No changes made."
exit 1
fi
echo ""if ! docker ps > /dev/null 2>&1 ; then
echo "== Waiting for docker to start..."
fi
open -a Dockerwhile ! docker ps > /dev/null 2>&1 ; do sleep 2; doneecho "== Stopping running docker containers..."
docker-compose down > /dev/null 2>&1
docker volume prune -f > /dev/null
osascript -e 'quit app "Docker"'echo "== Resetting folder permissions..."
U=`id -u`
G=`id -g`
sudo chown -R "$U":"$G" .
echo "== Setting up nfs..."
LINE="/Volumes/MyVolume -alldirs -mapall=$U:$G localhost"
FILE=/etc/exports
sudo cp /dev/null $FILE
grep -qF -- "$LINE" "$FILE" || sudo echo "$LINE" | sudo tee -a $FILE > /dev/null
LINE="nfs.server.mount.require_resv_port = 0"
FILE=/etc/nfs.conf
grep -qF -- "$LINE" "$FILE" || sudo echo "$LINE" | sudo tee -a $FILE > /dev/null
echo "== Restarting nfsd..."
sudo nfsd restart
echo "== Restarting docker..."
open -a Docker
while ! docker ps > /dev/null 2>&1 ; do sleep 2; doneecho ""
echo "SUCCESS! Now go run your containers 🐳"

Which I got from here: https://gist.github.com/seanhandley/7dad300420e5f8f02e7243b7651c6657

This script removes your project and volumes, quits Docker and configures the NFS server. To change the volume name simply change this line:

LINE="/Volumes/MyVolume -alldirs -mapall=$U:$G localhost"

and amend /Volumes/MyVolume to your path.

Then make the file executable and run:

sudo chmod +x setup_nfs_docker.sh
./setup_nfs_docker.sh

You can check that NFS works properly by selecting Go > Connect to server... from the finder menu:

Finally, we need to reference the mount from the docker-compose.yml file:

version: "3.3"
services:
nginx:
image: project/nginx
build: ./docker/nginx
ports:
- 8080:80
volumes:
- nfsmount:/var/www/project:delegated
networks:
- projectnetwork
php:
image: project/php
build:
context: ./
volumes:
- nfsmount:/var/www/project:delegated
env_file:
- ./docker/php/.local.env
working_dir: /var/www/project
networks:
- projectnetwork
volumes:
nfsmount:
driver: local
driver_opts:
type: nfs
o: addr=host.docker.internal,rw,nolock,hard,nointr,nfsvers=3
device: ":${PWD}"

Now just restart the containers:

docker-compose up -d

Please leave any tips for performance improvements you came across in the comments.

Thanks!

--

--

Paul Reaney

I am a software developer and I like to write about interesting things I come across in my day to day.