Creating your own docker-compose environment for Laravel

docker-compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.

It's recently become more popular as many Laravel projects start to use it, and it's a system we've used for a long time when developing for Laravel. LoadForge is a Laravel project and uses docker-compose for it's development environment, and Laravel Vapor for production. We also have other projects that are deployed in a more traditional fashion that we use docker-compose in the exact same way for.

Installing docker-compose

You need to install docker and docker-compose of course. On Ubuntu you can run the following (ps yes this works in WSL2 on Windows with the Ubuntu subsystem).

apt-get install docker-compose

WSL2 Specific Notes

The best WSL2 Windows Laravel dev environment in my mind is this exact one, run inside WSL but with Docker Desktop running in Windows.

What is included?

Below is our Laravel specific docker-compose. It's a lot more complex than you may need because it includes many functions, specifically:

  • Nginx for serving pages on localhost:80
  • PostgreSQL for the database
  • Redis for key-value and caching
  • A custom php container
  • A composer wrapper
  • An NPM wrapper
  • An artisan wrapper
  • A queue worker process
  • And the laravel scheduler for scheduled tasks

docker-compose.yml

version: '3'

networks:
  laravel:

services:
  site:
    image: nginx:stable-alpine
    container_name: laravel_site
    hostname: 'laravel.dev.test'
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./:/var/www/html
      - ./docker/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - php
      - postgres
      - redis
    networks:
      - laravel

  postgres:
    image: postgres:12.3-alpine
    container_name: laravel_postgres
    ports:
      - 5432
    environment:
      POSTGRES_DB: laravel
      POSTGRES_USER: laravel
      POSTGRES_PASSWORD: secret
    networks:
      - laravel

  redis:
    image: redis:6-alpine
    container_name: laravel_redis
    ports:
      - 6379
    networks:
      - laravel

  php:
    build:
      context: .
      dockerfile: docker/php.dockerfile
    container_name: laravel_php
    volumes:
      - ./:/var/www/html
    ports:
      - 9000
    networks:
      - laravel

  composer:
    image: composer:2
    container_name: laravel_composer
    volumes:
      - ./:/var/www/html
    working_dir: /var/www/html
    depends_on:
      - php
    networks:
      - laravel

  npm:
    image: node:13.7
    container_name: laravel_npm
    volumes:
      - ./:/var/www/html
    working_dir: /var/www/html
    entrypoint: ['npm']
    networks:
      - laravel

  artisan:
    build:
      context: .
      dockerfile: docker/php.dockerfile
    container_name: laravel_artisan
    volumes:
      - ./:/var/www/html
    depends_on:
      - postgres
      - php
      - redis
    working_dir: /var/www/html
    entrypoint: ['php', '/var/www/html/artisan']
    networks:
      - laravel

  scheduler:
    container_name: laravel_scheduler
    build:
      context: .
      dockerfile: docker/php.dockerfile
    volumes:
      - ./docker/scheduler.sh:/run.sh
      - ./:/var/www/html
    working_dir: /var/www/html
    depends_on:
      - site
    networks:
      - laravel
    command: /bin/sh /run.sh

  worker:
    container_name: laravel_worker
    build:
      context: .
      dockerfile: docker/php.dockerfile
    volumes:
      - ./:/var/www/html
    working_dir: /var/www/html
    restart: always
    depends_on:
      - site
      - redis
    networks:
      - laravel
    command: php /var/www/html/artisan queue:work

Additional files

You will want to create the above as docker-compose.yml in your main project directory (laravel/). Then create a docker directory inside of that for storing our custom configurations.

mkdir docker

In there we want to create three files. One is a custom nginx config file, and the second is our custom container for PHP allowing for customization of your PHP install. Finally we add a helper script for the scheduler.

docker/default.conf

server {
    listen 80;
    index index.php index.html;
    server_name localhost;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/html/public;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

docker/php.dockerfile

FROM php:7.4-fpm-alpine

WORKDIR /var/www/html

RUN apk add --update sqlite-dev zlib-dev zlib libpng-dev libzip-dev freetype-dev libjpeg-turbo-dev libpq postgresql-dev pkgconfig imagemagick6 autoconf
RUN docker-php-ext-configure gd
RUN docker-php-ext-install zip pcntl pdo pdo_mysql gd exif sockets pgsql bcmath pdo_pgsql exif
RUN docker-php-ext-enable pdo_pgsql exif
RUN apk add --update autoconf build-base
RUN pecl install -o -f redis
RUN docker-php-ext-enable redis
RUN link /usr/local/bin/php /usr/bin/php

RUN echo "php_admin_value[memory_limit] = 2G" >> /usr/local/etc/php-fpm.d/www.conf
RUN echo "memory_limit=2G" >> /usr/local/etc/php/php.ini

docker/scheduler.sh

sleep 60

while [ true ]
do
   php /var/www/html/artisan schedule:run --verbose --no-interaction &
   sleep 60
done%

Running the setup

You can now run the following to launch the environment:

docker-compose up -d --build

Personally, I always add these aliases to my ~/.zshrc:

alias dev="docker-compose run --rm"
alias dcu="docker-compose up"
alias dqw="docker-compose run --rm artisan queue:work --timeout=900"

That allows you specifically to use "dcu" to launch your environment, and "dev" to run commands in it. For example:

dev npm update
dev composer require xyz
dev artisan test

Final words

Aside from the great opportunity to learn what's going on under the hood, and how it helps you to deploy into production, this guide really serves to help those who may have more complicated stacks that something like Laravel Sail provides. You can really customize and tweak your system to suit you.

Feel free to comment for any assistance :)

Ready to run that test?
Start your first test within minutes.