How-To: Dockerized Laravel that just works

Dockerizing Laravel for production is challenging. In this article, we provide a pattern for dockerizing Laravel that works in production and local.

September 1, 2021
Brad Sickles
Co-founder & CEO of Nullstone

Overview

## Introduction

Unfortunately, many guides explain *just enough* to get your Laravel app working in Docker, but fall short in a few areas.

1. **The resulting image is large.** This makes for slow local setup, especially if you are working at a coffee shop. This large image also makes for a frustrating experience if you are deploying a critical fix to production and it is taking forever to pull the image.
2. **The container needs restarted locally.** Every time you make a change to your Laravel application, you have to rebuild/restart your container to see your changes. When we face this, we end up running Laravel on our local machine and discarding Docker.
3. **The image is configured for production use.** When configured *only* for production use, the docker image is difficult to debug or diagnose. We end up running Laravel locally and use Docker for production. Or we create a separate `local.Dockerfile` that we have to maintain separately.

## Laravel that just works, everywhere

This guide gets you started with docker on a default Laravel app that *just works* locally and in production with the following:
- Server is optimized for [php-fpm](https://php-fpm.org/).
- Static assets and files are automatically added using `ONBUILD` during your docker build process.
- When making code changes, no need to rebuild/restart your container.
- Logs are emitted to stdout/stderr.
- The resulting image is relatively small (~37mb).
- Preconfigured to attach [nginx](https://www.nginx.com/) sidecar container. See below.

This guide assumes the default setup uses a mysql database, but also contains details on how to add other dependencies and databases (e.g. postgres, redis, etc.).

## Quickstart

To get started, you will need to add 3 files to our repository.
Create `Dockerfile` in your repository root. This is used to build your local and production docker image.
If you're curious how the base image is configured, visit [https://github.com/nullstone-io/docker-laravel](https://github.com/nullstone-io/docker-laravel).

```docker

# syntax=docker/dockerfile:1
FROM nullstone/laravel

# Copy code, install dependencies
COPY --chown=nobody:nobody . .
RUN composer install --optimize-autoloader --no-dev

```

Create `.dockerignore` in your repository root. This will prevent docker from adding sensitive files (e.g. `env`) or PHP packages that are specific to your local system. Don't worry, your vendor directory inside the docker container will be cached.

```docker
.*
vendor/

```

Create `docker-compose.yml` in your repository root. This stands up a docker container running php containing your laravel app, an nginx container for static assets, and a mysql database container.

```docker
version: "3.8"
services:
 nginx:
   image: nginx:stable-alpine
   volumes:
     - app-public:/app/public
     - nginx-confd:/etc/nginx/conf.d
     - nginx-templates:/etc/nginx/templates:ro
   ports:
     - "3000:80"
   environment:
     - WEBAPP_ADDR=app:9000
   depends_on:
     - app
 app:
   image: nullstone/laravel:local
   volumes:
     - vendor:/app/vendor
     - .:/app
     - app-public:/app/public
     - nginx-confd:/etc/nginx/conf.d
     - nginx-templates:/etc/nginx/templates
   environment:
     - APP_ENV=local
     - APP_KEY=${APP_KEY}
     - MYSQL_URL=mysql://acme:acme@db:3306/acme
   depends_on:
     - db
 db:
   image: mysql:8.0
   ports:
     - "3306:3306"
   environment:
     MYSQL_ROOT_PASSWORD: acme
     MYSQL_DATABASE: acme
     MYSQL_USER: acme
     MYSQL_PASSWORD: acme
   healthcheck:
     test: ["CMD", "mysqladmin", "ping", "-pacme"]
     retries: 3
     timeout: 5s
volumes:
 vendor:
 app-public:
 nginx-confd:
 nginx-templates:
```

Now, let's launch our Laravel app.

```sh
$ docker compose up
```

Once your container is launched, the application should be reachable from [https://localhost:3000](https://localhost:3000).

## How to extend and configure

If your app did not launch properly, you may need to extend or configure.
Below is a set of common ways to configure and extend Laravel.

### Port 3000 is already in use

By default, this setup uses port 3000, but only by convention.
If you wish to change, refer to line 11 of `docker-compose.yml`.
Change `3000` in the line `- "3000:80"` to an unused port on your system.

### Postgresql instead of mysql

By default, the database is configured for mysql.
If you would like to swap for postgres, you will need swap out the `db` service and reconfigure Laravel to use `pgsql`.
Swap out the `db` service in the `services:` section of `docker-compose.yml`.

```docker
services:
 ...
 db:
   image: postgres
   ports:
     - 5432:5432
   environment:
     - POSTGRES_USER=acme
     - POSTGRES_PASSWORD=acme
     - POSTGRES_DB=acme

```

Reconfigure Laravel for `pgsql`. Make sure to remove the existing `DATABASE_URL` environment variable.

```docker
services:
 ...
 app:
   ...
   environment:
     ...
     - DB_CONNECTION=pgsql
     - DATABASE_URL=postgres://acme:acme@db:5342/acme?sslmode=disable
```

### Redis for caching

By default, caching is done using the file system.

If you would like to add a redis cache layer, then you will need to add redis to your docker-compose and configure Laravel to use redis inside the docker-compose. Then, you will need to update your `Dockerfile` with the appropriate dependencies.

Add `redis` service under the `services:` section in `docker-compose.yml`.

```docker
services:
 ...
 redis:
   image: redis:6-alpine
   ports:
     - "6379:6379"

```

Configure Laravel to use redis in the existing `app` service under `services:` section in `docker-compose.yml`.

```docker
services:
 app:
   ...
   environment:
     ...
     - CACHE_DRIVER=redis
     - REDIS_URL=tcp://redis:6379/0

```

Install `php-redis` extension to your Dockerfile. Warning: This was left out of the base image because it added ~100mb to the image size.

```docker
FROM nullstone/laravel
RUN apk add --no-cache pcre-dev $PHPIZE_DEPS \
 && pecl install redis \
 && rm -rf /tmp/pear \
 && docker-php-ext-enable redis.so
```

## What's next

Hopefully you have a fully functioning Laravel app running in docker on your local machine now. If you still need to configure the OS, you will need to use `apk` (the alpine package manager) instead of `apt`, `apt-get`, or `yum`. As you configure your app in different environments, make sure to follow docker best-practices by using environment variables. This will allow you to easily configure regardless of how your app is launched into production.

If you are interested in launching your newly-dockerized app to a cloud provider, log in at [https://app.nullstone.io](https://app.nullstone.io) and we will launch a secure container app on your cloud provider. We have a reference guide for Laravel in our docs at [https://docs.nullstone.io/frameworks/php/laravel.html](https://docs.nullstone.io/frameworks/php/laravel.html).## Run Laravel anywhereDocker is rapidly becoming the standard for running server applications. One of the key benefits of Docker is portability: the ability to run applications identically regardless of the underlying platform or system. This is especially helpful when running Laravel applications that require installation and configuration of OS-level packages.However, configuring a docker image is a tedious, and many times, frustrating endeavor. ## Laravel is difficult to configure on Docker