In my recent post, I described how cool is the Free Tier in the Oracle Cloud – we can for example spawn a pretty strong ARM server and use it for free (see Oracle Cloud Free Tier – too good to be true? for more details). While I was using GitHub pages to host my personal website as well as a programming blog for a few years (I have written more about GitHub Pages in this post: Java’s guy journey to GitHub pages), I decided to switch to WordPress hosted in the cloud – and Oracle Cloud was a perfect fit for that.

It is not because I was unhappy with GH pages or something. GitHub Pages is a fantastic option to host a blog, especially together with Jekyll. It is fast, reliable, provides SSL cert, and everything is stored as Git revisions, naturally 🙂 Furthermore, it is for free. WordPress, on the other hand, is much more convenient in terms of writing posts or editing the website. There are tons of useful plugins too. Considering my willingness to play with WordPress and the free hosting in Oracle Cloud, I gave it a try.

The choice is not that easy but no matter what you choose – the most important is to write a programming blog. Programming blogs are not only a source of entertainment for devs but many times – almost a lifesavers:) (or day savers at least:)). So if you have not started your own yet – do not think much, just start writing:) Your solutions can help lots of people and your thoughts be an inspiration.

I started researching the topic with three points in mind:

  • a complete setup should require no more than a few commands (so ideally something dockerized)
  • seamless integration with Let’s Encrypt or other free SSL cert providers – as I had GitHub pages for free, I would like this solution to remain free
  • everything has to work in ARM architecture – in order to be hosted in the ARM-based machine in Oracle Cloud

I came across many different approaches. In this post, I combined a few of them. The best piece of advice I found in this post: Quickly setup WordPress & SSL via Let’s Encrypt and Certbot using Docker Compose | by Carl Willimott | Medium. I have simplified the solution from this post and made it work also in the ARM architecture. Nevertheless, if you are inpatient and want TL;DR…, here it comes 🙂

TL;DR – fire and forget – run a WordPress in 5 mins

Installing Docker

Basically, if you have not installed Docker yet, you can find all instructions here: Install Docker Engine on Ubuntu | Docker Documentation. Or to make it quicker for Ubuntu-like systems, just execute below to install necessary deps, docker + docker-compose and take care of necessary groups as well as systemd.

sudo apt-get update
sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose

sudo groupadd docker
sudo usermod -aG docker ubuntu
newgrp docker

sudo systemctl start docker
sudo systemctl enable docker

WordPress with Docker – a quick way (but without an SSL certificate)

In this approach, we will use docker to run both WordPress database and site itself. Basically, we are going to create a wordpress folder that will hold essential DB and WP files for us. From this folder, we are going to spawn these two containers. You just need to replace the ${db_password} with your own password.

--restart=always flag will keep these containers up if the server restarts.

For brevity, I have used root DB user for WordPress. You can create a separate user instead with MYSQL_USER and MYSQL_PASSWORD variables just for the WordPress. I have used MariaDB instead of MySQL image – it works well in ARM architecture (I have encountered issues with MySQL).

mkdir ~/wordpress && cd ~/wordpress
docker run -e MYSQL_ROOT_PASSWORD=${db_password} -e MYSQL_DATABASE=wordpress --name wordpressdb --restart=always -v "$PWD/database":/var/lib/mysql -d mariadb:latest
docker run -e WORDPRESS_DB_USER=root -e WORDPRESS_DB_PASSWORD=${db_password} --name wordpress --restart=always --link wordpressdb:mysql -p 80:80 -v "$PWD/html":/var/www/html -d wordpress

A better approach with Docker Compose + Certbot for the SSL certificate… and also quick!

While running two containers from a command line is not a big deal, setting up three or more from the command line is not clean anymore. With this in mind, Docker Compose comes in handy. Let’s rewrite the above and add another thing – Certbot which will take care of getting the SSL certificate for our domain. We want of course encrypt the traffic to and from our site and have this “lock” icon in the right place – for obvious reasons. I spent some time trying to find the best approach and as I mentioned – this great post helped a lot: Quickly setup WordPress & SSL via Let’s Encrypt and Certbot using Docker Compose | by Carl Willimott | Medium. We will use the LinuxServer SWAG docker image, which will take care of SSL-related things.

As stated in the Docker Hub for this image:

SWAG – Secure Web Application Gateway (formerly known as letsencrypt, no relation to Let’s Encrypt™) sets up an Nginx webserver and reverse proxy with php support and a built-in certbot client that automates free SSL server certificate generation and renewal processes (Let’s Encrypt and ZeroSSL). It also contains fail2ban for intrusion prevention.

linuxserver/swag – Docker Image | Docker Hub

Let’s rewrite the above Docker commands and add SWAG. Additionally, let’s use wordpress as the hostname for the WordPress container. This will be used to redirect traffic from SWAG to this container.

