How to setup a LEMP Wordpress site using Docker

How to setup a LEMP Wordpress site using Docker

Note: It is recommended to visit my previous articles about Docker and Bash Scripting for a better understanding of this task.

Agenda

In this article, we are going to see how we can easily set up a full fledge LEMP (Linux, Apache, Mysql, PHP) stack WordPress site in a local machine or browser.

It will be a LEMP stack each one running in separate docker containers.

We are going to write a Bash shell script to make the task easy for the end user, the user will be able to run the LEMP stack with just one command.

This bash script will contain the following features,

  1. Check if Docker and Docker-compose are installed on your machine or not. If not installed, install it.

  2. Create a /etc/hosts entry for 'site_name' (provided as argument) pointing to localhost.

  3. Create required files (eg. Docker-compose.yml, Nginx/default.conf, public/index.php, etc)

  4. Create a WordPress site using the latest WordPress Version. This will be a LEMP (Linux, Nginx, Mysql, PHP) stack running inside Docker containers.

  5. perform subcommands to, stop/start, deleting containers.

Writing The Script

creating files

To create the script firstly, we have to create a file with any name for ex, script.sh (it should have .sh extention to be able to get executed as bash script)

touch script.sh

# use any editor of your choice
nano script.sh

Checking Docker

After opening the file, let's start witing our script. Initially, we have to check if docker and docker-compose are installed or not and if not present, install them.

#!/bin/bash

# Check if docker-compose is installed
if ! [ -x "$(command -v docker-compose)" ]; then
  # Install docker-compose if not present
  echo "Installing Docker Compose..."
  apt-get update
  apt-get install -y docker-compose
fi

This snippet of code will check for the above condition, here first we have to write #!/bin/bash.

Making Entry in /etc/hosts

To make an entry of the site name in host file, firstly we have to check if the site name is provided as an argument or not. if not, make the user do it.

# Check if site name argument was provided
if [ -z "$1" ]; then
  echo "Please provide a site name as an argument."
  exit 1
fi
# Entry in /etc/hosts
site_name="$1"
echo "127.0.0.1:8000 $site_name" >> /etc/hosts

the above code snippet will perform the check and entry in the hosts' file.

Creating files

There are some required files for this LEMP stack to be operable and live, to create this either you can do it manually or just add it to the script and it will do it for you. I am going to add these to my script but you can do what you wish.

#creating required files
mkdir wordpress-docker
cd wordpress-docker
# Creating public and nginx
echo "Creating nginx configuration file"
mkdir public nginx
cd nginx
nano default.conf
  • Nginx config file

