Containerize a WordPress Site and Host at AWS EC2

Containerize and Host WordPress Site at AWS EC2 with Docker and Nginx

Abstract

Let us discuss the prerequisite first before jumping in. I am assuming that you are already having a domain/sub-domain registered that is pointed to an up and running Ubuntu Server IP address. If you don’t – then please follow the previous tutorial to configure and launch an Ubuntu AWS EC2 instance (free for up to 1 year). This EC2 instance will be good enough for a small-scale application or a website like a blog or a portfolio site. You may also check out the complete tutorial series to get the end-to-end hands-on experience – where we will walk you through all the steps from configuring and launching an AWS EC2 instance – to hosting a containerized website and securing it with an SSL certification.

Table of Contents

Let us discuss the prerequisite first before jumping in. I am assuming that you are already having a domain/sub-domain registered, and an Ubuntu EC2 instance up and running. If you don’t, then please follow the previous tutorial to configure and launch an Ubuntu EC2 instance (free for up to 1 year). This EC2 instance will be good enough for a small-scale application or a website like a blog or a portfolio site. You may also check out the complete tutorial series to get the end-to-end hands-on experience, where we will walk you through all the steps from configuring and launching an AWS EC2 instance, to hosting a containerized website and securing it with an SSL certification.

Now let’s look at the system architecture first to containerize the WordPress site and serve it with Nginx. In this architecture, there are two main components: the docker-compose and the global Nginx. The doctor-compose is the configuration of 3 components: the MySQL database for WordPress site, the WordPress site itself, and the local Nginx server. This configuration is also useful for deploying multiple websites in the same hosting platform (EC2 instance in our case). The global Nginx manages all the websites that we will be hosted in this instance. It will proxy the port 80 to a unique word 8060 for this particular website. Thus, whenever this particular website is required to connect to the internet, it will have its unique port. And, that way we will maintain a unique port for every website, so that we can serve multiple websites from different ports. That’s it. The browser will search by the domain name(of the website) and it will connect to the Global Nginx server first, and then it will proxy the port 80 to the Unique port of that website(which is 8060) and the WordPress site will be loaded. That is the idea.

Install Dependencies

We will install the dependencies (nginx and docker) first in the EC2 instance.

Syntax Highlighting

#Install dependencies
#!/bin/bash
#-----------Update and Upgarde OS-----------
sudo apt update
sudo apt upgrade

#-----------Install nginx-----------
sudo apt install nginx
#check version
nginx --v

#-----------Install Docker-----------

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the key repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update

#Install the Docker packages:
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo apt install docker-compose
#check version
docker --version
docker-compose --version
Configure Docker

Now we will prepare the docker configuration files to have a containerized WordPress site. We may start by creating a project repository in the default home directory. The name of the repo could be as per the project name. We used the website name here as an example. There are 3 items inside the repo. The docker-compose.yml file is for setting the configuration of docker – connecting the WP site to the DB and Local Nginx Server. The .env file contains the environment variables for <wordpress> and <db> services. And Finally the nginx.conf file contains the configuration of the <webserver> service.

Syntax Highlighting

#!/bin/bash
# File configuration to containerize the WordPress site
# Home directory
# ├── service.analyticalman.com
# │   ├── docker-compose.yml
# │   ├── .env
# │   └── nginx-conf
# │       └── nginx.conf

#-----------Prepare Docker config file-----------
mkdir service.analyticalman.com # create a project repo to store all files. We used use the website name.
cd service.analyticalman.com 
nano docker-compose.yml #Put the config for docker-compose.yml here

#-----------Prepare .env file (for WP and DB services)-----------
nano .env #put the config for .env here

#-----------Prepare nginx.conf file (for local nginx webserver service)-----------
mkdir nginx-conf
cd nginx-conf
nano nginx.conf #Put the config for nginx.conf here

The docker-compose.yml file sets the configuration of 3 containerized services: the WP site with DB and the Local Nginx. We selected the latest images available for every service. Both the <wordpress> and <db> services get the environment variables from .env file. These environment variables are used in the <Environment> section of the <wordpress> service. The corresponding files for these services are stored in their respective volumes (dbdata and wordpress). The volume: wordpress is used by two services: <wordpress> and <webserver>. The configuration of the service: <webserver> is saved at the nginx.conf file inside the nginx-conf volume. This service serves the wordpress files to the port 8060 which is forwarded to the internet port 80.

Syntax Highlighting

#service.analyticalman.com/docker-compose.yml
version: '3'

