Dockerized WordPress Architecture

For the currently running WordPress site, I envisioned and applied the architecture below.

All the components in the rectangle are Docker containers. NGINX container accepts HTTP(S) requests routes them to the WordPress container listening on port 80 only within Docker internal network. I could have exposed WordPress container listening to port 80 but it wasn’t going to be able to handle HTTPS requests, so I decided to have NGINX container to handle SSL cert.

version: "3.9"
    
services:
  db:
    image: mysql:5.7
    volumes:
      - ./db_data:/var/lib/mysql 
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: hogehogehogehoge
      MYSQL_DATABASE: mydatabase
      MYSQL_USER: buhibuhi
      MYSQL_PASSWORD: foobar
    networks:
      proxynet:
  phpmyadmin:
    depends_on:
      - db
    image: phpmyadmin/phpmyadmin
    restart: always
    ports:
      - '8080:80'
    environment:
      PMA_HOST: db
      MYSQL_ROOT_PASSWORD: hogehogehogehoge
    networks:
      proxynet:
  wordpress:
    image: wordpress:latest
    container_name: wordpress
    depends_on:
      - db
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: buhibuhi
      WORDPRESS_DB_PASSWORD: foobar
      WORDPRESS_DB_NAME: mydatabase
      WORDPRESS_DEBUG: 'true'
    volumes:
      - ./html:/var/www/html
      - ./wp-content:/var/www/html/wp-content
    networks:
      proxynet:
  reverse:
    image: nginx:latest
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/conf.d:/etc/nginx/conf.d
    ports:
      - "80:80"
      - "443:443"
    restart: always
    networks:
      proxynet:
volumes:
  db_data: {}
  wordpress: {}
networks:
  proxynet:

Paste the whole YAML to docker-compose.yaml file. There is some more steps to do before you can spin up the containers.

Let’s set up a firewall on the OS side. Ubuntu utilizes ufw. Execute the command to install it.

sudo apt install ufw

Let’s open a few necessary ports…

sudo ufw allow 80 # for NGINX http request redirect handling
sudo ufw allow 443 # for SSL (HTTPS) requests
sudo ufw allow 8080 # for myPHPAdmin access
sudo ufw allow 22 # for SSH access

You will need to get the firewall to start when the OS starts, so execute the following command.

sudo ufw enable

In the directory you created docker-compose.yaml file, create nginx/nginx.conf file with the following configuration.

user  nginx;
worker_processes  2;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
   worker_connections  1024;
   use epoll;
   accept_mutex off;
}

