Launching a WordPress Website on LAMP Stack Across Different Operating Systems using Anisble Playbook

sreehari s kumar
16 min readFeb 19, 2023

--

Features

Deploying a WordPress website on a LAMP (Linux, Apache, MySQL, PHP) stack can be a complicated and time-consuming process, especially when deploying to servers with different operating systems. However, with an Ansible playbook, you can automate the deployment process and ensure consistency and reliability across all environments.

In this article, we will walk you through the process of creating an Ansible playbook to automate the deployment of a WordPress website on a LAMP stack, regardless of the operating system used on the target servers.

We’ll begin by explaining the LAMP stack components required for a WordPress website and how to set them up. We’ll then introduce Ansible and cover the basics of creating a playbook that can be used across different operating systems.

We’ll dive deeper into the playbook creation process, showing you how to configure Apache and MySQL and how to set up the WordPress files and database. We’ll also cover how to manage environment-specific variables, so you can deploy your WordPress website securely.

Next, we’ll show you how to tailor your Ansible playbook to work with different operating systems, including Linux distributions such as Ubuntu and CentOS. We’ll cover how to manage packages, users, and services on different operating systems, so you can deploy your WordPress website with confidence.

By the end of this article, you will have a clear understanding of how to create an Ansible playbook that automates the deployment of a WordPress website on a LAMP stack, regardless of the operating system used on the target servers. You’ll be able to streamline your deployment process, save time, and ensure consistency and reliability across all environments.

So, whether you’re a developer, a system administrator, or a DevOps engineer, this article is a must-read if you want to simplify your WordPress website deployment process on a LAMP stack across different operating systems.

Requirements:

The lab setup includes 3 servers.
- 1 x ansible-master (running any Linux OS) with ansible installed.
- 2 x ansible-clients (running Amazon-Linux-2 & Ubuntu 20.04 on each)

The master server is running on Amazon-Linux-2.
To install ansible, follow the steps:

sudo amazon-linux-extras install ansible2 -y ; ansible --version

Ansible Hosts/Inventory file

An ansible hosts file is used to define the hosts or servers that Ansible will manage, along with any relevant connection details.

$ cat hosts 

[amazon]
172.31.35.253 ansible_user="ec2-user" ansible_port="22" ansible_ssh_private_key_file="wp.pem"

[ubuntu]
172.31.37.253 ansible_user="ubuntu" ansible_port="22" ansible_ssh_private_key_file="wp.pem"

Explanation:

  • [amazon], creates a group of hosts with the name “amazon”. This means that any servers listed beneath this line will be included in the “amazon” group. ( Any name can be given to a group. )
  • 172.31.35.253, specifies the IP address of a server that is a member of the “amazon” group. Here we use the private IP address of the servers as they are in the same VPC.
  • ansible_user=”ec2-user”, specifies the username that Ansible will use when connecting to the server. In this case, it is “ec2-user”, which is the default username for Amazon Linux instances running on Amazon Web Services (AWS).
  • ansible_port=”22", specifies the SSH port that Ansible will use when connecting to the server. The default SSH port is 22, so this line is not strictly necessary, but it is included for clarity.
  • ansible_ssh_private_key_file=”wp.pem”, specifies the path to the private key file that Ansible will use to authenticate the SSH connection to the server. In this case, the private key file is “wp.pem”, which is assumed to be in the same directory as the hosts' file. This private key file is required to establish a secure SSH connection to the server.
  • [ubuntu], creates another group of hosts with the name “ubuntu”. This means that any servers listed beneath this line will be included in the “ubuntu” group.
  • 172.31.37.253, specifies the IP address of a server that is a member of the “ubuntu” group.
  • ansible_user=”ubuntu”, specifies the username that Ansible will use when connecting to the server. In this case, it is “ubuntu”, which is a common default username for Ubuntu instances running on AWS. Other attributes are the same as that of the amazon group.

group_vars

In Ansible, group_vars is a directory where you can define variables that will be shared across all hosts in a given group. This directory contains a YAML file for each group of hosts, with each file defining variables that apply to that specific group.