services:
  db:
    image: mysql:8.3
    container_name: sam_db
    restart: unless-stopped
    env_file: .env
    environment:
      - MYSQL_DATABASE=wordpress
    volumes:
      - dbdata:/var/lib/mysql
    command: '--default-authentication-plugin=mysql_native_password'
    networks:
      - app-network

  wordpress:
    depends_on:
      - db
    image: wordpress:php8.3-fpm-alpine
    container_name: sam_wordpress
    restart: unless-stopped
    env_file: .env
    environment:
      - WORDPRESS_DB_HOST=db:3306
      - WORDPRESS_DB_USER=$MYSQL_USER
      - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
      - WORDPRESS_DB_NAME=wordpress
    volumes:
      - wordpress:/var/www/html
    networks:
      - app-network

  webserver:
    depends_on:
      - wordpress
    image: nginx:1.25.4-alpine
    container_name: service.analyticalman.com
    restart: unless-stopped
    ports:
      - "8060:80"
    volumes:
      - wordpress:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
    networks:
      - app-network

volumes:
  wordpress:
  dbdata:

networks:
  app-network:
    driver: bridge
    

The .env file contains the environment variables (User ID and Passwords for the Database).

Syntax Highlighting

#service.analyticalman.com/.env
MYSQL_ROOT_PASSWORD=PD
MYSQL_USER=SAM
MYSQL_PASSWORD=PD
    

The configuration of the <webserver> service is set up in nginx.conf file. It mainly listens to the internet port 80. If the server-name is matched – it will serve the index file(homepage) of the website.  Overall the configuration is designed to efficiently serve the website content while ensuring security and performance optimizations for static assets.

Syntax Highlighting

#service.analyticalman.com/nginx-conf/nginx.conf
server {
 listen 80;
        listen [::]:80;

        server_name service.analyticalman.com;

        index index.php index.html index.htm;

        root /var/www/html;

        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }

        location / {
                try_files $uri $uri/ /index.php$is_args$args;
        }

        location ~ \.php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass wordpress:9000;
                fastcgi_index index.php;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_path_info;
        }

        location ~ /\.ht {
                deny all;
        }

        location = /favicon.ico {
                log_not_found off; access_log off;
        }
        location = /robots.txt {
                log_not_found off; access_log off; allow all;
        }
        location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
                expires max;
                log_not_found off;
        }
}
    

Now we may execute the docker-compose once all configuration files are prepared. It will download the required images to build the containers and set up the necessary connections.

Syntax Highlighting

#!/bin/bash
#-----Run docker-compose to build the containers -----
sudo docker compose up -d
# check if all containers are up and running
sudo docker ps 
    
Configure Global Nginx

Now at first we need to point the name server for our domain/sub-domain to the EC2 instance IP address. We can have a domain/sun-domain from a preferred domain service provider. I have registered my domain from Namecheap. So I will be creating a subdomain there now and point it towards this IP address of the EC2 instance. If you use a domain, make sure to add both of the following DNS records set up for your server.

 

  • A record with yoursite.com pointing to your server’s public IP address.
  • A record with www.yoursite.com pointing to your server’s public IP address.

Now when the name server (Website Domain/sub-domain name) is pointed towards the ec2 instance IP address, we can write that URL in the web browser and it will load the nginx homepage. It is because we already installed nginx with the default configuration in the Ubuntu EC2 instance. This global nginx is not yet configured, and therefore, it is not yet connecting us to the WordPress site that we just containerized and launched.

Now to configure the global nginx, the configuration file has to be available in two nginx directories: site-available and site-enabled. we will first put the configuration file in the site-available directory (The file name here is the same as the website name). Then we will create a soft-link between site-available and site-enabled. Finally, we will test if the local nginx configuration (nginx.conf) runs successfully. Once tested, we will restart nginx.

Syntax Highlighting

#!/bin/bash
#-----------Configure the global nginx-----------

# configure site-available
cd /etc/nginx/sites-available
nano service.analyticalman.com #Put the config for site-available here

# configure site-enabled
cd /etc/nginx/sites-enabled
# create a soft-link between site-available and site-enabled
sudo ln -s /etc/nginx/sites-available/service.analyticalman.com .

# Return back to site-available
cd /etc/nginx/sites-available
# Check if the configuration of nginx.conf is ok
sudo nginx -t #will show a success message
#restart nginx
sudo systemctl restart nginx 
    

The configuration file for the global nginx (service.analyticalman.com) sets it to read the website name by listening to port 80 (HTTP port) and proxy this is port 8060 (local nginx port for the website) if the website name is matched to the target name (service.analyticalman.com).

 
Syntax Highlighting

#/etc/nginx/sites-available/service.analyticalman.com
server {

    server_name service.analyticalman.com;
    listen [::]:80;
    listen 80;
    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://127.0.0.1:8060$request_uri;
    }
}

Once the global nginx is configured and re-started, we can now write that web URL in the browser and it will load the WordPress site as we expected. 

Now the containerized website is served with nginx successfully. This way, we can now serve multiple websites from the same EC2 instance. 

Leave a Comment

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