http {
   include       /etc/nginx/mime.types;
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

   default_type  application/octet-stream;

   log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                     '$status $body_bytes_sent "$http_referer" '
                     '"$http_user_agent" "$http_x_forwarded_for"';

   access_log  /var/log/nginx/access.log  main;

   sendfile        on;
   #tcp_nopush     on;

   keepalive_timeout  65;

   client_max_body_size 300m;
   client_body_buffer_size 128k;

   gzip  on;
   gzip_http_version 1.0;
   gzip_comp_level 6;
   gzip_min_length 0;
   gzip_buffers 16 8k;
   gzip_proxied any;
   gzip_types text/plain text/css text/xml text/javascript application/xml application/xml+rss application/javascript application/json;
   gzip_disable "MSIE [1-6]\.";
   gzip_vary on;

   server {
       listen       80 default_server;
       listen       [::]:80 default_server;
       server_name  _;
       sendfile        on;
       #tcp_nopush     on;

       keepalive_timeout  65;

      client_max_body_size 300m;
      client_body_buffer_size 128k;

      gzip  on;
      gzip_http_version 1.0;
      gzip_comp_level 6;
      gzip_min_length 0;
      gzip_buffers 16 8k;
      gzip_proxied any;
      gzip_types text/plain text/css text/xml text/javascript application/xml application/xml+rss application/javascript application/json;
      gzip_disable "MSIE [1-6]\.";
      gzip_vary on;
      return 301 https://hayato-iriumi.net$request_uri;
   }
    include /etc/nginx/conf.d/*.conf;
}


You need another configuration file at nginx/conf.d/ssl.conf. As you could see, this file handles HTTPS requests with the certificate and routes the requests to the wordpress container listening to the port 80 within the Docker network. So the port 80 is not exposed to the host side.

upstream wordpress_upstream {
    server wordpress:80;
}

server {
    server_name hayato-iriumi.net;
    listen 443 ssl;
    ssl_certificate /etc/nginx/conf.d/ssl/certificate.crt;
    ssl_certificate_key /etc/nginx/conf.d/ssl/private.key;

    location / {
        proxy_set_header        Host $host:$server_port;
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto $scheme;
        resolver 127.0.0.11;
        proxy_redirect http:// https://;
        proxy_pass http://wordpress_upstream;
        # Required for new HTTP-based CLI
        proxy_http_version 1.1;
        proxy_request_buffering off;
        proxy_buffering off; # Required for HTTP-based CLI to work over SSL
    }
}


Obviously, you need to have your SSL cert ready. I have blogged about a free SSL solution here, so please refer to it for your SSL cert. Place the SSL cert files at the specified location in the ssl.conf file.

Finally, if you go back to the directory where you created docker-compose.yaml file, and execute docker-compose up -d, you will be able to see the WordPress initial UI.

If you run through the WordPress installation process, you are half way there. Once you run through the installation process, it creates necessary tables in the MySQL database. Once you have the basic data structure automatically created, you need to export the data from the following tables and import them in the new database. It is easy to export and import data using myPHPAdmin UI even if you don’t know anything about SQL. (Knowing basics of SQL helps when there are problems in importing data)

  • wp_posts
  • wp_terms
  • wp_termsrelationships
  • wp_comments
  • wp_commentmeta
  • wp_postmeta
  • wp_term_taxonomy

Your WordPress site still lacks the images. Backup all files under wp_contents/uploads and restore it at the wp_contents/uploads directory of docker-compose.yaml location. And what I had to do was to run the following command to be able to upload files from WordPress UI.

sudo chmod 777 wp_contents

This may be too open. I may tighten it up a little more later.

I put too much information alraedy in this blog post. I will write about Dockerizing WordPress more as I have some time.

Moved to OCI’s Free Tier

I decided to move to OCI’s free tier because the charge was going up faster than I expected. If you see this blog post, it’s on the free tier host.

I had already gone through the migration process so it wasn’t too hard to do it but still it was some work.

I was starting to get charged 67 cents a day. My ads on this blog doesn’t earn that much, so I was starting to be negative on the budget.

So far, since June 3 to June 17, only $3.13 but you can see the cost was starting to get higher in the last few days. The Shape of the host was VM.Stardard.E3.Flex with 2 AMD CPUs and 2GB of memory. $3.13 is nothing for the technical knowledge I gained migrating my WordPress site to my own Cloud but I wanted to make it economical to make it more sustainable. I thought 1 AMD CPU and 1GB of memory would do, so I moved everything to the free tier host. For more details about OCI’s free resources, click here.

As far as I know, it is possible for anyone who has OCI account to have a free tier host in us-ashburn-1 region in Availability Domain 3 with an AMD CPU. I saw a free tier host with ARM CPU just recently but I will wait and see if docker-compose releases bits for ARM processor. I don’t know if they will charge me for anything, but we will see. Once DNS propagates to this new host, I am going to terminate the old host.

Edit: I was just digging the OCI’s UI and found the combination is free. Just need to have docker-compose for ARM processor…

Mistake

As I was trying to migrate this blog over to a free tier host on OCI, I accidentally ssh’ed into the existing host and removed the wp-content directory, which removed all the images for this site. I am trying to restore it now.

Restoring MySQL Database for WordPress

Backing up is one thing and restoring the data is another important piece to migrate a WordPress site. Restoring the data and being able to access from the Dockerized WordPress was not easy for me.

I restored the entire database and I tried to access it from the newly Dockerized WordPress instance but it would not work. It raise page took too long to load type of error. What I resorted to do is to restore database as blog_bak and exported data from several tables and then imported them to the new database and the site worked. I will write about it in detail.

Open your backed up database file (.sql) in a texteditor and change the following part where it tries to create the database with the original database name to blog_bak so that the database name does not conflict with the new one instance. If you don’t see CREATE DATABASE statement at the beginning of the file, you may have to re-export your database with custom export with Add CREATE DATABASE option.

--
-- Database: `hayato_iriumi_db`
--
CREATE DATABASE IF NOT EXISTS `blog_bak` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci;
USE `blog_bak`;

Zip the database backup to [youfilename].sql.zip to get ready to be restored on the target MySQL Server. It is much faster to restore database using a zipped file.

Navigate to Import tab on the target MySQL Server and upload the zip file to restore your old database to blog_bak database.

This will restore your blog database to blog_bak. I am going to stop here because we haven’t gone over spinning up the new MySQL Server with Docker yet. You can get ready up to the point where editing the exported SQL file.

Will continue on this topic later because there is a lot more to cover.

Backing up Database for WordPress

To migrate your WordPress site to your own host on Cloud, you have to backup your database. Backing up database is a very simple process as long as your host has phpMyAdmin to manage your database.

  • Login to your phpMyAdmin.
  • Select the database for your WordPress blog.
  • Click Export and then simply click Go.
  • After a while, you get the entire database as a SQL file. That’s it for backing the data up!

Configuring Ubuntu for Containerized Blog Engine

There are some prerequisites on the OS to support Dockerized WordPress.

  • Update OS (packges) with sudo apt-get update && sudo apt-get upgrade
  • Install Docker Engine by executing the following commands.
sudo apt-get install apt-transport-https 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=amd64 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
sudo usermod -aG docker $USER
  • Install docker-compose by executing the following commands.
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
  • Install VIM by executing sudo apt-get install vim (This is necessary to edit config files while working on configurations.
  • Install net-tools by executing sudo apt-get install net-tools. This is not a must but it helps to check if your host is listening to the expected ports. sudo netstat -tulp is useful to check the listening ports.
  • Install ufw to manage firewall on Ubuntu. sudo apt-get install ufw
  • Open ports 80, 443 and 8080 by executing the following command. Adding 22 for ssh access.
    sudo ufw allow 80 && sudo ufw allow 443 && sudo ufw allow 8080 && sudo ufw allow 22 && sudo ufw enable

That’s pretty much what you need to get ready your own Dockerized self hosted WordPress on your own Cloud.


Deploying an Ubuntu VM on OCI

Thought you could provision VMs manually using the OCI’s UI, I prefer using Terraform to manage it because you can manage infrastructure as code and provisioning and destroying are easily repeatable.

Here is Terraform code I used to provision my Ubuntu VM.

resource oci_core_instance ashburn-blog {
    compartment_id      = var.tenancy_ocid
    display_name        = "blog-host"
    availability_domain = "Gumm:US-ASHBURN-AD-1"
    shape               = "VM.Standard.E3.Flex"

    shape_config {
        memory_in_gbs = 2
        ocpus = 2
    }
    
    source_details {
        source_type = "image"
        source_id   = var.config["image_ocid"]
        boot_volume_size_in_gbs = 80
    }
    create_vnic_details {
        assign_public_ip = "true"
        hostname_label   = "blog-host"
        subnet_id        = var.config["public_subnet_ocid"]
    }
    metadata = {
        ssh_authorized_keys = file(var.public_ssh_key)
    }
}

Some values are defined in variables.tf file as variables as you can imagine. You may have to get the correct OCID for var.config[‘image_ocid’] by executing OCI CLI command. I have blogged how to list available images before, please refer to this blog post to list images and their OCID.

Once that’s ready, all you have to do is to run terraform apply to provision the VM as the way you want. (I didn’t realize I had 2 CPUs in the VM though I said I had 1 CPU for this blog. htop shows I have 2 CPUs.)

What Consists of This Blog

Like I said in my previous blog, I have moved my blog from iPage.com to Oracle Cloud Infrastructure. There were 2 motivation to move my blog to my own Cloud. One was because iPage.com was very slow. It’s a cheap service so I was getting what I was paying for. My second motivation was I wanted to Dockerize everything just because I was very curious about what I could do with the technical knowledge I learned over the years. So, what consists of this blog?

  • A Ubuntu VM
  • VM.Standard.E3.Flex Shape
  • 1 CPU with 2GB Memory
  • Docker CE
  • MySQL Container
  • phpMyAdmin Container
  • WordPress Container
  • NGINX Container as a Reverse Proxy
  • All Containers are in docker-compose YAML file
  • Internet Gateway
  • Security List to accept traffic from the Internet to the VM.
  • Change to A Record in iPage.com’s DNS to the public IP address.

I have used Terraform to provision the VM so that I can consistently reprovision the same VM over and over.

I will write about the details more in the coming weeks as I organize what I have done to accomplish the migration.

Moved to OCI

I have completely migrated my blog from iPage.com to Oracle Cloud Infrastructure. There were some struggles over the last few weeks but I have learned how to solve the problems.

I will start to blog about the struggles I had to migrate my blog. One thing that I have accomplished is Dockerize WordPress + NGINX. This is something I have always wanted to do. My struggles paid off and my blog runs so much faster. It runs on a VM with 1 CPU with 2 GB memory on AMD. I will see how much it costs and will talk about it here.

My site runs on Ubuntu. I was able to clean up some mess that accumulated over the years in the database too.

Will blog again.