Use Docker to Provision Multiple WordPress Websites on a Single Server

by | May 13, 2024 | Software Development | 1 comment

This post will demonstrate using docker to provision two stand-alone SSL-secured WordPress websites with different root domains on a single server.  Both websites are backed by individual Oracle MySQL databases and phpMyAdmin interfaces.

After being inspired by a HTTPS/SSL security article created by my colleague Anastasios Kentominas, I started tinkering with the idea of creating a website to serve my own blog. After exploring the many different blog hosting frameworks, I settled on WordPress. Though there are many options out there for pay-to-host WordPress sites, I wanted to experiment and see if I could get a secure WordPress website hosted from my home network.

Obviously, there are network security risks in this. I plan to share how I set the networking up to (hopefully) protect my home network while also not paying monthly hosting fees for a low-traffic website. I will create a future blog post describing that part of the project.

I also wanted to hone my Docker skills. I had set up LAMP servers before, but this time, I wanted to see if I could get everything deployed on a Mac OS server, with each LAMP service running in its own container. There are many examples on the internet on how to achieve this, but I had some additional requirements. I wanted to be able to serve multiple independent websites from the same server simultaneously. I also wanted access to a stand-alone phpMyAdmin UI for each website I was hosting.

Prerequisites

To get started, you will need a server that has access to the internet and has a functioning version of Docker installed.

Server Hardware

The setup described below should be compatible with any modern MacOS, Windows, or Linux operating system. I’ve tested it on MacOS 14.3, Ubuntu 22, Ubuntu 20, Oracle Linux 8, and Oracle Linux 9. I haven’t tested it on Windows, which is why I am saying it “should” work. If anyone does end up testing it on Windows, please do let me know your results in the comments.

That being said, this configuration should run on any server hardware that can run Docker and that the container base images are compatible with.

To check your server architecture against the images used in this demo, take a look at the supported architecture sections in the documentation included with the official images below:

Docker images used in this demo

If you need help getting this part of the prerequisites completed, let me know in the comments, and I’ll create a blog post on how to get docker installed and your DNS settings configured.

Networking

The server needs to have port 80 and 443 open, and be reachable via two different URLs (not bare IP addresses), from the public internet.

For people who are trying this at home, I would suggest using the excellent Firewalla Purple Firewall/Router to both secure your network and provide you with a dynamically updating DNS URL that points to your home network. You can also use software-based solutions like the no-ip free Dynamic DNS service.

DNS and Domains

Both websites created in this demo will accept traffic directed to www.<yourdomain>. In addition, one website will be configured to listen on site1.<yourdomain>, and the other website will be configured to listen on site2.<your_other_domain>. These subdomain names (www, site1, site2) are arbitrary and can be configured to suit your needs.

You will need to access your domain DNS settings and create a CNAME record that points those subdomains to the URL of your server.

If you need help with configuring your domain to point to your server URL, please let me know in the comments below and I will create a blog post on that subject.

Downloading the repository

It is important that you download the entire dual-wordpress-phpmyadmin-docker git repository, as several files need to be in place before starting the containers. I suggest you clone the dual-wordpress-phpmyadmin-docker repo from Github, but you can also download a zip file of the configuration if you are not set up with a Githb account.

Website Security

A .env file must be created in the root level of the dual-wordpress-phpmyadmin-docker directory to configure the database and website credentials. This file is where the WordPress website and database credentials are stored. To get started, copy this example to your local filesystem:

This example .env file should be changed to utilize unique and secure usernames and passwords before you create any containers. The .gitignore file in the repository directory is also set to ignore this .env file. This should help prevent you from inadvertently pushing your actual passwords to a Git repository.

Customizing the Website Domain Names

Though site1 and site2 are perfectly good for subdomain names 🙃, the default dual-wordpress-phpmyadmin-docker configuration files point to the domain1.com and domain2.net domains. Before deploying your WordPress websites, you must change the compose.yml file and the nginx configuration files to refer to the domains you configured in the DNS and Domains section above.

Note: You have the option to simplify your setup by using a single domain for both WordPress websites. This means you can create two separate WordPress websites using the same single domain. If you choose this single-domain solution, you will need to rename one of the phpmyadmin subdomains.

Updating the compse.yml file to point to your domain(s)

  • Update the environment and volume arrays in the swag service section, changing every reference to domain1.com and domain2.net to your domain(s). Update the email address and time zone as well.
  • I suggest you set the STAGING variable to true until you are sure you have the correct configuration. If you try too many times with an invalid config, you will get blocked from creating certificates for a period of time.

Note: When STAGING is set to true, you will NOT get valid SSL certificates, but you can ensure that all the DNS and Firewall settings configured above are correctly resolving and allowing traffic.

Pointing the SWAG NGINX Reverse Proxy to your domains

You may have noticed that when you updated the volumes section in the compose.yml file to point to your domains, you were actually changing the expected names of the nginx configuration files that will be shared from your local docker host to the container filesystems.

Before starting the containers, it is imperative that you also rename the files on your docker host to match what the compose.yml file is expecting to find. Specifically, the 4 files listed below will need to be renamed to match the domain names you will be using:

phpmyadmin.domain1.com.phpmyadmin.subdomain.conf
phpmyadmin.domain2.net.phpmyadmin.subdomain.conf
site1.domain1.com.wordpress.subdomain.conf
site2.domain2.net.wordpress.subdomain.conf

These files contain references to the domain names that were part of their original filename. The server_name attribute in each file must be updated to point to domain indicated by their new filename.

Setting WordPress Application Defaults

