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 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 the 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 \

curl -fsSL | 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] \
  $(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 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 the WordPress database and the 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 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.

Update as of 23rd Nov 2022: Apparently, it looks that the latest version of SWAG does not work correctly with this setup. linuxserver/swag:1.26.0-ls123 is a way to go.

version: '3.3'

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

    image: wordpress:latest
    container_name: wordpress
    hostname: wordpress
      - wordpressdb
    restart: always
      - wordpressdb:mysql
      - "8080:80"
      - "./html:/var/www/html"
      WORDPRESS_DB_PASSWORD: wordpress-db-password

    image: linuxserver/swag:1.26.0-ls123
    container_name: swag
      - wordpress
    restart: always
      - ./config:/config
      - ./default:/config/nginx/site-confs/default
      - SUBDOMAINS=www
      - VALIDATION=http
      - TZ=Europe/London
      - PUID=500
      - PGID=500
      - "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-related stuff and forward it further to WordPress by its hostname:

Here is what it looks like after running the docker-compose.yml:

CONTAINER ID   IMAGE              COMMAND                  CREATED        STATUS          PORTS                                                                      NAMES
896fc8a07b51   linuxserver/swag   "/init"                  6 months ago   Up 14 minutes>80/tcp, :::80->80/tcp,>443/tcp, :::443->443/tcp   swag
71f3b7e65db3   wordpress:latest   "docker-entrypoint.s…"   7 months ago   Up 14 minutes>80/tcp, :::8080->80/tcp                                      wordpress
8a145c3d2d67   mariadb:latest     "docker-entrypoint.s…"   7 months ago   Up 14 minutes   3306/tcp                                                                   wordpressdb

I use Ubuntu 20.04.4 LTS and the version of SWAG is 1.26.0-ls123 (it was the latest when I was setting things up).

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

      - ./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. 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 (, replace and 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 the 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 🙂


moss · 14/11/2022 at 15:16

Hello, Thanks for your sharing. I got some problem that I can’t make my domain from port 80 to 8080, only see swag home page. I made default file to fit my own domain configuration. But it can’t rediect into 8080 to my wordpress.
Did l lost something for setting ? Please guide me. Thanks for your time.
Btw I read your blog from Taiwan. 🙂

    Dawid Kotarba · 14/11/2022 at 20:39

    Hi, moss!

    1. Is your WordPress running correctly on 8080?
    2. How about redirecting from 443 (Swag) to 8080 (WordPress)?

    Greetings from Poland!

      moss · 15/11/2022 at 03:04

      Yes, I can visit 8080 to setup WordPress done. I checked db records the option_value of home & site url are : .
      I used the it just show up SWAG home page. Do you have any suggestion?
      Thanks for your time.

        Dawid Kotarba · 15/11/2022 at 22:09

        I have added a diagram to this post that will describe things more visually, but basically, your domain should point to HTTP (80) and HTTPS (443) of the host machine.
        SWAG proxy listens on 80 and 443. It will take care of the SSL stuff and forward the traffic to 8080 on which the WordPress container listens. Port 8080 should not be accessible outside of the host machine.
        The home & site URL shall be Also, ensure that DNS records on the domain side are updated properly (the propagation may take a while).

        Matt · 21/11/2022 at 21:39

        Great blog article! Super useful, but sadly I’m also seeing the same issue as “moss”. I can connect to WordPress on port 8080 (when I disable the firewall on my instance). It just seems like Swag isn’t redirecting from 443 to 8080 of the WordPress container. It’s definitely redirecting properly from 80->443 and the DNS is definitely set up correctly.
        Thank you!
        Any ideas? I likely have a newer version of Ubuntu than when the blog was written. Could that be related?

          Dawid Kotarba · 22/11/2022 at 22:12

          Hi Matt, thank you!

          It looks like something has to change because I did not have not experienced issues like that when I was setting up my WordPress. Unfortunately, I cannot do much testing on my own as I will just put this website down (and I do not have any other domain for testing).
          The Ubuntu version I am running is 20.04.4 LTS. Something could also change on the SWAG site. I used this one: 1.26.0-ls123 which can be found here: Maybe using this version, instead of the latest, will also help.

          Matt · 23/11/2022 at 01:19

          Dawid –

          Fantastic, and thanks for the quick reply!

          Using the fixed version of swag did the trick. I changed the line in your docker-compose.yaml to:

          image: linuxserver/swag:1.26.0-ls123

          This is on the latest ubuntu image: Canonical-Ubuntu-22.04-aarch64-2022.11.06-0.

          Keep up the great posts!


          Dawid Kotarba · 23/11/2022 at 22:19

          Thanks, Matt, great to hear this is working right now! I have updated the configuration in the post of the fixed version for SWAG. Cheers!

Leave a Reply

Avatar placeholder

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