Launching a WordPress Website on LAMP Stack Across Different Operating Systems using Anisble Playbook
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
andhttpd_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
andhttpd_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
, andmariadb_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
andhttpd_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 thehttpd.conf.j2
file is a placeholder for thehttpd_port
variable defined in thegroup_vars
file. During the Ansible playbook execution, the actualhttpd_port
value from thegroup_vars
file is substituted in place of the placeholder{{ httpd_port }}
. This way, the finalhttpd.conf
file that is created by Ansible has the correctListen
directive with the correct port number. - For example, if the
httpd_port
variable is set to80
in thegroup_vars
file, then the lineListen {{ httpd_port }}
in thehttpd.conf.j2
file would be rendered asListen 80
in the finalhttpd.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 thehttpd_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 thegroup_vars
file for the target host and are used by Ansible to generate thewp-config.php
file with the correct database connection details for that host.define()
function is used in thewp-config.php
file to define constants used by the WordPress application.DB_NAME
,DB_USER
, andDB_PASSWORD
constants set the database name, username, and password, respectively.DB_HOST
constant sets the host of the MySQL database server, which is set tolocalhost
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 totrue
, 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 theapt
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 thetemplate
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 thetemplate
module to create a virtual host file (/etc/apache2/sites-available/{{ httpd_domain }}.conf
) from a Jinja2 template file (virtualhost.conf.j2
). Thehttpd_domain
,httpd_owner
, andhttpd_group
variables are defined elsewhere in the playbook.name: "Ubuntu - enabling wp site"
This task uses theshell
module to enable the virtual host site ({{ httpd_domain }}.conf
) in Apache.name: "Ubuntu - disabling default site"
This task uses theshell
module to disable the default site (000-default.conf
) in Apache.name: "Ubuntu - installing package"
This task uses theapt
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 theservice
module to restart the MySQL service and enable it to start automatically on boot.name: "Ubuntu - Set the root password"
This task uses themysql_user
module to set the root password for the MySQL server. Themariadb_root_pass
variable is defined elsewhere in the playbook.name: "Ubuntu - remove all anonymous user accounts"
This task uses themysql_user
module to remove all anonymous user accounts from the MySQL server.name: "Ubuntu - creates database for WordPress"
This task uses themysql_db
module to create a database ({{ mariadb_extra_db }}
) for the WordPress site. Themariadb_extra_db
andmariadb_root_pass
variables are defined elsewhere in the playbook.name: "Ubuntu - creating users"
This task uses themysql_user
module to create a MySQL user ({{ mariadb_extra_user }}
) for the WordPress site with access to the database created in the previous step. Themariadb_extra_user
,mariadb_extra_pass
,mariadb_extra_db
, andmariadb_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 theamazon
orubuntu
group defined in the inventory file.- The first task in the play uses the
include_tasks
module to include a task file calledamazon-tasks.yml
, which contains tasks specific to Amazon Linux. This task file will only be included if theansible_distribution
variable is set to "Amazon" and theansible_os_family
variable is set to "RedHat". - The second task in the play uses the
include_tasks
module to include a task file calledubuntu-tasks.yml
, which contains tasks specific to Ubuntu. This task file will only be included if theansible_distribution
variable is set to "Ubuntu" and theansible_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.