In the context of a LAMP stack, you could use group_vars to define variables such as the MySQL root password, the Apache document root directory, and other attributes. By defining these variables in the group_vars file, you can ensure that all hosts in the group have the same configuration, and can update or modify the configuration by making changes in a single location.

mkdir group_vars ; touch group_vars/{amazon,ubuntu}
$ cat group_vars/amazon 
---

httpd_owner: "apache"
httpd_group: "apache"
httpd_domain: "wp.1by2.online"
httpd_port: "80"
mariadb_root_pass: "admin123"
mariadb_extra_user: "wp_user"
mariadb_extra_pass: "wp_user_pass"
mariadb_extra_db: "wp_db"
wp_url: "https://wordpress.org/wordpress-6.0.3.tar.gz"

Explanation:

The above code block is an example of a group_vars file for a group of hosts named amazon. The file is written in YAML format, and starts with three dashes, indicating that it is a YAML document.

The variables defined in this group_vars are used to configure a LAMP stack for a WordPress website. Here's what each variable does:

  • httpd_owner and httpd_group: These variables define the owner and group for the Apache HTTP server process. In this case, both are set to "apache", which is the default for many Linux distributions.
  • httpd_domain and httpd_port: These variables define the domain name and port that the Apache HTTP server will listen on. In this case, the domain name is "wp.1by2.online" and the port is "80".
  • mariadb_root_pass: This variable defines the root password for the MariaDB database server, which is used to store the WordPress site data.
  • mariadb_extra_user, mariadb_extra_pass, and mariadb_extra_db: These variables define a non-root user, password, and database for use by WordPress. The user, password, and database are created by the Ansible playbook and used to connect to the MariaDB server.
  • wp_url: This variable defines the URL for the WordPress package that will be downloaded and installed on the target hosts.
$ cat group_vars/ubuntu 
---

httpd_owner: "www-data"
httpd_group: "www-data"
httpd_domain: "wp.1by2.online"
httpd_port: "80"
mariadb_root_pass: "admin123"
mariadb_extra_user: "wp_user"
mariadb_extra_pass: "wp_user_pass"
mariadb_extra_db: "wp_db"
wp_url: "https://wordpress.org/wordpress-6.0.3.tar.gz"

Explanation:

  • httpd_owner and httpd_group: These variables define the owner and group for the Apache HTTP server process. In this case, both are set to "www-data", which is the default for Ubuntu.
    Other variables are the same as the amazon variables.

Templates

We use templates for the configuration files such as httpd.conf, virtualhost.conf & wp-config.php.

The template files are typically stored in the templates directory of the Ansible playbook. During execution, Ansible reads the template file, replaces the variables with their values (which are defined in the group_vars file), and then deploys the resulting configuration file to the target hosts.

The template files will usually have a .j2 extension as ansible uses the jinja module to replace the variables. But you can also save the template files without a .j2 extension.

httpd.conf: This is the main configuration file for the Apache HTTP server.
We use httpd.conf.j2 as the template file.

virtualhost.conf: This file defines the virtual host configuration for the Apache HTTP server. It specifies the domain name, the directory where the website files are stored, and other settings specific to the website.
We use virtualhost.conf.j2 as the template file.

wp-config.php: This is the configuration file for the WordPress website. It specifies the database settings, authentication keys, and other options for the WordPress installation. We use wp-config.php.j2 as the template file.

mkdir templates ; touch templates/{httpd.conf.j2,virtualhost.conf.j2,wp-config.php.j2}
$ cat templates/httpd.conf.j2

Listen {{ httpd_port }}

Explanation:

  • The {{ httpd_port }} in the httpd.conf.j2 file is a placeholder for the httpd_port variable defined in the group_vars file. During the Ansible playbook execution, the actual httpd_port value from the group_vars file is substituted in place of the placeholder {{ httpd_port }}. This way, the final httpd.conf file that is created by Ansible has the correct Listen directive with the correct port number.
  • For example, if the httpd_port variable is set to 80 in the group_vars file, then the line Listen {{ httpd_port }} in the httpd.conf.j2 file would be rendered as Listen 80 in the final httpd.conf file created by Ansible.

Remember: This is the only change you have to make in the “httpd.conf.j2” file. The remaining contents should be kept as it is.

$ cat templates/virtualhost.conf.j2 

