Dockerize PHP app with Apache on HTTPS

Pankaj Patel
time2hack
Published in
6 min readJan 10, 2023

Recently I came across an application built with the LAMP (Linux, Apache, MySQL and PHP) stack.

And this application was critical enough, and one must not mess with it to break anything or increase maintenance.

But there was one problem, we had to use XAMPP to enable development on this application. XAMPP is a great tool that has served PHP developers for a long time.

And in modern software development, where we talk about shipping the whole platform with IaaC/IaC (Infrastructure as a Code), this development workflow could be more convenient and help the developer’s productivity.

After learning a few things about Docker and its benefits and building dockerized CI/CD process, I set myself to bring Docker to this application and help future devs with easy onboarding and development.

First, let’s take an example PHP application which shows today’s date and greets the visitor nicely.

<!-- File: index.php -->
<?php
echo date('l jS \of F Y h:i:s A');
echo '<br />';
echo 'Welcome stranger!';

We must prepare the docker container for the above PHP file via the Docker file. We could use the Docker compose and use the base image directly, but as we need to enable a few plugins and install a few packages, it is better to prepare our custom image and then use it.

The base image we will use for our PHP application is the official one from php with apache prepackaged: php:7.3-apache.

As we want to use apache to serve our PHP application, we will need to prepare some configuration for apache. We can use the .conf extension for apache config files. For the initial setup, it should look like this:

# File: .docker/apache/vhost.conf 
LoadModule headers_module /usr/lib/apache2/modules/mod_headers.so

<VirtualHost *:80>
ServerName localhost
DocumentRoot /app

<Directory "/app">
Options Indexes FollowSymLinks Includes execCGI
AllowOverride All
Require all granted
allow from all
</Directory>

ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

And now, the Dockerfile to build the image:

FROM php:7.3-apache

RUN mkdir /app

COPY .docker/apache/vhost.conf /etc/apache2/sites-available/000-default.conf

WORKDIR /app

RUN chown -R www-data:www-data /app && a2enmod rewrite

With the above Dockerfile, we need to prepare a docker-compose file to ease the service bootstrapping.

version: "3"

services:
my-app:
build:
context: .
dockerfile: .docker/Dockerfile
image: my-app
environment:
- "APACHE_LOG_DIR:/app"
ports:
- 80:80
volumes:
- .:/app

With the above simple Docker compose file, we can run the following command and start our container.

docker-compose up -d --build

And you can see the command to run for a while if you run it for the first time, but subsequent runs will fast to start the container.

Editor screenshot when building Docker

Docker’s screenshot with successful execution

You can visit `http://localhost` to see if the app is working fine or not.

Chrome screenshot with URL http://localhost/

HTTPS

Most internet websites are moving to secure client and server connections. Even Search Engines penalise websites for not serving content with HTTPS.

It is crucial to develop the applications with a secure protocol. For most web applications, HTTPS is handled by the web server. Apache, in our case of the post.

Let’s add support for HTTPS on put PHP+Apache application.

First, we need to tell Apache via Virtual Hosts to serve the website on Secure port, i.e. on 443.

We would do so with the following entry on the config file we created earlier.

LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so

# ... VirtualHost Entry for HTTP port

<VirtualHost _default_:443>

DocumentRoot "/app"
ServerName localhost:443
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

SSLEngine on

SSLCertificateFile "/etc/apache2/sites-available/ssl/localhost.crt"
SSLCertificateKeyFile "/etc/apache2/sites-available/ssl/localhost.key"

<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>

<Directory "/app">
Options Indexes FollowSymLinks Includes execCGI
AllowOverride All
Require all granted
</Directory>

</VirtualHost>

A few notable things we did above are:

  • Load the SSL Module
  • Turn the SSL Engine On for the matching server address
  • Provide the SSL Certificate and Key files
  • Set some additional options for SSL when serving specific files

With the above config, we need to do two more things:

  • Install and Enable the SSL module for the Apache web server
  • Generate Certificate and Key files to use with the `localhost` and mount them on the right path

Installing and Enabling SSL Module

Apache installation comes bundled with the SSL Module. And all we have to do is to enable this module for the Apache server.

On a server where the Apache server is running, we need to trigger the following command to enable the SSL module:

a2enmod ssl

But for our docker setup, we need to do this while building the container. And for that, we will need to adjust the last line of the `**Dockerfile**` as:

- RUN chown -R www-data:www-data /app && a2enmod rewrite
+ RUN chown -R www-data:www-data /app && a2enmod rewrite ssl

Generating Certificate files

We don’t have any high-level signing authority. We can use the OpenSSL CLI tool to generate the SSL Certificate files. This should work for local development purposes.

The following command will generate the certificate files:

openssl req -x509 -out localhost.crt -keyout localhost.key \
-newkey rsa:2048 -nodes -sha256 \
-subj '/CN=localhost' -extensions EXT -config <( \
printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")

After generating these files, we should place them in the following location relative to the root of the project:

<project-root>/.docker/apache/localhost.crt
<project-root>/.docker/apache/localhost.key

After placing the files in the right place, we need to create the following placeholder directory on the server build.

/etc/apache2/sites-available/ssl

So, to create this directory, we will add the following line to our Dockerfile before COPY commands.

...

RUN mkdir /etc/apache2/sites-available/ssl

...

After everything, this is how our `Dockerfile` should look like this:

FROM php:7.3-apache

RUN mkdir /app

RUN mkdir /etc/apache2/sites-available/ssl

COPY .docker/apache/vhost.conf /etc/apache2/sites-available/000-default.conf

WORKDIR /app

RUN chown -R www-data:www-data /app && a2enmod rewrite ssl

Providing Certificates to Apache

As the Server building is in place, we will now look at the server execution, i.e. executing the Dockerfile and providing it at that time.

As we created the docker-compose file earlier; we will now mount what we created in the Dockerfile (/etc/apache2/sites-available/ssl)

...
volumes:
- ./.docker/apache:/etc/apache2/sites-available/ssl
- .:/app

With the above change, our docker-compose.yml file should look like this:

version: "3"

services:
my-app:
build:
context: .
dockerfile: .docker/Dockerfile
image: my-app
environment:
- "APACHE_LOG_DIR:/app"
ports:
- 80:80
- 443:443
volumes:
- ./.docker/apache:/etc/apache2/sites-available/ssl
- .:/app

With all the setup done, we will execute the following command again:

docker-compose up -d — build

Now, if we check in Docker Dashboard:

And when we try to go to the application with https: https://localhost, it will show the warning.

It is normal as the certificates are locally generated. You can go to the Advanced section of the warning and proceed to the site anyway

🖥️ Checkout the source code at: https://github.com/time2hack/php-apache-https

Conclusion

We looked at a basic server on PHP and Apache and then Dockerized it to enable HTTPS.

Further steps we can go for are

- Use signed certificates, like those from LetsEncrypt
- Use other services like MySQL etc., in the above stack

Have you managed to enable HTTPS for your local development?

What is your stack and process to enable HTTPS?

Let me know through comments 💬 or on Twitter at @heypankaj_ and/or @time2hack

If you find this article helpful, share it with others 🗣

Subscribe to the blog to receive new posts right in your inbox.

Originally published on time2hack.com on 21 Dec 2022

--

--