Can’t “Contain” My Words – WordPress Site, Containerized

Hey there! Hope you could enjoy the last post and maybe take away a thing or two. Or maybe even three… ๐Ÿ™ƒ Setting up reliable networking is an incredibly difficult task, but thankfully, OpenWrt got me through the process fairly well. Docker setup took a while as well, but at the end, things were running fairly smoothly.

If you’re reading through this – you probably are ๐Ÿคช, I want to say thank you for supporting this site! This is not the first time making a website, I’ve done it quite a few times, actually. But this time, I have a feeling that this site is going to become something big… I’ve never produced so much content for the other sites I’ve run in the past!

And those of you that are somewhat experienced in this field may instantly notice that this is a WordPress site. If you didn’t notice, just scroll to the bottom. Have a look at the footer. ๐Ÿ™ƒ And in case you don’t know what WordPress is – don’t worry, we’ll get to it.

A screenshot of the footer, containing the text "Designed with WordPress"
Here’s the footer so you don’t have to scroll. ๐Ÿ™ƒ

WordPress is kind of the software that motivated me to make a website in the first place. Three or maybe even two years ago, I probably only had a quarter of the expertise and knowledge I have about web services today. Back then, I thought that making a website myself was super hard. Using other pre-built solutions (“website toolkits”, stuff like wix.com) were also an option… if you have some good content to share. But to be honest, I didn’t have much content to share in the past, so if I were to make a website, I was going to make it a technical challenge and maybe learn about things I haven’t touched before. And also, you’re talking to a tech nerd – you don’t offer them anything that’s click-and-run! ๐Ÿ˜Ž

Well, WordPress is no different than those “website toolkits” – at least that’s something I would say if I didn’t go down the rabbit hole. There are services that host WordPress for you, making it an incredibly easy-to-use website toolkit, but WordPress itself is open-source and you can self-host it as well anywhere you like. To this day, I’m still not sure whether self-hosting motivated me to make a website or the other way around. But either way, I’m glad I have my own website running now – even if the daily visitor count is below 50, and 40 of them are probably bots… Make sure to share my posts if you like them. ๐Ÿ˜

Alright, so what is WordPress? Well, it’s complicated – if you search up WordPress on any search engine, the top search result is probably WordPress.com, the “official” WordPress hosting service. It’s run by the same people that started the open-source project WordPress (referred to as WordPress.org further below), which is the actual software we’re talking about. Let’s first talk about the WordPress project.

WordPress is referred to as a Content Management System (CMS). You could make a website with just static HTML, CSS and maybe JavaScript, but the only thing your website could do then is to show some static content. Every time you attempt to update anything, you’d have to change everything by hand – everything, by hand. And this is not how the internet works, and computers are invented to avoid these issues in the first place! ๐Ÿ˜” (very sad CrunchyCrunch)

So the path WordPress and many other CMS go down is to implement a fair amount of server-side backend logic. WordPress is built with PHP – a pretty popular language used for backends in web applications. Instead of serving static HTML files, the so-called PHP pre-processor runs a PHP file that’s associated with the path and returns the generated content, most of the times HTML, to the client. The client won’t see what the server has exactly done, but only the response that’s generated by the PHP scripts. Of course the web server has to be set up to co-operate with a PHP pre-processor and all that stuff, but that’s not the main focus in this post… Should I write another post all about that, just maybe? ๐Ÿค”

Example interaction of client, server, web server software and PHP preprocessor

This is an oversimplified model of how websites work nowadays. A good amount of sites don’t use PHP, of course, but the important takeaway here is that the server can and should have some code logic to generate a response that is returned to the client. The web nowadays won’t run with just static files… ๐Ÿ˜›

And with server-side logic, all sorts of possibilities open up. Let’s take an example you encounter every day: If there was no server-side logic, the concepts of “accounts” and “login” wouldn’t exist at all, since the account information are all stored somewhere in a database (or a CSV or even Excel file if you’re crazy ๐Ÿคจ). And there would be absolutely zero user interaction anywhere – imagine social media, but without likes, commenting, view counts – or even social media itself, since you wouldn’t be able to upload or publish anything at all if no logic on the server side handles this.