<VirtualHost *:{{ httpd_port }}>
ServerName {{ httpd_domain }}
DocumentRoot /var/www/html/{{ httpd_domain }}
DirectoryIndex index.html index.php

<Directory "/var/www/html/{{ httpd_domain }}">
AllowOverride all
</Directory>
</VirtualHost>

Explanation:

  • {{ httpd_port }} and {{ httpd_domain }} variables are placeholders for the port and domain name used by the virtual host configuration.
  • <VirtualHost *:{{ httpd_port }}> directive specifies that this virtual host configuration applies to all IP addresses on the server, and listens on the port defined by the httpd_port variable.
  • ServerName {{ httpd_domain }} directive sets the domain name for the virtual host.
  • DocumentRoot directive specifies the location of the root directory for the virtual host.
  • DirectoryIndex directive sets the default files to be loaded when no file is specified in the URL.
  • <Directory "/var/www/html/{{ httpd_domain }}"> directive sets the permissions and access control for the directory where the website files are stored.
  • AllowOverride directive allows the use of .htaccess files for directory-specific configuration.
$ cat templates/wp-config.php.j2


define( 'DB_NAME', '{{ mariadb_extra_db }}' );
define( 'DB_USER', '{{ mariadb_extra_user }}' );
define( 'DB_PASSWORD', '{{ mariadb_extra_pass }}' );
define( 'DB_HOST', 'localhost' );

Explanation:

  • {{ mariadb_extra_db }}, {{ mariadb_extra_user }}, and {{ mariadb_extra_pass }} variables are placeholders for the database name, username, and password, respectively. These variables are defined in the group_vars file for the target host and are used by Ansible to generate the wp-config.php file with the correct database connection details for that host.
  • define() function is used in the wp-config.php file to define constants used by the WordPress application.
  • DB_NAME, DB_USER, and DB_PASSWORD constants set the database name, username, and password, respectively.
  • DB_HOST constant sets the host of the MySQL database server, which is set to localhost in this case.

Remember: These are the only changes you have to make in the
“wp-config.php.j2” file. The remaining contents should be kept as it is.

Tasks

In the case of launching a WordPress website on a LAMP stack on servers with different operating systems, you can use include_tasks to include tasks that are specific to each operating system.
For example, you may have tasks that are specific to Ubuntu and tasks that are specific to Amazon Linux, each of which would be defined in separate task files.

include_tasks is an Ansible module used to include a set of tasks from a different tasks file. This allows you to reuse tasks across multiple playbooks and roles, making your Ansible code more modular and easier to maintain. Using the include_tasks module in Ansible is a powerful way to organize and reuse code across playbooks, particularly when you need to perform different tasks depending on the target hosts.

To use include_tasks, you would first define the task files that contain the specific tasks for each operating system, such as tasks/amazon-tasks.yml and tasks/ubuntu-tasks.yml. You would then use include_tasks in your main playbook to include the appropriate task file based on the target host’s operating system.

A “when” condition is used to include the appropriate task file based on the value of ansible_os_family fact, which contains the target host’s operating system during the execution of the main playbook.

First, let’s take a look at the tasks for the Amazon server.

$ cat tasks/amazon-tasks.yml 


---

- name: "Amazon - installing httpd"
yum:
name: httpd
state: present

- name: "Amazon - installing php"
shell: amazon-linux-extras install php7.4 -y

- name: "Amazon - creating httpd conf file from template"
template:
src: "./templates/httpd.conf.j2"
dest: "/etc/httpd/conf/httpd.conf"

- name: "Amazon - creating virtualhost from template"
template:
src: "./templates/virtualhost.conf.j2"
dest: "/etc/httpd/conf.d/{{ httpd_domain }}.conf"
owner: "{{ httpd_owner }}"
group: "{{ httpd_group }}"

- name: "Amazon - creating document root"
file:
path: "/var/www/html/{{ httpd_domain }}"
state: directory
owner: "{{ httpd_owner }}"
group: "{{ httpd_group }}"

- name: "Amazon - installing mariadb"
yum:
name:
- mariadb-server
- MySQL-python
state: present

- name: "Amazon - restarting & enabling mariadb service"
service:
name: mariadb
state: restarted
enabled: true

