Dockerizing a PHP Application on Apache, MySQL

  • Hosting
  • 12 Min Read

Dockerizing a PHP Application on Apache, MySQL

This article tells you how to make a Docker version of a PHP app that talks to a MySQL database and runs on an Apache server.

If you want to learn more about Dockerizing and Containerizing applications, read this.


We are going to go through a very simple situation to try to figure out what choices we have, especially when it comes to connecting to a database.

We’re using the Crane Cloud platform to run the database as a different service for this app. Crane Cloud not only lets you quickly launch containerized workloads, but it also lets you make any database you want with just one click. Their cutting-edge infrastructure hosts the database, and they give you the information you need to join.

This was done with a single PHP script. Here’s a piece from that script. It is assumed that you already have a database and its passwords, either on your computer or somewhere else.

PHP Script

// credentials
$dsn = 'mysql:host=' . $host . ';port='. $port .';dbname=' . $dbname;
// connection
$pdo = new PDO($dsn, $user, $password);
// GET
$stmt = $pdo->query('SELECT * FROM products');

Setting Up Environment Variables

Setting up environment variables to store my passwords is helpful for two main reasons, though there may be more:

  • So they don’t get exposed by chance. When you reveal the secrets or include them with the picture, you are exposed.
  • To quickly add a different database. By giving the env vars different values, you can launch a second copy of the same image or app that uses a different database.

Setting variables in the environment relies on the situation. You could export them locally in /etc/apache2/envvars, or whatever your machine’s path is, as <VAR_NAME>=<value>. On the other hand, all directories will be able to see those variables, and name conflicts are sure to happen. You could also make an .htaccess file and use the command SetEnv <VAR_NAME> <value> to set the variables. Like an .env file, the second one sets them up in Apache for this project or path. It’s scalable and works great locally.

As soon as you go remote or containerize, you need to make changes. You don’t want to reveal the secrets, so you tell Docker to ignore the .htaccess file. There may be times when the programme needs other instructions in the file, such as redirects or rewrites. Well, that’s easy: just get rid of the secret environment variables when you make the picture. Though useful, it’s important to remember that I don’t think the .htaccess file is the best way to give secrets to a programme.

You should be able to add the variables during runtime when the container is first created. Make a .env file that Docker ignores and pass it to the --env-file flag of docker run from the command line. The secrets will be available to the container after this because they are added to its runtime environment widely. I think you can guess some of the issues this could cause, like name clashes.

I like using the short form PWD for password a lot. So I used the name PWD as the database password one time in my .env file. The app sent back a db auth error because, when I looked at the container and printed all the environment variables, I saw that PWD was set to the working path instead of my password. The password had been erased! That makes sense. In my code and env file, I changed the var name to PASSWORD. Then I rebuilt the picture, and everything worked fine. When you name your environment variables, you should watch out for these kinds of name clashes. I suggest that you add a unique string to the beginning of your environment variables, like your app name: <APP_NAME>_<VAR_NAME>. I think there might be a better way to namespace, but I haven’t found it yet.

Crane Cloud and a lot of other platforms let you add environment variables easily by giving you form fields when you publish. As with the command line, env vars sent through the form don’t matter in Crane Cloud and can be changed. For now, use the prepend hack until I come up with a better way to do it.

The Dockerfile

Take a look at the Dockerfile. I would like to use the same steps to set up PHP and Apache on my own computer. However, as you may have seen, the next process can be made better. Let’s not change it for now.

FROM php:7.1.23-apache
COPY . /var/www/html
RUN echo "ServerName localhost:80" >> /etc/apache2/apache2.conf
RUN docker-php-ext-install pdo_mysql
CMD ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

The local PHP version I used for coding helped me choose the base image. This picture also comes with the web server, Apache. Learn how the picture you chose is put together. I might have been able to pick a better one.

This picture is based on Debian. That is, apt is the package manager in case you need to install any other packages, and /var/www/html is the usual DocumentRoot for Apache. That’s why I copy files to that spot; if you don’t, you’ll get this message when you run the container: “No index file found in the DocumentRoot.”

Copy and WORKDIR

Let’s talk about copy and WORKDIR. The first command sets the working directory for all tasks that follow, and the second command copies files to a directory that is close to the working directory. These two can be used in interesting ways, according to me:

  • It wouldn’t change anything if we set WORKDIR to /var/www/html and copied. That means that even the COPY command now points to /var/www/html, and the present directory (.) also points to that directory file. I’m sorry if that was hard to understand, but I hope you get it.
  • For some reason, though, I always leave out the WORKDIR directive, especially if I’m not going to be running commands that depend on certain folders later on. For example, in Node.js, I would set the WORKDIR to where the package.json file is stored. This way, when I run yarn install or yarn start, they will be in the right place with the right files.

You tell Apache what the ServerName is on Line 4, so it doesn’t have to guess. Things may still work, like they do for me, but you get this message: “Could not reliably determine the server’s fully qualified domain name…”

I set the port to listen on 80 while I was doing that, but I didn’t use an EXPOSE command. Even so, port 80 is still the default. You know that line 4 is already there.

The next step is to install the MySQL driver for PDO, which doesn’t come with this image. At first, I tried to install it with apt, but it wasn’t in the repository! There is a helper tool called docker-php-ext-install that makes installing PHP extensions easier.

Next, we set the usual command, which is run when the container is created. That’s where the Apache binary is, but we don’t need to use the absolute path because we can find the location in the PATH environment variable (look at the picture). Instead, we can just call the binary apache2ctl.

Some people might ask, “Why not just start the server with CMD ["apache2ctl", "start"]?” It was my first try, so I know what you mean. But it turned out to be more difficult than that. In Docker, you need to change a few things about the code for it to work. “That simple command puts the Apache process in detached mode, but Docker only works when the main process is alive. The solution is to put Apache in the foreground,” it seems. That’s why the order was twisted.

If you understood everything above, this is how the real Dockerfile should look (or even better, based on your setup).

FROM php:7.1.23-apache
COPY . /var/www/html
RUN docker-php-ext-install pdo_mysql
CMD ["apache2ctl", "-D", "FOREGROUND"]

Okay, now you can build and release the app. Will be here to hear any thoughts.


Q: Can Docker run PHP?

A: Yes. Docker makes it easy for PHP to run, so coders can use its platform-independent and isolated environment to run PHP apps quickly.

Q: How big is Docker PHP?

A: The original size of a Docker PHP image can change based on the dependencies and extensions that are included. Some optimization methods, such as multi-stage builds, reducing dependencies that aren’t needed, and compressing image layers, can greatly shrink the size of a Docker PHP image. Based on your needs, you can get a smaller footprint and make the picture size work best for you.