To be honest, I’m not quite sure how I should explain this whole server-client concept to anyone, but I hope you get the idea. Server-side logic is important. ๐Ÿคช

And WordPress makes use of server-side logic basically everywhere. Static files are still important – stuff like media files, CSS and JavaScript files shouldn’t be generated the whole time when they don’t change a lot (if at all). But for everything else, being able to interact with the server and not just receiving static files upon requests is very, very desirable.

To visitors, WordPress “renders” the webpages according to the way the administrators have configured it to, i.e. responds with the pages it generates. And to administrators and authors, WordPress offers a WebUI for them to design the appearance of the site, manage pages and posts, manage plugins to extend functionalities and change some settings that affect the behavior of the site – for example, to discourage search engines from indexing a site.

You’re probably very confused right now… Think of it as a ticket shop: All a normal visitor does is getting a ticket from one of the windows, but the administration can decide what tickets to sell or how they want their ticket-selling windows to look like, with decorations or whatever. The administration may have another back entrance, but that doesn’t change the physical location of the ticket shop. ๐Ÿ™ƒ

Let me give you a couple of screenshots of the management UI in WordPress… If you’re a user that logged in without a WordPress.com account, you can probably access a similar page (under /wp-admin). The WordPress UI probably only exposes your user settings though, since you probably are not an administrator.

I really hope you aren’t… (please don’t hack me ๐Ÿ˜ฅ)

WordPress management UI dashboard page
WordPress management UI posts page
WordPress management UI post editing page
WordPress management UI dsign and appearance interface

One question remains open – that is, what’s the thing with WordPress.com? Well, the relation is not that complicated, but still confusing for a lot of people… Including me. ๐Ÿ™ƒ In fact, when writing this post, I found an official book that tells the story of WordPress. Maybe it’s worth reading some time, but here’s what I’ve heard over the time…

Some of the core founders of the WordPress project went and founded a company – Automattic. And the main service they offer is WordPress.com, and you can think of it as the “official” hosting provider for WordPress sites. Sites hosted on WordPress.com are somewhat different than regular WordPress sites, because those are configured to make use of some of the cloud services from Automattic by default. Out of the box, WordPress.com sites are configured with functionalities like email newsletters or anti-spam for comments, they get access to a few official WordPress.com themes and have a different management interface than regular WordPress sites. Funny thing, when writing this post, I just found a list with features that are configured by default, exclusively on WordPress.com.

For those (me…) that also want some of the cloud-based features on their WordPress.org can use the Jetpack plugin made by Automattic. It backports some features from WordPress.com like email newsletters or commenting anti-spam (Akismet). The plugin itself should be open-source with its source code in this GitHub repository, but it doesn’t change the fact that a lot of features are pay-to-use, especially those that leverage APIs from Automattic. But luckily a portion of the basic features can be used free of charge, so I simply stuck with Jetpack for now. ๐Ÿคช Since this plugin is directly maintained by Automattic, it should be pretty well supported on any WordPress installation.

This plugin can be installed as well on sites that are hosted with other hosting providers like Bluehost, Strato, Hostinger or whatever… I never dug into those, so I’m just going to assume those offer pre-installed WordPress.org sites so you don’t have to go through the pain of setting up things yourself.

Self-hosting is painful. Run… /j

A diagram showing the differences between 3rd party hosting, self-hosting and WordPress.com hosting

And just as mentioned before, I self-hosted WordPress many, many times. And there are a handful of tutorials just to do that. The tutorial that I remember following was this one, but the steps are practically the same. You start by installing a Linux distribution…

“But there’s Windows Server!”

Nope, go install a Linux distribution. ๐Ÿซฅ

Then, use the package manager of that distribution to install all services you need. Apache, PHP, MySQL – all the usual procedure. And don’t forget Redis for object cache.

Then, get the PHP files from WordPress.org, put them in /var/www/html, and then you’re pretty much good to go. The only thing left is to install Certbot to get SSL certificates automatically, but that can be done in a one-liner command, and there’s no maintenance required afterwards since the script will handle everything from verification to auto-renewal.

That was pretty technical and… well, cool, hehe. ๐Ÿ˜Ž And I’ve done this a few times actually. But that didn’t solve the problem of me having pratically no content to share at all…