- name: "Amazon - updating root pass"
ignore_errors: true
mysql_user:
login_user: root
login_password: ""
name: root
password: "{{ mariadb_root_pass }}"
host_all: true

- name: "Amazon - removing anonymous users"
mysql_user:
login_user: root
login_password: "{{ mariadb_root_pass }}"
name: ""
password: ""
host_all: true
state: absent

- name: "Amazon - creating db"
mysql_db:
login_user: root
login_password: "{{ mariadb_root_pass }}"
name: "{{ mariadb_extra_db }}"
state: present

- name: "Amazon - creating user"
mysql_user:
login_user: root
login_password: "{{ mariadb_root_pass }}"
name: "{{ mariadb_extra_user }}"
password: "{{ mariadb_extra_pass }}"
state: present
host: "%"
priv: "{{ mariadb_extra_db }}.*:ALL"

- name: "Amazon - downloading wp archive"
get_url:
url: "{{ wp_url }}"
dest: "/tmp/wordpress.tar.gz"

- name: "Amazon - extracting wp archive"
unarchive:
src: "/tmp/wordpress.tar.gz"
dest: "/tmp/"
remote_src: true

- name: "Amazon - copying wp files"
copy:
src: "/tmp/wordpress/"
dest: "/var/www/html/{{ httpd_domain }}/"
remote_src: true
owner: "{{ httpd_owner }}"
group: "{{ httpd_group }}"

- name: "Amazon - creating wp-config file from template"
template:
src: "./templates/wp-config.php.j2"
dest: "/var/www/html/{{ httpd_domain }}/wp-config.php"
owner: "{{ httpd_owner }}"
group: "{{ httpd_group }}"

- name: "Post Installation - restarting services"
service:
name: "{{ item }}"
state: restarted
enabled: true
with_items:
- httpd
- mariadb

- name: "Post Installation - cleanup"
file:
name: "{{ item }}"
state: absent
with_items:
- "/tmp/wordpress/"
- "/tmp/wordpress.tar.gz"

Explanation:

This file contains a set of tasks to be executed on an Amazon Linux system. Each task is defined with a name and a module from Ansible that performs the action. Here’s a breakdown of each task:

  • Install the Apache HTTP Server (httpd) using the “yum” module.
  • Install PHP 7.4 using the “shell” module and the “amazon-linux-extras” command.
  • Create an HTTPD configuration file from a template using the “template” module.
  • Create a virtual host configuration file from a template using the “template” module. The values of the “httpd_domain”, “httpd_owner”, and “httpd_group” variables are used in the template.
  • Create a document root directory for the virtual host using the “file” module. The value of the “httpd_domain” variable is used in the path, and the values of the “httpd_owner” and “httpd_group” variables are used for the file ownership and permissions.
  • Install the MariaDB server and the MySQL-Python package using the “yum” module.
  • Restart and enable the MariaDB service using the “service” module.
  • Update the root user’s password for the MariaDB server using the “mysql_user” module. The value of the “mariadb_root_pass” variable is used for the new password.
  • Remove any anonymous users from the MariaDB server using the “mysql_user” module.
  • Create a new database for WordPress using the “mysql_db” module. The value of the “mariadb_extra_db” variable is used for the database name.
  • Create a new user for WordPress using the “mysql_user” module. The values of the “mariadb_extra_user”, “mariadb_extra_pass”, and “mariadb_extra_db” variables are used for the user’s name, password, and database permissions.
  • Download the WordPress archive using the “get_url” module. The value of the “wp_url” variable is used for the download URL.
  • Extract the WordPress archive using the “unarchive” module. When remote_src is set to true, it tells Ansible to extract the archive file located on the remote server instead of the local machine.
  • Copy the WordPress files to the virtual host’s document root directory using the “copy” module. The value of the “httpd_domain” variable is used in the destination path, and the values of the “httpd_owner” and “httpd_group” variables are used for file ownership and permissions.
  • Create a wp-config.php file from a template using the “template” module. The values of the “httpd_domain”, “httpd_owner”, “httpd_group”, “mariadb_extra_user”, “mariadb_extra_pass”, and “mariadb_extra_db” variables are used in the template.
  • Restart the httpd and mariadb services using the “service” module.
  • Delete the temporary WordPress files and archive files using the “file” module.

