Blog Software

Setting up a WordPress staging system with docker

I wanted a WordPress staging system to play around with new plugins, new WordPress versions, and PHP versions.

The primary goals of my mini-project were:

  • Have a staging system for WordPress available for testing new WordPress versions and Plugins
  • Enhance the Performance of the actual WordPress Blog

How can we do it?

Here is a network diagram of the current system setup.

OK, the ideas were quite clear. Now we need a migration plan. And this looks like that:

  1. Install Raspian on the RPi5 SSD, with the Raspberry Pi imager
  2. Install docker and Portainer on the RPi5, based on the Heise description
  3. Install the following docker containers via „docker compose“
    1. Nginx Reverse Proxy
    2. Maria DB for WordPress Staging 
    3. WordPress Staging
    4. Maria DB for WordPress Production
    5. WordPress Production
    6. (Setting up Home Assistant also as a docker container is not a good idea as we lose the functionality of Add-Ons)
  4. With the current live system I’m doing a regular backup via Updraft, so let’s import these backups into the two WordPress instances
  5. At the DNS provider define the new subdomains and route them also to the Dynamic DNS provider
  6. Configure the NPM (Nginx Proxy Manager) with these domains and define the forwarding to the different instances
    1. Setup SSL via Let’s encrypt 
    2. Add also the local Home Assistant server/Port as the target
  7. Reconfigure the port forwarding of the router to the NPM
  8. Shutdown the old RPi4

So, let’s dive a little bit deeper into the different steps.

Configure the new RPi5 hardware and software

OK, let’s get the first new hardware, which means the new high-performance RPi5.

It took some time to be available on the market and the announced performance numbers looked promising.

As the power supply is quite strong I attached directly a SSD drive to the USB3 port.

So no need anymore for a USB hub!

Installing the latest Rasbian operating system on the SSD was pretty easy using the Raspian Imager.

I also configured the SSH access for it, of course.

So, how are we going further?

Install docker on the RPi5

As I wanted to run a WordPress life system and a WordPress staging system in my local network, I thought it would be a good idea to go with:

  • Docker to run multiple images/containers
  • Nginx Reverse Proxy to manage/route the traffic to/from these containers to the outside world

Install the docker containers

  • Setup a docker network manually
docker network create dockerwp 
  • Create a directory for wp_prod, wp_staging, nginx
  • Define a file for common parameters used as anonymized volumes in the docker containers
  • Create docker-compose.yml for the Nginx Proxy
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: nginx-proxy
    restart: unless-stopped
      # These ports are in format <host-port>:<container-port>
      - '80:80' # Public HTTP Port
      - '443:443' # Public HTTPS Port
      - '81:81' # Admin Web Port
      # Add any other Stream port you want to expose
      # - '21:21' # FTP

    # Uncomment the next line if you uncomment anything in the section
    # environment:
      # Uncomment this if you want to change the location of
      # the SQLite DB file within the container
      # DB_SQLITE_FILE: "/data/database.sqlite"

      # Uncomment this if IPv6 is not enabled on your host
      # DISABLE_IPV6: 'true'

      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt

    external: true
  • Create docker-compose.yml for the MariaDB and the WordPress container (one for the staging and one for the production system)
    image: mariadb:11.3.2
    container_name: prod_db
      MARIADB_DATABASE: "wordpress"
      MARIADB_USER: "wordpress"
      MARIADB_PASSWORD: "secret"
      - 'mysqldb0:/var/lib/mysql'
    restart: always
      -  mysqldb0

    image: wordpress:6.5.3
    container_name: prod_wp
      WORDPRESS_DB_HOST: "mysqldb0"
      WORDPRESS_DB_USER: "wordpress"
      WORDPRESS_DB_NAME: "wordpress"
        define('AUTOMATIC_UPDATER_DISABLED', true);

      - 'wordpress0:/var/www/html/wp-content'
      - '../uploads.ini:/usr/local/etc/php/conf.d/uploads.ini'
    restart: always
      - "8001:80"
      - mysqldb0
      - mysqldb0
      - dockerwp


    internal: true
    external: true

Setup the WordPress content

As I backed up the WordPress blog site with updraft, we are restoring the backup to the new WordPress staging and WordPress production system, respectively.

Configure the Sub-Domains

Go to your DNS provider and configure the new Sub-domains with a CNAME entry.

If you have a dynamic IP address, route the Sub-Domain entries to the same Dynamic DNS entry of your DynDNS Provider.

Configure the NPM

Define the target of the routing for all the Sub-Domains, e.g. the Ports of your WordPress containers.

Let the Sub-Domain for the Home Assistant point to the separate HA RPi3.

Add the SSL certificates from Let’s Encrypt.

Additional configuration for the Home Assistant

We must add the following configuration steps to make the Home Assistant run with an external Sub Domain, based on ademalidurmus.

1.) At the NPM, enter the following entry in the advanced config for the Home Assistant Host.