WordPress utilizes the php.ini file to set application defaults, such as the maximum size for uploaded files or post content. These defaults are often too conservative for modern website assets. To adjust these and any other configuration values supported by the php.ini file, you can modify the values in the php.ini files within the wordpress-site1 and/or wordpress-site2 directories.

A more detailed article describing the php.ini file can be found here.

Creating the WordPress Websites

After setting up your DNS records, server firewall, website/database credentials, and the SWAG configuration files, you are ready to create the websites. docker compose handles the creation of the required containers and inter-container networking. After ensuring your shell is open to the top-level of the dual-wordpress-phpmyadmin-docker directory, execute the following command:

docker compose up --detach

The —-detach argument tells docker to run the containers in the background. This prevents you from accidentally stopping the containers by closing the shell or otherwise ending the process. Take a look at the command line execution and expected output below.

[+] Running 8/8
 ✔ Network dual-wordpress-phpmyadmin-docker_default  Created                                    0.0s
 ✔ Container database-site1                          Started                                    0.0s
 ✔ Container database-site2                          Started                                    0.0s
 ✔ Container swag                                    Started                                    0.0s
 ✔ Container wordpress-site2                         Started                                    0.1s
 ✔ Container phpmyadmin-site2                        Started                                    0.1s
 ✔ Container phpmyadmin-site1                        Started                                    0.1s
 ✔ Container wordpress-site1                         Started                                    0.1s

Viewing the Container Log Output

When executing docker compose with the --detach argument, you will not see the log output of the containers. To view a live output of the container logs, execute the docker compose logs command with the --follow argument.

Take a look at a sample of the container logs below:

docker compose logs --follow

phpmyadmin-site2  | [Mon May 13 06:04:46.551172 2024] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.57 (Debian) PHP/8.2.16 configured -- resuming normal operations
phpmyadmin-site2  | [Mon May 13 06:04:46.551701 2024] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND'
wordpress-site1   | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.27.0.8. Set the 'ServerName' directive globally to suppress this message
wordpress-site1   | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.27.0.8. Set the 'ServerName' directive globally to suppress this message
wordpress-site1   | [Mon May 13 06:04:46.469542 2024] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.57 (Debian) PHP/8.2.18 configured -- resuming normal operations
wordpress-site1   | [Mon May 13 06:04:46.469695 2024] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND'
database-site1    | 2024-05-13 00:04:46-06:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
database-site1    | 2024-05-13T06:04:46.802088Z 0 [System] [MY-015015] [Server] MySQL Server - start.
database-site1    | 2024-05-13T06:04:47.023450Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.3.0) starting as process 1
database-site2    | '/var/lib/mysql/mysql.sock' -> '/var/run/mysqld/mysqld.sock'
database-site2    | 2024-05-13T06:04:46.722533Z 0 [System] [MY-015015] [Server] MySQL Server - start.
database-site2    | 2024-05-13T06:04:46.958134Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.3.0) starting as process 1

Testing and Troubleshooting

Connecting directly to WordPress and phpMyAdmin

There might be times when you want to test the WordPress or phpMyAdmin containers/services without going through the nginx reverse proxy (Swag container/service). For example, during troubleshooting, maybe you aren’t sure if the port forwarding on your router/firewall is malfunctioning or if there is a problem with the WordPress site itself. 

To help determine the source of a problem, its useful to be able to reach out directly to either of the WordPress sites and/or the phpMyAdmin dashboards. The docker compose configuration is set to allow this via local port forwarding. 

  • wordpress-site1 – port 33080
  • wordpress-site2 – port 33081
  • phpmyadmin-site1 – port 8080
  • phpmyadmin-site2 – port 8081

Deleting the container volumes

If you have made significant changes to your container configurations, for example, changing the name of the website domains or database credentials, it is sometimes best to delete the container volumes and start from a clean slate. Be advised that this will delete ALL the databases referenced by the containers and all the configuration settings changed inside the WordPress website.

Use the docker volume rm command to remove each of the container volumes.

❯ docker volume rm dual-wordpress-phpmyadmin-docker_database-site1-data 
❯ docker volume rm dual-wordpress-phpmyadmin-docker_database-site2-data 
❯ docker volume rm dual-wordpress-phpmyadmin-docker_wordpress-site1-data
❯ docker volume rm dual-wordpress-phpmyadmin-docker_wordpress-site2-data

Credit where credit is due

The hard work of many others made this post possible. I’d like to thank the developers at linuxserver.io, and phpMyAdmin, the Oracle MySQL Team, and the maintainers of the official WordPress docker images.

 

Further Reading

  •  SWAG User Documentation – very detailed information on how to further configure SSL and nginx reverse proxies within the SWAG container.

1 Comment

  1. nobody

    nice writeup!

    Reply

Submit a Comment

Your email address will not be published. Required fields are marked *

Seth Lippman

Striving to make the world a better place through technology.

Fixing VS Code SSH Connection Failures

I use the Firewalla Purple Firewall/Router. I am unsure if that has anything to do with this issue I often face, but I figured I'd mention it. Also, I'd like to mention that the Firewilla Purple is a fantastic device that I highly recommend. I also use VS Code as my...

Snowfall over Gorgon City

This was the second time Stacia and I have seen Gorgon City, and the second time we've seen them at Red Rocks. Immediatly after our first Gorgon City show, which featured an appearnce by Drama, we knew we'd come back any time Gorgon City played at Red Rocks.  What we...

Breakfast, Breakbeats, Birthdays, and Tim Burton

I was reading through online posts celebrating my partner Stacia's birthday when a social media platform notified me that it was also my friend Simon's birthday. Simon is someone I used to go to Drum & Bass shows with in London whilst attending university there in...