Now, let’s check the tasks for the Ubuntu server.

$ cat tasks/ubuntu-tasks.yml 

---

- name: "Ubuntu - installing httpd"
apt:
update_cache: true
name:
- apache2
- php
- libapache2-mod-php
state: present

- name: "Ubuntu - creating httpd conf file from template"
template:
src: "./templates/httpd.conf.j2"
dest: "/etc/apache2/sites-available/apache2.conf"

- name: "Ubuntu - creating virtualhost from template"
template:
src: "./templates/virtualhost.conf.j2"
dest: "/etc/apache2/sites-available/{{ httpd_domain }}.conf"
owner: "{{ httpd_owner }}"
group: "{{ httpd_group }}"

- name: "Ubuntu - enabling wp site"
shell: a2ensite {{ httpd_domain }}.conf

- name: "Ubuntu - disabling default site"
shell: a2dissite 000-default.conf

- name: "Ubuntu - creating document root"
file:
path: "/var/www/html/{{ httpd_domain }}"
state: directory
owner: "{{ httpd_owner }}"
group: "{{ httpd_group }}"

- name: "Ubuntu - installing package"
apt:
name:
- mysql-server
- php-mysql
- python3-pymysql
state: present

- name: "Ubuntu - restarting & enabling service"
service:
name: mysql
state: restarted
enabled: true

- name: "Ubuntu - set the root password"
ignore_errors: true
mysql_user:
name: root
password: "{{ mariadb_root_pass }}"
login_unix_socket: /var/run/mysqld/mysqld.sock

- name: "Ubuntu - remove all anonymous user accounts"
mysql_user:
name: ''
host_all: yes
state: absent
login_user: root
login_password: "{{ mariadb_root_pass }}"

- name: "Ubuntu - creates database for WordPress"
mysql_db:
name: "{{ mariadb_extra_db }}"
state: present
login_user: root
login_password: "{{ mariadb_root_pass }}"

- name: "Ubuntu - creating users"
mysql_user:
name: "{{ mariadb_extra_user }}"
password: "{{ mariadb_extra_pass }}"
priv: "{{ mariadb_extra_db }}.*:ALL"
state: present
login_user: root
login_password: "{{ mariadb_root_pass }}"

- name: "Ubuntu - downloading wp archive"
get_url:
url: "{{ wp_url }}"
dest: "/tmp/wordpress.tar.gz"

- name: "Ubuntu - extracting wp archive"
unarchive:
src: "/tmp/wordpress.tar.gz"
dest: "/tmp/"
remote_src: true

- name: "Ubuntu - copying wp files"
copy:
src: "/tmp/wordpress/"
dest: "/var/www/html/{{ httpd_domain }}/"
remote_src: true
owner: "{{ httpd_owner }}"
group: "{{ httpd_group }}"

- name: "Ubuntu - creating wp-config file from template"
template:
src: "./templates/wp-config.php.j2"
dest: "/var/www/html/{{ httpd_domain }}/wp-config.php"
owner: "{{ httpd_owner }}"
group: "{{ httpd_group }}"

- name: "Post Installation - restarting services"
service:
name: "{{ item }}"
state: restarted
enabled: true
with_items:
- apache2
- mysql

- name: "Post Installation - cleanup"
file:
name: "{{ item }}"
state: absent
with_items:
- "/tmp/wordpress/"
- "/tmp/wordpress.tar.gz"

The tasks are similar to the amazon tasks with only a few differences. We discuss the ones that are different here.