location / {
        proxy_pass    ;
        proxy_set_header        Host            $host;
        proxy_redirect          http://         https://;
        proxy_set_header        Authorization   $http_authorization;
        proxy_pass_header       Authorization;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection “upgrade”;

In the Home Assistant configuration.yml file add:

    - https://public.domain.tld # my public domain
  use_x_forwarded_for: true
    - # nginx proxy manager internal IP adress

At the „Home Assistant URL“ page enter:

Internet: [External Domain]
Local network: [Internal IP]:8123

Reconfigure the Port forwarding at your router

The Ports 80 for HTTP and 443 for HTTPS must now be reconfigured to forward the traffic from these two ports to the NPM.

Here is the network diagram of the new setup.

WordPress staging system with docker

Let’s now check our goals from the beginning. Did we reach them?

  • Have a staging system for WordPress available for testing new versions and Plugins: Available
  • Enhance Performance of the actual WordPress Blog: Super fast!
Blog Software

Upgrade to PHP 7.0

After the upgrade to php 7.0 version of my WordPress website, users are experiencing a real performance boost!

And all is happening on such small Rapberry Pi!

I assume it’s around two times faster! Very cool.

Upgrade to php 7.0

The upgrade procedure was done based on the detailed step by step description of this site.


The way of working

Matt Mullenweg, the founder of wordpress and automaticc, the company behind wordpress, gives some insights about their way of working.

Blog Software

Blog Domain für WordPress

Ich habe ein paar Änderungen an meiner Blog Domain URL durchgeführt. Ich wollte nämlich, dass der Blog unter einer eigenen Blog Domain erreichbar ist, und das auch, wenn ich den Blog auf einem eigenen Server hoste und dieser nur über eine dynamische IP Adresse angesprochen werden kann.

Dazu muss man an verschiedenen Stellen Änderungen machen. In der Beschreibung gehe ich von folgenden Annahmen aus, welche Komponenten vorhanden sind:

  • URL eines Anbieters für Dynamic IPs
  • Apache WebServer
  • Eigene Domain ist vorhanden und es kann eine eigene SubDomain eingerichtet werden
  • WordPress Blog

Zuerst bereiten wir den Apache WebServer so vor, dass er mehrere VirtualHosts verwalten kann. Dazu gibt es das Kommando „NameVirtualHost *:80“.
Dann muss man für jeden VirtualHost eine eigene Konfiguration erstellen und diese verfügbar machen.
<VirtualHost *:80>
ServerAdmin webmaster@localhost
ServerName blog.mydomain.de
RewriteEngine On
DocumentRoot /var/www
<Directory />
Options FollowSymLinks
AllowOverride None
<Directory /var/www/>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
allow from all
<Directory /var/www/html/>
AllowOverride all
ErrorLog ${APACHE_LOG_DIR}/error.log
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/access.log combined

Nun richtet man sich eine Subdomain bei seinem Domain-Provider ein, z.B. „blog.mydomain.de“. Diese wird dann per CNAME auf die URL des IP-Anbieters weitergeleitet. Meist kann man die „www.mydomain.de“ nicht direkt per CNAME weiterleiten, deswegen habe ich die Subdomain eingerichtet. Damit auch die „www.mydomain.de“ auf die „blog.mydomain.de“ zeigt habe ich dafür auch eine Weiterleitung auf die „blog.mydomain.de“ konfiguriert.

Als letztes muss man bei WordPress noch die URLs anpassen, entweder über die GUI oder in dem File „wp-config.php“.

define('WP_SITEURL', 'http://blog.mydomain.de/wordpress');
define('WP_HOME', 'http://blog.mydomain.de');

Bitte beachten, dass bei meiner WordPress Installation die Files unter /var/www/html liegen, aber die URL der Site erfolgt über „blog.mydomain.de“.

Wenn man vorher eine andere Domain verwendet hat, sollte man noch redirects auf die neue Domain beim Apache anlegen, z.B. mit dem mod_rewrite.

RewriteRule /.* http://www.new-domain.com/ [R]

Blog Software

Caching für WordPress Blog

Nun habe ich mal den W3 Total cache von meinem WordPress blog optimiert. Das heisst, ich habe zuerst ein weiteres Plugin für WordPress installliert, das eine XML Sitemap generiert (Google XML Sitemap). Diese Sitemap wird dann dazu verwendet, dem Cache mitzuteilen, welche Seiten es auf dem Blog/Site gibt, und welche Seiten der Crawler (Prime, PreLoad) aufrufen muss, um sie in den Cache zu bekommen.

Eine weitere Optimierung war, dass der Cache nun nicht mehr auf der Platte liegt, sondern APC (Alternative PHP Cache) verwendet wird. Das soll nach den Recherchen im Internet schneller die Seiten ausliefern.

Und dann habe ich bei den Menüs auf der Blog-Seite die Einträge so geändert, dass man mit einem mobilen Browser die Menüs besser bedienen kann. Die Obermenü-Punkte hatten nämlich eigene Seiten, und damit wurden diese Seiten direkt aufgerufen, und man konnte die einzelnen Untermenüpunkte  nur aufrufen, wenn man schnell genug geklickt hat…

Have fun! 🙂