Creating a Docker image for Laravel from scratch

04 Mar 2018 | docker, laravel, php, nodejs


I was looking at various stacks to develop a RESTful API and I came across Laravel. Laravel was a viable candidate mainly because it was in PHP (which my company is familiar with) and also it seems not too hard to develop (after going through a few webcasts on YouTube). Hence I decided take Laravel for a spin.

Never did I know setting up the development environment was a PITA.


There were several options to set up Laravel locally, but none seem to work for me. Starting from the options from the official docs, Laravel Homestead was the recommended option but I wanted support for using Bash on Windows, which Vagrant doesnt support. Setting up from scratch isn't great either cause doing that on Windows is suicidal.

Next up, XAMPP wasn't a good choice as I have existing PHP projects where the PHP version isn't supported by Laravel. Valet isn't a good option either as it is macOS only, which is a big no as I develop across different platforms.

So Docker was the most viable option, I guess. Docker supports Bash on Windows, albeit hacky (ie. Running commands from bash, but the daemon is running natively on Windows) as you need to install Docker on both bash and Windows. But that is a small price to pay in my opinion. Also, I've known Docker for awhile now and I've always wanted to try it out so why not?

Pre-built Docker Images

At first, things were looking good as there are already pre-built Docker Images for Laravel. One of the popular image is Laradock, with started out with Laravel but also expanded to other stacks as well.

However, after following the documentation, I couldn't see the laravel start page. Laradock bundles with MYSQL and phpmyadmin as well, and I couldn't access either of them.

Hence, I tried creating my own from scratch.

File structure

NOTE: Github repo available here

- docker-laravel
 |- docker-compose.yml
 |- web.dockerfile
 |- [other laravel source files go here]

STEP1: Configuring docker-compose

So the first thing I did was configuring a file called docker-compose.yml. In this file, the docker containers to be hosted and each of its configuration is listed out. In an empty directory I created the file, then entered the following:

version: '2'

services:                        # list of services(containers)
  web:                           # container name
    build:                       # build options
      context: ./                # file path context
      dockerfile: web.dockerfile # path to dockerfile
    ports:                       # ports to map
      - 80:80                    # [local port]:[container port]
    volumes:                     # list of directories to map
      - ./:/var/www
    links:                       # links to other container
      - db                       # db container; hosts the db container

First off, the web container hosts the NGINX HTTP service, with Node.js and Laravel's cli installed and ready to use. This is done by loading up web.dockerfile which is created in the next step, then the relevant ports and volumes (directories) are linked between the local machine and docker container. Finally, links is used to link the web container with other containers, in this case the db container (created later) in order for laravel to communicate with the database.

STEP2: Configuring the dockerfile

A dockerfile is a file that list outs the steps to build an image. A container is an instance of an image. In this case, I made a dockerfilenamed web.dockerfile for the web container:

# Sourcing official docker image of PHP 7.1 with Apache web server
FROM php:7.1-apache

# Change DocumentRoot directory
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf && \
    sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf

# Install dependencies for nodejs & laravel
RUN apt-get update && apt-get install -y gnupg zlib1g-dev
RUN docker-php-ext-install zip

# Install nodejs
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash && \
	apt-get install -y nodejs

# Install composer
RUN curl -sS https://getcomposer.org/installer | php && \
    mv composer.phar /usr/local/bin/composer && \
    chmod +x /usr/local/bin/composer

# Install laravel globally
RUN composer global require "laravel/installer"

# Add .composer/vendor/bin to bash_profile
RUN echo export PATH='$HOME/.composer/vendor/bin:$PATH' >> ~/.bashrc && \
	echo cd .. >> ~/.bashrc

At the time of this writing, PHP7.2 is available but it doesn't play well during the installation of Laravel, and hence PHP7.1 was used instead.

On top of the PHP7.1 image, Node.js and Laravel, together with their dependencies are automagically installed.

STEP3: Configuring docker-compose, Part II

Although now we have Laravel set up, we still need a database for it to store data. An instance of MYSQL is added by adding the following lines into docker-compose.yml:

version: '2'

services:                       # list of services(containers)
  db:                           # MYSQL instance is named db
    image: mysql:5.7.21         # Using MYSQL 5.7.21
    ports:                      # Mapping ports from container to local machine
      - 3306:3306
    environment:                # Environment variables (change accordingly)
      MYSQL_ROOT_PASSWORD: root # Root password
      MYSQL_DATABASE: db        # Database name
      MYSQL_USER: homestead     # Username
      MYSQL_PASSWORD: secret    # User password

The MYSQL instance was spun up with the above credentials, which were injected to the container as environment variables. Then, the port 3306 of the container is then mapped to the local machine's port 3306.

BONUS STEP: Setting up phpMyAdmin

At this point we can already start working on Laravel but, I wanted to set up phpMyAdmin to work on the MYSQL database in a easy-to-use GUI, as a bonus.

Again, going back to docker-compose.yml, another container phpmyadmin is set up by adding the following lines:

  phpmyadmin:                        # phpmyadmin container
    image: phpmyadmin/phpmyadmin:4.7 # Sourced from the official docker image of phpMyAdmin
    ports:                           # Mapping ports
      - 8080:80
      - db                           # Linking phpmyadmin container with db container

STEP4: Putting it all together

Finally, all the docker configuration has been set in place and it's time to spin up an instance. Run following commands in a terminal window:

# Build and start up a docker instance based on docker-compose.yml
# -d to run containers in detached mode
$ docker-compose up -d

# Launch a bash shell into the web container
$ docker-compose exec web bash

# Create a new laravel project
$ laravel new

If everything goes well, laravel should be up and running at http://localhost/. MYSQL and phpMyAdmin should be up and running at http://localhost:3306 and http://localhost:8080 respectively.


Checking status of containers

In the project directory, run the following command:

$ docker-compose ps

Remapping Ports

In docker-compose.yml the ports for each service can be changed. Eg: mysql service:

    image: mysql:5.7.21
      - [your_local_machine_port_here]:3306

Changing the directory of DocumentRoot

In web.dockerfile change ENV APACHE_DOCUMENT_ROOT /var/www/public to the line below:

ENV APACHE_DOCUMENT_ROOT /path/to/new/DocumentRoot

Rebuilding the containers from scratch

In the project directory, run the following command:

# --build rebuilds the containers
$ docker-compose up -d --build

Permission Denied

The stream or file "/var/www/storage/logs/laravel.log" could not be opened: failed to open stream: Permission denied

If the above message is shown while trying to access http://localhost/, run the follow commands:

# Launch a bash shell into the web container
$ docker-compose exec web bash
# Set permissions to 755 for /var/www
$ chmod -R 755 .
# Set owner of /var/www
$ chown -R www-data:www-data .

Volume mounts not working with Bash on Windows

Referring to this Github issue, run the following commands in a terminal:

$ sudo mkdir /c
$ sudo mount --bind /mnt/c /c
$ cd /c/path/to/project
$ docker-compose up -d

Future Work

Adwin Ying's avatar
Adwin Ying

Self-taught full-stack web dev based in Tokyo. Occasionally wrecks servers through self-hosting and homelab-ing.

← Back to all posts