version: '3.3'
services:

  wordpressdb:
    image: mariadb:latest
    container_name: wordpressdb
    volumes:
      - "./database:/var/lib/mysql"
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: wordpress-db-password
      MYSQL_DATABASE: wordpress

  wordpress:
    image: wordpress:latest
    container_name: wordpress
    hostname: wordpress
    depends_on:
      - wordpressdb
    restart: always
    links:
      - wordpressdb:mysql
    ports:
      - "8080:80"
    volumes:
      - "./html:/var/www/html"
    environment:
      WORDPRESS_DB_USER: root
      WORDPRESS_DB_PASSWORD: wordpress-db-password

  swag:
    image: linuxserver/swag
    container_name: swag
    depends_on:
      - wordpress
    restart: always
    volumes:
      - ./config:/config
      - ./default:/config/nginx/site-confs/default
    environment:
      - EMAIL=youremail@something.com
      - URL=yoursite.com
      - SUBDOMAINS=www
      - VALIDATION=http
      - TZ=Europe/London
      - PUID=500
      - PGID=500
    ports:
      - "443:443"
      - "80:80"

Basically, the idea is to start the Docker container on 8080 instead of 80 (HTTP), SWAG on 80 (HTTP), and 443 (HTTPS). SWAG will receive the traffic, take care of the SSL-stuff and forward it further to WordPress by its hostname.

In the volumes for SWAG, we also see a default config:

 volumes:
      - ./config:/config
      - ./default:/config/nginx/site-confs/default

Below there is this additional configuration we need to provide. It will redirect HTTP traffic to HTTPS and eventually hit our WP container:

server {
	listen 80;
	listen [::]:80;
	server_name _;
	return 301 https://$host$request_uri;
}

server {
	listen 443 ssl http2 default_server;
	listen [::]:443 ssl http2 default_server;

	root /var/www/html/example;
	index index.html index.htm index.php;

	server_name _;

	include /config/nginx/proxy-confs/*.subfolder.conf;

	include /config/nginx/ssl.conf;

	client_max_body_size 0;

	location / {
		try_files $uri $uri/ /index.php?$args @app;
	}

	location @app {
		proxy_pass http://wordpress;
		proxy_set_header Host $host;
        	proxy_redirect off;
        	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        	proxy_set_header X-Forwarded-Proto https;
        	proxy_set_header X-Real-IP $remote_addr;
	}
}

SWAG will use Certbot to validate whether we own the domain and generate certificates for us. Of course, before we can successfully prove that, we need to update the DNS records of our domain to point the host machine. We can get the IP address of the machine from the cloud and update A records, i.e. like that:

SubdomainValueRecord type
@ip of the hostA
wwwip of the hostA

It will take a while to propagate these changes – we can go to i.e. https://dnschecker.org and verify whether DNS entries are updated. Still, a cached value can remain on our ISP DNS, so patience is required in this matter.

Of course, do not copy the above pieces of code – just clone dawidkotarba/docker-wordpress-mariadb-swag-ssl (github.com), replace EMAIL=youremail@something.com and URL=www.yoursite.com in the docker-compose.yml file and run docker-compose up.

Configuring UFW (Uncomplicated Firewall)

It is good to ensure we open only necessary ports, i.e. SSH, HTTP, and HTTPS in the machine. For this purpose, we can configure UFW with just a few commands. I have described it briefly somewhere in the middle of post: Oracle Cloud Free Tier – too good to be true?. Overall, we may consider allowing traffic to:

  • TCP port 22 (SSH)
  • TCP ports 80, 443 (HTTP, HTTPS)
  • TCP ports 465, 587 (SMTP)
  • TCP ports 110, 995 (POP3)
  • TCP ports 143, 993 (IMAP)

WordPress in the cloud

I was playing with WordPress in the cloud and moved that from one machine to another. What I realized is that WordPress ties the IP address of the machine to the configuration. I was not able to access it from the new machine. To fix issues like that, connect to the database container:

docker exec -it wordpressdb bash
mysql -u root -p
use wordpress;

Once you are connected, check what IP or domain is used:

SELECT * FROM wp_options WHERE option_name IN('siteurl','home');

If this is incorrect, update siteurl and home:

UPDATE wp_options SET option_value='http://your-ip-or-domain' WHERE option_name = 'siteurl';
UPDATE wp_options SET option_value='http://your-ip-or-domain' WHERE option_name = 'home';

To prevent issues like that, reserve an IP in the cloud and assign it to the new machine. You will not need to update DNS entries in this case too.

Final thoughts

If you have reached this point, I guess it is already more spent than 5 mins to set up a blog in the cloud 🙂 Nevertheless, I believe the overall setup still should not take much time. Docker is a fantastic tool allowing us to set up things in a matter of seconds. I was using WordPress for a while and it is great too, especially thanks to lots of useful plugins. Combined together with a cool Free Tier like this one (Oracle Cloud Free Tier – too good to be true?) they give a superb experience even compared to the GitHub Pages. It is definitely worth having a blog – even to play with things around from time to time 🙂


0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published.