A Docker Image specialized for running Laravel PHP framework with Nginx on an Alpine platform.

GitHub: https://github.com/a2way-com/docker-base-laravel
Docker Hub: https://hub.docker.com/r/a2way/docker-base-laravel

I use Laravel as my framework of choice when developing backends both in my personal and work projects. In all of those occasions, I have been manually configuring the Docker container environments separately. I created a2way/docker-base-laravel Docker image to minimize the stuff I have to do manually.

This Docker Image contains following:

  • Alpine Linux base.
  • Nginx.
  • PHP-FPM.
  • Supervisor to keep Nginx and PHP-FPM running.
  • Composer.
  • A script to auto build the .env file based on environment variables provided into the container.

The instructions were tested on Ubuntu. They should work on other Linux OSes as well. You should be able to follow the same general steps in other OSes too.

You must have installed docker and docker-compose to follow the instructions.

To use, either COPY or mount Laravel root directory into /app directory. You can use Composer to bootstrap a Laravel project right inside the Docker container’s /app directory. By having that directory mounted to the host file system, you can persist the Laravel files.

To auto build the .env file, inject an environment variable named LARAVEL_VARS, and list all Laravel environment variable names as LARAVEL_VARS‘s value. Make the Laravel environment variable names space-separated (Eg: LARAVEL_VARS=APP_NAME APP_ENV APP_KEY APP_DEBUG ....). Then inject each of those environment variables with their values.

Model Development Setup

Open your shell’s “rc file” (Eg: .bashrc or .zshrc.). At the end of the file, add following two lines:

export UID
export GID

Make a directory for your new project (Eg: my-proj.). In it, create a Dockerfile like this:

FROM a2way/docker-base-laravel:v_._._
RUN apk --update add shadow
ARG UID
ARG GID
RUN usermod -u $UID app && groupmod -g $GID app

Replace v_._._ with the version you’re going to use. Always try to use the latest one.

In my-proj directory, make a docker-compose.yml file like this:

version: '3'
services:
  my-proj:
    build:
      context: .
      args:
        UID: ${UID}
        GID: ${GID}
    ports:
     - 8000:80
    env_file:
     - ./env/my-proj.env
    volumes:
    # - ./vols/vendor:/app/vendor/
     - ./src/:/app/

Note that # - ./vols/vendor:/app/vendor/ is commented out, until we make it active in a later step.

Create following sub directories inside my-proj:

  • vols/vendor.
  • src.
  • env.

Create a .gitignore file to ignore files and directories we don’t need tracked in the Git repo:

src/.env
vols/

Inside the env directory, make a file .gitignore file like this:

*
!tmp.*
!.gitignore

It will cause Git to ignore any file inside the env directory, except the .gitignore file itself, and anything that has a file name starting with tmp.. We can use that behavior to ignore actual environment variable files but track templates of them, that has “tmp.” at the start of their names.

Inside the env directory, make two files: tmp.my-proj.env and my-proj.env. Use the following as the content of tmp.my-proj.env:

LARAVEL_VARS=APP_NAME APP_ENV APP_KEY ...
// Fill in complete list of Laravel environment variable names.

APP_NAME=my-proj
APP_ENV=local
APP_KEY=
.
.
. //Fill in complete list of Laravel environment variable names.
. //Keep default values in, when they are okay to be tracked in Git.

Next, copy the content of tmp.my-proj.env into my-proj.env, and fill in all required values.

Go back to the project root.

Make a Makefile to make it easy to access the shell of the Docker container as the app user inside the Docker Container, which is mapped to your host user’s UID, which also shares GID with your host user:

shell-my-proj:
	docker-compose exec -u ${UID}:${GID} my-proj sh

To access it as root, you can run docker-compose exec my-proj sh.

Now you can start the Docker container:

docker-compose up --build -d

It should run without a problem, and docker ps should show you the container running. If you go to http://localhost:8000/ in your browser, you should see a 404 page from Nginx.

Now go inside the container.

make shell-my-proj

Inside the /app directory, you should see that an .env file is already created with the values you provided. Delete it for now, as otherwise composer won’t create a Laravel project inside this directory as it’s not empty. Don’t be afraid, as that file would be auto created next time you turn the Docker container on.

Then, create the Laravel project:

composer create-project --prefer-dist laravel/laravel .

Then, exit the container, and turn it off:

docker-compose down

After that, delete the vendor directory inside src directory.

Now uncomment the line we had commented out in docker-compose.yml file, and re run the Docker container.

docker-compose up --build -d

Then go inside the container again with make shell-my-proj, and reinstall Composer packages:

composer install

This time, the content of the Docker container’s /app/vendor directory will be persisted in the vols/vendor directory in the project root in the host machine.

Go to http://localhost:8000/, and you should be greeted with Laravel welcome page.

Produce Production Docker Images

In the my-proj directory, make a file named prod.Dockerfile, and have the following as its content:

FROM a2way/docker-base-laravel:v_._._
WORKDIR /app
RUN chown -R app:app .
USER app:app
COPY --chown=app:app ./src/composer.json ./src/composer.lock /app/
RUN composer install --no-autoloader --no-dev 
COPY --chown=app:app ./src /app
RUN composer dump-autoload

Make a file named prod.docker-compose.yml, and have the following as its content:

version: '3'
services:
  my-proj:
    image: my-docker-username/my-proj
    build:
      context: .
      dockerfile: prod.Dockerfile
    ports:
     - 9000:80
    env_file:
     - ./env/my-proj.env

Then build and run it:

docker-compose -f prod.docker-compose.yml up --build -d

You should be able to see your production Docker container running in http://localhost:9000/. You should also be able to see your production Docker Image tagged with my-docker-username/my-proj:latest.