Explanation:

  • name: "Ubuntu - installing httpd" This task uses the apt module to update the package cache and install Apache (apache2), PHP (php), and the Apache PHP module (libapache2-mod-php) on the Ubuntu server.
  • name: "Ubuntu - creating httpd conf file from template" This task uses the template module to create an Apache configuration file (/etc/apache2/sites-available/apache2.conf) from the template file (httpd.conf.j2).
  • name: "Ubuntu - creating virtualhost from template" This task uses the template module to create a virtual host file (/etc/apache2/sites-available/{{ httpd_domain }}.conf) from a Jinja2 template file (virtualhost.conf.j2). The httpd_domain, httpd_owner, and httpd_group variables are defined elsewhere in the playbook.
  • name: "Ubuntu - enabling wp site" This task uses the shell module to enable the virtual host site ({{ httpd_domain }}.conf) in Apache.
  • name: "Ubuntu - disabling default site" This task uses the shell module to disable the default site (000-default.conf) in Apache.
  • name: "Ubuntu - installing package" This task uses the apt module to install MySQL (mysql-server), the PHP MySQL module (php-mysql), and the Python 3 MySQL module (python3-pymysql) on the Ubuntu server.
  • name: "Ubuntu - restarting & enabling service" This task uses the service module to restart the MySQL service and enable it to start automatically on boot.
  • name: "Ubuntu - Set the root password" This task uses the mysql_user module to set the root password for the MySQL server. The mariadb_root_pass variable is defined elsewhere in the playbook.
  • name: "Ubuntu - remove all anonymous user accounts" This task uses the mysql_user module to remove all anonymous user accounts from the MySQL server.
  • name: "Ubuntu - creates database for WordPress" This task uses the mysql_db module to create a database ({{ mariadb_extra_db }}) for the WordPress site. The mariadb_extra_db and mariadb_root_pass variables are defined elsewhere in the playbook.
  • name: "Ubuntu - creating users" This task uses the mysql_user module to create a MySQL user ({{ mariadb_extra_user }}) for the WordPress site with access to the database created in the previous step. The mariadb_extra_user, mariadb_extra_pass, mariadb_extra_db, and mariadb_root_pass variables are defined elsewhere in the playbook.
  • Restart the apache2 and mysql services using the “service” module.
  • Delete the temporary WordPress files and archive files using the “file” module.

Finally, let’s check the main playbook.

$ cat wordpress.yml 

- name: "Launching WordPress website on LAMP Stack"
become: yes
hosts: amazon,ubuntu
tasks:

- include_tasks: ./tasks/amazon-tasks.yml
when: ansible_distribution == "Amazon" and ansible_os_family == "RedHat"

- include_tasks: ./tasks/ubuntu-tasks.yml
when: ansible_distribution == "Ubuntu" and ansible_os_family == "Debian"

Explanation:

This is an Ansible playbook named wordpress.yml that launches a WordPress website in a LAMP (Linux, Apache, MySQL, PHP) stack. The playbook has two plays, each of which includes tasks specific to either an Amazon Linux or an Ubuntu operating system.

  • The first play is called “Launching WordPress website on LAMP Stack”. become: yes statement at the beginning of the play means that Ansible will use sudo or other privilege escalation method to run the tasks with elevated permissions.
  • hosts: amazon,ubuntu statement means that the tasks will be run on hosts that are part of either the amazon or ubuntu group defined in the inventory file.
  • The first task in the play uses the include_tasks module to include a task file called amazon-tasks.yml, which contains tasks specific to Amazon Linux. This task file will only be included if the ansible_distribution variable is set to "Amazon" and the ansible_os_family variable is set to "RedHat".
  • The second task in the play uses the include_tasks module to include a task file called ubuntu-tasks.yml, which contains tasks specific to Ubuntu. This task file will only be included if the ansible_distribution variable is set to "Ubuntu" and the ansible_os_family variable is set to "Debian".
  • By using conditional statements in the task inclusions, the playbook ensures that only the relevant tasks for a particular operating system are executed on the corresponding hosts.

Result

After successful execution of the playbook, you can access the public DNS name of both the servers and you may get the page to install wordpress.

Amazon server

Ubuntu server

Conclusion

In conclusion, deploying a WordPress website on a LAMP stack can be a time-consuming and complicated process, especially when dealing with servers that have different operating systems. However, with the use of an Ansible playbook, the deployment process can be automated, making it consistent and reliable across all environments.
This article has provided a comprehensive guide on how to create an Ansible playbook that can be used to deploy a WordPress website on a LAMP stack, regardless of the target server’s operating system. With the knowledge gained from this article, developers, system administrators, and DevOps engineers can streamline the deployment process, save time, and ensure consistency and reliability across all environments.

--

--