events {}
http{
    server {
        listen 80;
        server_name $host;
        root /usr/share/nginx/html;
        index  index.php index.html index.html;
        location / {
            try_files $uri $uri/ /index.php?$is_args$args;
        }
        location ~ \.php$ {
            # try_files $uri =404;
            # fastcgi_pass unix:/run/php-fpm/www.sock;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass phpfpm:9000;
            fastcgi_index   index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
    }
}
  • PHP file

This PHP file just provides info about the page, you can add your own PHP file here if you want.

<?php
phpinfo();
  • Docker Compose file

This is the quite long Docker-compose file that I have written it took me around 1 hour to do so but in the end, it is worth it.

services:
  #databse
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
      MYSQL_ROOT_PASSWORD: password
    networks:
      - wpsite
  #php
  phpfpm:
    image: php:fpm
    depends_on:
      - db
    ports:
      - '9000:9000'
    volumes: ['./public:/usr/share/nginx/html']
    networks:
      - wpsite
  #phpmyadmin
  phpmyadmin:
    depends_on:
      - db
    image: phpmyadmin/phpmyadmin
    restart: always
    ports:
      - '8080:80'
    environment:
      PMA_HOST: db
      MYSQL_ROOT_PASSWORD: password
    networks:
      - wpsite
  #wordpress
  wordpress:
    depends_on: 
      - db
    image: wordpress:latest
    restart: always
    ports:
      - '8000:80'
    volumes: ['./:/var/www/html']
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
    networks:
      - wpsite
  #nginx
  proxy:
    image: nginx:1.17.10
    depends_on:
      - db
      - wordpress
      - phpmyadmin
      - phpfpm
    ports:
      - '8001:80'
    volumes: 
      - ./:/var/www/html
      - ./nginx/default.conf:/etc/nginx/nginx.conf
    networks:
      - wpsite
networks:
  wpsite:
volumes:
  db_data:

This Docker Compose file is for setting up a WordPress website with a MySQL database, PHP, PHPMyAdmin, Nginx, and WordPress itself.

Let's go through it section by section,

  • Services: This is where we define the services that we want to run as part of our application.

  • Database: This service defines the MySQL database that we will be using to store our website data. We specify the version of the MySQL image we want to use, and we also set up some environment variables to create a new database, user, and password. We also set up a volume to store the data in the /var/lib/mysql directory. This allows us to persist our database data even if the container is destroyed. We also set the restart policy to always, which means that if the container crashes or is stopped, it will be automatically restarted.

  • PHP: This service sets up the PHP-FPM container, which is a fast and efficient way to run PHP scripts. We specify the version of the PHP-FPM image we want to use, and we also set up a dependency on the db service, since our PHP scripts will need to connect to the MySQL database. We expose port 9000 so that other services can communicate with PHP-FPM. We also set up a volume to mount our ./public directory into the container so that PHP scripts can be served from there.

  • PHPMyAdmin: This service sets up PHPMyAdmin, which is a web-based database administration tool for MySQL. We specify a dependency on the db service since we need to connect to the MySQL database. We expose port 8080 so that we can access PHPMyAdmin in our web browser. We also set up an environment variable to specify the database host and root password.

  • WordPress: This service sets up the WordPress container, which is where our website will run. We specify a dependency on the db service since WordPress needs to connect to the MySQL database. We expose port 8000 so that we can access our website in our web browser.

  • Nginx: Here is the configuration of nginx server which depends on all the above services. This server runs on 8001 of the local system and 80 of the container.

Adding Sub commands

# Adding subcommands to enbale/disable
if [ "$2" == "enable" ]; then
 docker-compose strat
elif [ "$2" == "disable" ]; then
 docker-compose stop
fi

# Adding subcommands to delete site
if [ "$2" == "delete" ]; then
 docker-compose down -v
 #removing hosts entry
 sed -i "/$site_name/d" /etc/hosts
 #removing all local files
 rm -rf ./
fi

These subcommands will help users to perform other actions easily.

Final Result

The final script will look something like this,

#!/bin/bash

# Check if docker-compose is installed
if ! [ -x "$(command -v docker-compose)" ]; then
  # Install docker-compose if not present
  echo "Installing Docker Compose..."
  apt-get update
  apt-get install -y docker-compose
fi

# Check if site name argument was provided
if [ -z "$1" ]; then
  echo "Please provide a site name as an argument."
  exit 1
fi
# Entry in /etc/hosts
site_name="$1"
echo "127.0.0.1:8000 $site_name" >> /etc/hosts

#creating required files
mkdir wordpress-docker
cd wordpress-docker
# Creating public and nginx
echo "Creating nginx configuration file"
mkdir public nginx
cd nginx
cat > default.conf << EOF
events {}
http{
    server {
        listen 80;
        server_name $host;
        root /usr/share/nginx/html;
        index  index.php index.html index.html;

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

        location ~ \.php$ {
            # try_files $uri =404;
            # fastcgi_pass unix:/run/php-fpm/www.sock;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass phpfpm:9000;
            fastcgi_index   index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
    }
} 
EOF
echo "Done"
echo "Creating index.php file in public"
cd ..
cd public
cat > index.php << EOF
<?php
phpinfo();
EOF
echo "Done"
cd ..
echo "Creating docker-compose file ..."
cat > docker-compose.yml << EOF 
version: '3'

services:
  #databse
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
      MYSQL_ROOT_PASSWORD: password
    networks:
      - wpsite
  #php-fpm
  phpfpm:
    image: php:fpm
    depends_on:
      - db
    ports:
      - '9000:9000'
    volumes: ['./public:/usr/share/nginx/html']
    networks:
      - wpsite
  #phpmyadmin
  phpmyadmin:
    depends_on:
      - db
    image: phpmyadmin/phpmyadmin
    restart: always
    ports:
      - '8080:80'
    environment:
      PMA_HOST: db
      MYSQL_ROOT_PASSWORD: password
    networks:
      - wpsite
  #wordpress
  wordpress:
    depends_on: 
      - db
    image: wordpress:latest
    restart: always
    ports:
      - '8000:80'
    volumes: ['./:/var/www/html']
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
    networks:
      - wpsite
  #nginx
  proxy:
    image: nginx:1.17.10
    depends_on:
      - db
      - wordpress
      - phpmyadmin
      - phpfpm
    ports:
      - '8001:80'
    volumes: 
      - ./:/var/www/html
      - ./nginx/default.conf:/etc/nginx/nginx.conf
    networks:
      - wpsite
networks:
  wpsite:
volumes:
  db_data:
EOF
echo "Done"
fuser -k 8000/tcp 9000/tcp 8081/tcp 8080/tcp
echo "Creating LEMP stck in docker for wordpress"
docker-compose up -d
echo "Servers created"

# prompting user to open site in browser
echo "Site is up and healthy. Open $site_name in any browser to view it."
echo "Or click on the link -> http://localhost:8000"

# Adding subcommands to enbale/disable
if [ "$2" == "enable" ]; then
 docker-compose strat
elif [ "$2" == "disable" ]; then
 docker-compose stop
fi

# Adding subcommands to delete site
if [ "$2" == "delete" ]; then
 docker-compose down -v
 #removing hosts entry
 sed -i "/$site_name/d" /etc/hosts
 #removing all local files
 rm -rf ./
fi

How to run this script

To run any script, we have to make it executable first. We can do this by running the following command in terminal

chmod +x script.sh

After making the script executable, we can run the script for the above operations to perform using the following command,

Note: It is MANDATORY to run the script as an administrator or superuser

sudo ./script.sh SITE_NAME

Note: Here you have to provide the site name as an argument.

By running the script, it will perform the above-mentioned task. It creates 5 different docker containers running the LEMP stack (with phpmyadmin). If everything goes well, you can visit your WordPress site by opening it in the browser using either site_name or Localhost.

Running Subcommands

There are some sub-commands available in the script to perform operations like stopping, starting/restarting, and deleting the containers. To run the sub-commands you need to add it to the main command as an argument for example,

  1. To start/Restart the containers,
./script.sh SITE_NAME enable
  1. To Stop the containers,
./script.sh SITE_NAME disable
  1. To Delete the containers,
./script.sh SITE_NAME delete

Installing WordPress at localhost

After opening the site in a browser, you can see the WordPress installation page with a form requiring your information and login credentials. Fill out the form and create the credentials and note them down for further use. Click on the Install WordPress button at the bottom. Log in with your credentials and you are ready with your WordPress site.

Error handling

The script is already written so that there will be no room for error while execution but, we can't guarantee that the error will not occur. In case any error occurs, you can look at the logs of all the containers and get rid of the error course manually by running,

docker-compose logs CONTAINER_NAME

There are more chances that the error can occur due to the ports on which we are running our stack being already in use. Although I have added a command in the script that makes the required ports free, there can be some services which can restart at the same port. To avoid this error, check for the services running on the following ports 8000, 8080, 9000, 3306, and 8081. If you found any service on one of these ports please try to stop them or you can change the script according to your required ports.

Conclusion & Results

With the above bash script, we can create a sample WordPress site running inside Docker containers. the script itself creates the required files and folders like docker-compose.yml, nginx config files, etc.

Did you find this article valuable?

Support Gaus Mohiuddin Ahmed by becoming a sponsor. Any amount is appreciated!