About Blog Contact
 
 

RSS

Mon, Feb 10, 2020
4 min read

Build Docker images for production

Many people have reached out to me with questions about how I would use the images from this setup in production. After the third question, I thought it would be the best to write a small article about building docker images for production.

The Dockerfile

As a convention, we put the Dockerfile which will be used in production, in the root of the project.

Project structure

We have a conventional project structure (read more) which looks like this:

1- build
2 - # files needed for production build
3- web
4 - # the actual Laravel application
5- Dockerfile

In my opinion, it is a good pattern to nest the actual application in a sub folder of the GIT repository because there are many files which are used for infrastructure related stuff, and the application should not be aware of these files.

Production images != development images

The main difference between the production and the development image is that the production image contains the actual application code. The development image is a image where we will mount the application code into. Another difference, at least at my company, is that the development image is a more generic image. It will be used for any PHP project in development and has optional xdebug, preconfigured msmtp, many enabled php extensions and installed composer for example.

The actual Dockerfile

We are leveraging Docker multi stage builds to get our dependencies installed and built.

PHP dependencies

1FROM composer:latest as step1
2COPY ./web ./app/web
3WORKDIR /app/web
4RUN composer install —-no-dev —-no-scripts —-optimize-autoloader
5 
6# ...

In step1, we will use the composer:latest image. First, we have to copy our actual application into the container. Next, we set the current WORKDIR to /app/web . Now we can run composer install with a few options.

Frontend dependencies

1# ...
2 
3FROM node:lts as step2
4COPY --from=step1 /app/web /app/web
5WORKDIR /app/web
6RUN npm install && npm run prod && rm -rf node_modules
7 
8# ...

For step2, we will use the node:lts image to install and build our frontend dependencies. The next step is to copy the files form the first stage to the second stage. Unfortunately, we have to set the WORKDIR in every stage. To install and build the frontend dependencies and to delete the not used node_modules, we execute npm install && npm run prod && rm -rf node_modules.

Now we have installed and built all our dependencies.

Webserver configuration

As last stage we use the official php:7.4-apache image.

1FROM php:7.4-apache
2COPY --from=step2 /app /app
3COPY build/vhost.conf /etc/apache2/sites-available/000-default.conf
4 
5# ...

We copy the files with all dependencies installed and built from step2 to its final position. Next, we copy a vhost.conf file from our build folder into the containers /etc/apache2/sites-available/ folder with the name 000-default.conf. This file is the configuration file which is loaded by apache by default. It looks like this:

1<VirtualHost *:80>
2 DocumentRoot /app/web/public
3 
4 <Directory "/app/web/public">
5 AllowOverride all
6 Require all granted
7 </Directory>
8 
9 ErrorLog ${APACHE_LOG_DIR}/error.log
10 CustomLog ${APACHE_LOG_DIR}/access.log combined
11</VirtualHost>

Nothing special is going on here. We just need this file to point the web server to the correct document root /app/web/public.

PHP extensions

We need to install a few PHP extensions for the runtime.

1# ...
2 
3# Install needed php extenstions
4RUN docker-php-ext-install -j “$(nproc)” \
5 bcmath \
6 opcache \
7 pdo_mysql
8 
9# ...

Final configuration

As a final configuration, we change the owner of /app to the web server user www-data and activate the apache module rewrite which is needed to enable .htaccess configuration.

1RUN chown -R www-data:www-data /app \
2 && a2enmod rewrite
3 
4WORKDIR /app/web

Only for convenience do we set the WORKDIR to the directory of our application. When we execute a command via Docker in the container we do not have to set the WORKDIR explicitly.

The full Dockerfile

The result at the end should look like this:

1FROM composer:latest as step1
2COPY ./web /app/web
3WORKDIR /app/web
4RUN composer install --quiet --optimize-autoloader —-no-dev
5 
6FROM node:lts as step2
7COPY --from=step1 /app /app
8WORKDIR /app/web
9RUN npm install --no-optional && npm run prod && rm -rf node_modules
10 
11FROM php:7.4-apache
12COPY --from=step2 /app /app
13COPY build/vhost.conf /etc/apache2/sites-available/000-default.conf
14 
15# Install needed php extenstions
16RUN docker-php-ext-install -j "$(nproc)" \
17 bcmath \
18 opcache \
19 pdo_mysql
20 
21RUN chown -R www-data:www-data /app \
22 && a2enmod rewrite
23 
24WORKDIR /app/web

To build and start the image, we need to execute the following commands:

1docker build -t docker-example .
2docker run -p 8000:80 -e APP_KEY=base64:w0T2so9vxBfHWm5q0jQuJHhtQwHnWGdqRsXf2S7KtcE= docker-example:latest

We build the image and add a tag with -t. In our case, docker-example. To execute the image, we use the docker run command. With -p 8000:80, we map the containers port 80 to the port 8000 on our host system. As you can see, we add an APP_KEY as environment variable. Without this, we would get a 500 server error. Now we can open the browser and point it to localhost:8000. Boom, we see the Laravel welcome page.

Thanks for reading my article. If you have questions just drop me a line here.

Legal Notice  |   Privacy  |   RSS  |   © 2025 christlieb.eu