And because of having no content, for a long time, I didn’t tinker that much with WordPress and self-hosting in general. But that changed again after we moved into our current apartment and I suddenly had the idea to host a Minecraft server on my PC… I don’t know. ๐Ÿ˜›

I remember that the PC ran with Pop!_OS… yup, I didn’t even bother to set up a server OS. I just took the functional desktop system and enabled SSH… But hey, it’s Ubuntu after all, so a lot of things are practically the same! On there, I just downloaded the Bedrock server executable and ran it as a systemd service. Just like anything else would have run on a traditional server environment. Everything was perfect (if anybody played there at all).

And then I discovered Docker, and I asked myself a question… Why didn’t I learn about it earlier!

That was after I shut down the Minecraft server where nobody played on anyways, but Docker really ignited my passion for self-hosting again. I took the official Docker tutorial and decided to do something with it. And Docker is really, really amazing.

You see, normally you would have each service (web server, database…) run as a systemd (or other init systems back in the day) service, without any isolation on the system. That works very well, and this has always been the way to do stuff ever since servers existed (I think…). But I knew little about Linux and tended to mess up everything that you could ever possibly mess up when trying to configure a service like WordPress. So I had to constantly reinstall the services, but something always felt off after reinstalling anything since the system is now “messed up” in my definition. I know that the server is probably running well, but the feeling that a Linux installation is fresh and clean is now completely gone. And that makes me really sad… ๐Ÿ˜–

And here’s Docker to save the day! Docker is one of the so-called container runtimes (actually, a higher level one), among other options like Podman. Instead of installing millions of packages, you just install the Docker runtime, and all your services run as so-called containers. I’m not going to explain the concept of containerization in detail, that would probably be a good topic for another post… ๐Ÿคช But Docker lets you delete and recreate a container and all its volumes, so you can reset a service that you’ve messed up with a way smaller footprint than reinstalling the whole thing.

The important thing to note here is that containers are not virtual machines – the processes run in containers still share the same kernel and the same resources as the host system, so the overhead can basically be ignored. Docker ensures security by isolating the filesystem layers and the networks of the containers using some Linux kernel features, so if one container happens to get compromised, in the best case, only that container would be affected. Pretty cool, huh? And that was only a fraction of what Docker and containerization can do. Especially cloud environments use Docker or other container runtimes with so-called orchestrators like Kubernetes, and they scale their containerized applications across hundreds of thouands of server instances, often with little manual intervention.

And then of course then I had the idea to install WordPress with Docker. A very good tutorial that covers a lot of things is this one from DigitalOcean. With that, I managed to install WordPress with Docker a few times when I used my Lenovo Ideapad as a server. Over the time, I looked into a lot of sources and updated my configuration with the recommendations listed there. Now it’s a complete mess… but a somewhat secure and performant mess. ๐Ÿ˜› And this site, now running on OpenWrt on my home server, is the result of endless afternoons of exploring and optimizing. Let me get you an insight…

My setup is a bit different, since I have four services running that all need to be accessible from outside, and those should all listen on the standard HTTP/S ports so I can really access them from anywhere, with different hostnames. That’s why I went with an nginx reverse proxy for all my services.

Diagram showing four services proxied behind nginx: Home Assistant, Jellyfin, WordPress and Nextcloud
A very high effort diagram /j

Normally, you start single containers with the docker run command, but this becomes barely manageable if you have more than one service. For server clusters and all that crazy stuff, people use Kubernetes pods, but for most home servers, Docker Compose is enough to manage services where multiple components (For WordPress: PHP, Database, Redis) are involved. It helps managing the lifecycle of containers and other resources a bit easier. nginx has its own Docker Compose service in my setup, so it’s not listed here… More on that in another post maybe. ๐Ÿค”

services:
  wordpress:
    restart: unless-stopped
    hostname: wordpress
    image: wordpress:fpm-alpine
    env_file: .env
    environment:
      - WORDPRESS_DB_HOST=db
      - WORDPRESS_DB_USER=${MYSQL_USER}
      - WORDPRESS_DB_PASSWORD=${MYSQL_PASSWORD}
      - WORDPRESS_DB_NAME=${MYSQL_DATABASE}
    depends_on:
      - db
      - redis
    networks:
      default:
      nginx_containers:
        ipv4_address: 172.18.0.3
    volumes:
      - ./webroot/:/var/www/html/
  db:
    restart: unless-stopped
    image: mysql:8.0
    env_file: 
      - .env
      - .mysql-env
    volumes:
      - ./db_data/:/var/lib/mysql
    command: '--default-authentication-plugin=mysql_native_password'
  redis:
    restart: unless-stopped
    image: redis:alpine

networks:
  nginx_containers:
    external: true

I defined the secrets in the .env and .mysql-env files in the same directory. Specifically, MYSQL_USER, MYSQL_PASSWORD and MYSQL_DATABASE in .env, and MYSQL_ROOT_PASSWORD in .mysql-env.

The cool thing to note here is that I use a bridge network for nginx to access WordPress and Jellyfin. When a Docker bridge network is created, the host gets access to it with a virtual interface, often with the first address in the subnet like 172.18.0.1. Nginx, which gets access to all host interfaces by running in host networking mode, will use that virtual interface to reach WordPress, which gets assigned 172.18.0.3 as a fixed address. Here’s the relevant virtual interface on the host:

$ ip addr show br-114e0487c99b

10: br-114e0487c99b: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:30:79:2c:a5 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-114e0487c99b
       valid_lft forever preferred_lft forever
    inet6 fe80::42:30ff:fe79:2ca5/64 scope link 
       valid_lft forever preferred_lft forever

And with the other services I have running, the situation is a bit different. Home Assistant requires host mode networking to connect with different smart home devices, so nginx can reach the Home Assistant HTTP server on 127.0.0.1:8123. There wasn’t really an easy way to configure bridge networking for Nextcloud AIO, so I just let it listen on 127.0.0.1, although on a different port… Which I don’t remember anymore. I believe it was 19000 though… ๐Ÿคช

Diagram showing the networking related to nginx. WordPress and Jellyfin are connected to nginx using a bridge network, Nextcloud and Home Assistant via localhost

And on the nginx side of things, the configuration for WordPress is a complete mess, since a lot of WordPress plugins require to add snippets to the nginx configuration to function properly… I tried my best to keep it clean, but yeah. ๐Ÿ˜

Not going to show the whole configuration here, but the main things I did was enabling HTTP/3 with QUIC, setting up SSL, turning off auto-indexing, add caching headers to media files and forwarding all requests to PHP if a static file can’t be found.

I set up SSL on the server side with Let’s Encrypt, but don’t worry if you see another issuer when visiting this site. This is because all the traffic is proxied by Cloudflare for security and caching, and they use other certificates when serving sites from their network.

Now, on the WordPress side of things, I have several performance plugins in place. Remember when I mentioned Redis? With the plugin Redis Object Cache, WordPress can make use of a super fast in-memory database to cache queries so it doesn’t have to hit the MySQL database for every query. Since I use Cloudflare, I installed this plugin that helps setting up page caching in a way so that even more pages can be served from Cloudflare’s edge servers directly. Cloudflare’s CDN is… Pretty amazing! You know what I’m about to say – post is on the way… ๐Ÿคช

And lastly, we have the Jetpack plugin of course. But I only use a small portion of features it offers, like Akismet Anti-Spam, WordPress.com login, post likes and email newsletters. Definitely not because all other features are paid… definitely not. ๐Ÿ˜ข

Currently enabled Jetpack features on my site. Akismet and AI being the only ones.
Everything that says “learn more” is a paid feature, basically…

There you have it, my WordPress setup. It’s pretty secure and fast now for a small home server/router running on a Celeron CPU, but it’s probably not going to handle tens of thousands of concurrent visitors. But for a personal blog, this is my dream setup for now… Until I get my hands on a server cluster one day. Hehe… ๐Ÿ˜

Recent Posts


Comments

One response to “Can’t “Contain” My Words – WordPress Site, Containerized”

  1. Your WordPress setup looks great! WordPress is a great website builder, in the past I used other website building tools like Jimdo and Wix but in my Opinion they have way more paid features and don’t support all the features to build a useful blog. Thank you for inspiring me to use WordPress!

Leave a Reply