Setting up a LAMP Stack application
Published: 2024-03-31 03:44:09


The LAMP stack is a powerful combination of open-source technologies, enabling developers to build dynamic and interactive websites and web applications. 

This article is a comprehensive guide to containerizing a LAMP (Linux, Apache, MySQL, PHP) stack application. It covers everything from setting up the application to running the containers using Docker commands and Docker Compose, along with monitoring the containers. To improve readability, it has been split up into different parts to form a series. This first part focuses on setting up the application on your local machine.


## Application Architecture
![image edit](https://user-images.githubusercontent.com/89241109/256314580-e1dedd92-4c23-4fcb-92f6-c88ed87715ab.png)

Please check the below embed to view the image in higher resolution:
{% embed https://user-images.githubusercontent.com/89241109/256314580-e1dedd92-4c23-4fcb-92f6-c88ed87715ab.png %}

In the above architecture diagram, users initiate an HTTP request by accessing the application through the browser using either "localhost" or the server's IP address. The server, with Apache installed, responds by serving the "form.html" file to users, prompting them to fill in their details, including their name, email, and description.

Upon completing the form, users submit the data back to the server. Apache then forwards the submitted data to a PHP script responsible for storing this information in the MySQL database. If the data is successfully stored, MySQL communicates this success to the PHP script, which responds with an HTML message displayed in the user's browser. On the other hand, if there is an issue while saving the data, the PHP script returns an error message to the user's browser, notifying them of the encountered problem.

This robust architecture ensures a seamless flow of data between users, Apache, PHP, and MySQL, providing a smooth user experience and reliable data management.

## Prerequisites
Before we get started, ensure that you have the following in place:

- [Docker](https://docs.docker.com/engine/install/) or [Docker Desktop](https://www.docker.com/products/docker-desktop/) installed
- [Docker Compose](https://docs.docker.com/compose/install/) (If you are using Docker Desktop, it comes with it already)
- An IDE, [VSCode](https://code.visualstudio.com/Download) recommended

## Application Setup
For setup and testing purposes, the application will be deployed and tested on a Linux Ubuntu OS, utilizing VirtualBox and Vagrant within a macOS environment.

### Project Structure
At the beginning of the build, the environment will resemble the structure below but overtime as we create more folders and files, the structure will change.
```
.
├── form_submit.php
├── form.html
├── install.sh
├── setup.sh
└── vagrantfile
```

### Setup the Linux Environment
To setup the Linux Environment on mac, we will be utilizing Vagrant and Virtual Box. A script has been provided to automatically provision them.

**NB:** This step is applicable to mac users only. Linux users can proceed to the [installation](#installation) section.

- Create an `install.sh` file in the root of the project and copy the below contents into it.

```jsx
#/bin/sh

# Check if VirtualBox is installed
if ! command -v VBoxManage &> /dev/null; then
    echo "VirtualBox not found. Installing VirtualBox..."
    brew install --cask virtualbox
else
    echo "VirtualBox is already installed."
fi


# Check if Vagrant is installed
if ! command -v vagrant &> /dev/null; then
    echo "Vagrant not found. Installing Vagrant..."
    brew install --cask vagrant
    brew install --cask vagrant-manager
else
    echo "Vagrant is already installed."
fi
```
- Give the script executable permission and run it with the following command:
```jsx
chmod +x install.sh
./install.sh
```
It will automatically check if Vagrant and Virtual Box are already installed on your system, if they are installed outputs a message to let you know. However, if they are not found, the script will proceed to install both Vagrant and VirtualBox for you seamlessly.

Also in the root of the project folder, create a `vagrantfile` file and paste the below contents into it:
```jsx
NUM_CONTROLLER_NODE = 1
IP_NTW = "192.168.56."
CONTROLLER_IP_START = 2
NODE_IP_START = 3

Vagrant.configure("2") do |config|
    config.vm.box = "ubuntu/bionic64"

    i = 0

    (1..NUM_CONTROLLER_NODE).each do [i] 
        config.vm.define "dockertask" do |node|
            node.vm.provider "virtualbox" do |vb|
                vb.name = "dockertask"
                vb.memory = 2048
                vb.cpus = 2
            end

            node.vm.hostname = "dockertask"
            node.vm.network "private_network", ip: IP_NTW + "#{CONTROLLER_IP_START + i}"
            node.vm.network "forwarded_port", guest: 22, host: "#{2710 + i}"
        end
    end
end
```
This vagrantfile configuration helps in automatically setting up the Linux VM, utilizing an `ubuntu/bionic64` image for the setup. The VM has been given a name `dockertask` which you are free to modify. To provision the VM, run the following command

```jsx
vagrant up --provision
```
Open the Virtual Box application to see the setup VM

NB: To seamlessly copy files from your local (macOs) to the Linux VM, you can use the below command:

```jsx
vagrant scp </absolute-path-of-the-project-directory-to-the project-folder/> <name-of-vm>:/home/vagrant
```
- It should look like this: 

``` 
vagrant scp /Users/favour/Desktop/Modules/module-2/ dockertask:/home/vagrant
```
Note that the command should always be ran every time a change has been made on your local so that the Linux VM has the updated version.

### Installation

With the VM now provisioned, the next step involves installing Apache, MySQL, and PHP, creating the essential LAMP stack foundation for the application. A script has also been provided to automate the installation process. 

- Create a `setup.sh` file at the root of the project folder, copy and paste the below contents into it:

```jsx
#/bin/sh

# update the VM
sudo apt-get update -y

# Install apache web server and start it
sudo apt-get install apache2 -y
sudo service apache2 start

# Install mysql and go through the setup. Please remember your root password
sudo apt-get install mysql-server -y
sudo /usr/bin/mysql_secure_installation

# Install PHP
sudo apt-get install php -y
sudo apt-get install php-mysql -y
sudo apt-get install libapache2-mod-php -y
sudo apache2ctl -M
sudo a2dismod mpm_event
sudo a2enmod mpm_prefork
sudo a2enmod php7.2
sudo /etc/init.d/apache2 restart
```
What each command does:

- **`sudo apt-get update -y`:** This updates the package lists and ensures that the package information is up-to-date before proceeding with any installations.

- **`sudo apt-get install apache2 -y`:** This installs the Apache web server on the system. The -y flag allows the installation to proceed automatically without asking for user confirmation.

- **`sudo service apache2 start`:** This command starts the Apache web server, so it becomes active and can serve web pages.

- **`sudo apt-get install mysql-server -y`:** This installs the MySQL database server. The -y flag allows the installation to proceed automatically without asking for user confirmation.

- **`sudo /usr/bin/mysql_secure_installation`:** This script guides you through a series of steps to set up MySQL securely. It prompts you to configure the root password, remove anonymous users, disable remote root login, and more.

- **`sudo apt-get install php -y`:** This installs PHP, a server-side scripting language, on the system.

- **`sudo apt-get install php-mysql -y`:** This installs the PHP MySQL extension, which allows PHP to communicate with a MySQL database. This extension is required if your PHP application needs to interact with a MySQL database.

- **`sudo apt-get install libapache2-mod-php -y`:** This installs the PHP module for Apache web server (libapache2-mod-php) along with its dependencies. The -y flag allows the installation to proceed automatically without asking for user confirmation.

- **`sudo apache2ctl -M`:** This command lists all the loaded Apache modules. It is used to check if the PHP module (mod_php) is successfully loaded after installation.

- **`sudo a2dismod mpm_event`:** This disables the Apache event module (mpm_event) to switch to the prefork module, which is required for running PHP with Apache.

- **`sudo a2enmod mpm_prefork`:** This enables the Apache prefork module (mpm_prefork), which is necessary to work with PHP.

- **`sudo a2enmod php7.2`:** This enables the PHP module (mod_php) in Apache for PHP version 7.2. Replace 7.2 with the appropriate version if you are using a different PHP version.

- **`sudo /etc/init.d/apache2 restart`:** This restarts the Apache web server to apply the changes made by enabling the PHP module and switching to the prefork module.

To execute the script, run the below command:
```jsx
chmod +x setup.sh
./setup.sh
```
A prompt will come up asking you to pick a password level for mysql, it should look like this :

```
Would you like to setup VALIDATE PASSWORD plugin?

Press y|Y for Yes, any other key for No: y
```
It is best to give a password for security purposes, so input "y", then you will get the following prompt asking you to pick a password level: 

```
LOW    Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary

Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 2
``` 
For securing the MySQL server, you have the option to select the password complexity level of your preference. If you choose the "LOW" length, which is set to "0", you could use a password like `dockertask` to meet the requirements. 

It's important to note that selecting a more complex password, especially for production environments, is strongly recommended to enhance security. However, for local development or testing purposes, a simple password like `dockertask` can be used. 


Enabling password validation during the MySQL setup will prompt the system to evaluate the strength of the root password you provided. The server will then present you with the password strength assessment. If you are satisfied with the current password, you can proceed by entering "Y" for "yes" at the prompt. This step ensures that you have reviewed the password strength and are content with the chosen password before proceeding with the setup. 

It should resemble the below output:

```
Estimated strength of the password: 100 
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y
```
After completing the MySQL setup, the script will proceed with the remaining instructions. To ensure that PHP has been successfully installed, you can confirm it by running the following command:
```
php -v
```
Executing this command will display the installed PHP version along with relevant details, affirming that PHP is now operational and ready for use in your environment.

Now, open your web browser and enter the IP address of the server, which should be `192.168.56.2` or `localhost` depending on the environment you are working from. If everything is set up correctly, you should see the Apache web server running, and it will display the default Apache landing page or any other content that you might have configured. This confirms that Apache is up and running and successfully serving web pages.

- To verify that you can access the MySQL console with the updated root user password, execute the following command:

```jsx
sudo mysql -p
```
NB: The `-p` flag prompts you to enter the password you set during the MySQL setup. After providing the correct password, you should be able to log in to the MySQL console, where you can manage and interact with the MySQL database.

To exit the MySQL console, simply type:
```
exit
```
This will take you out of the MySQL console and return you to the regular command prompt.


### Setup the Application
In the root of the project folder, create a `form.html` file:

```jsx
touch form.html
```
- Paste the below contents into it
```
<head>
    <title>
    Test Page
    </title>
    </head>
    <body>
    <form action="http://localhost/form_submit.php" class="alt" method="POST">
    <div class="row uniform">
    <div class="name">
    <input name="name" id="" placeholder="Name" type="text">
    </div>
    <div class="email">
    <input name="email" placeholder="Email" type="email">
    </div>
    <div class="message">
    <textarea name="message" placeholder="Message" rows="4"></textarea>
    </div>
    </div>
    <br/>
    <input class="alt" value="Submit" name="submit" type="submit">
    </form>
    </body>
```
- In the same directory, create a `form_submit.php` file:
```
touch form_submit.php
```
- Paste the below contents into it:'
```
<?php
$host = getenv('DB_HOST');
$db_name = getenv('MYSQL_DATABASE');
$username = getenv('DB_USER');
$password = getenv('MYSQL_PASSWORD');
$option = array(
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
  );

# Catch errors
try{
    $connection = new PDO("mysql:host=" . $host . ";dbname=" . $db_name, $username, $password, $option);
    $connection->exec("set names utf8");
} catch(PDOException $exception){
    echo "Connection error: " . $exception->getMessage();
}


function saveData($name, $email, $message){
    global $connection;
    $query = "INSERT INTO test(name, email, message) VALUES( :name, :email, :message)";

    try {
        $callToDb = $connection->prepare( $query );
        $name=htmlspecialchars(strip_tags($name));
        $email=htmlspecialchars(strip_tags($email));
        $message=htmlspecialchars(strip_tags($message));
        $callToDb->bindParam(":name",$name);
        $callToDb->bindParam(":email",$email);
        $callToDb->bindParam(":message",$message);

        if($callToDb->execute()){
            return '<h3 style="text-align:center;">Your information has been submitted to the database successfully!</h3>';
        }   else {
            return '<h3 style="text-align:center;">Failed to save data.</h3>';
        }
    } catch (PDOException $exception) {
        return '<h3 style="text-align:center;">Error: ' . $exception->getMessage() . '</h3>';
    }
}


if( isset($_POST['submit'])){
    $name = htmlentities($_POST['name']);
    $email = htmlentities($_POST['email']);
    $message = htmlentities($_POST['message']);

    //then you can use them in a PHP function. 
    $result = saveData($name, $email, $message);
    echo $result;
} else{
    echo '<h3 style="text-align:center;">A very detailed error message ( ͡° ͜ʖ ͡°)</h3>';
}
?>
```
- Create a `.env` file which will contain environment variables for the PHP script to use in connecting to the database:
```
touch .env
```
- Paste the below content into the file:
```
DB_HOST=mysql
MYSQL_DATABASE=dev_to
DB_USER=root
MYSQL_PASSWORD=dockertask
```

### Creating a Virtual Host
Creating a Virtual Host in Apache provides numerous benefits, such as hosting multiple websites on a single server while maintaining separate configurations for each site. It conserves IP addresses, streamlines website management, and enhances security by isolating websites from one another.

While it is **not mandatory** to create a virtual host, doing so is highly beneficial, especially when dealing with multiple websites that need to be served using Apache. We will be creating a virtual host for learning purposes as this process helps you gain familiarity with Apache's directory configurations, enabling you to manage websites more effectively in the future. 


In case you choose not to create a virtual host, you can proceed to work with the default directory located at `/var/www/html`. This directory serves as the default location for hosting websites on the Apache web server. 

For better organization, we will create a directory called `dockertask` to house the project files, making it easier to manage and serve them.

- To create the directory for the `dockertask`, run the following command:

```jsx
sudo mkdir /var/www/dockertask
```
- To ensure proper ownership and permissions, assign the directory dockertask to your current system user by running the below command:
```jsx
sudo chown -R $USER:$USER /var/www/projectlamp
sudo chmod -R 755 /var/www/dockertask
```
This will allow you to work with the project files without encountering any permission issues.

- Create and open a new configuration file, `dockertask.conf`, in Apache’s sites-available directory 
```jsx
sudo nano /etc/apache2/sites-available/dockertask.conf
```

This will create a new blank file. Paste in the following bare-bones configuration, then do a `ctrl+x` and type y for yes to save it:

```
<VirtualHost 192.168.56.2:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/dockertask
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
```
If you are working directly from a Linux OS, use the belo configuration:
```
<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/dockertask
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
```
- Set the ServerName directive globally, add the ServerName at the top or bottom of the file
```jsx
sudo nano /etc/apache2/apache2.conf
```
- On the first line of the `apache2.conf` file, add the below:

```
ServerName 192.168.56.2
```
Leave the ServerName as `localhost` if you are working directly with a Linux OS.

Setting `ServerName` globally in Apache means specifying a default server name that will be used for any virtual host that does not explicitly define its own ServerName.

In Apache HTTP Server, the ServerName directive is used to define the hostname and port number of the virtual host. A virtual host allows you to run multiple websites on the same physical server, and each virtual host can have its own ServerName that corresponds to a unique domain or hostname.

- Use the `ls` command to confirm the `dockertask.conf` file exists in apache's sites-available directory
```jsx
sudo ls /etc/apache2/sites-available
```

- The output should resemble the below:
```
000-default.conf  default-ssl.conf  dockertask.conf
```

- Enable the newly created virtual host:
```jsx
sudo a2ensite dockertask
```

**NB:** We need to disable the default website that comes installed with Apache. This is required if you’re not using a custom domain name, because in this case Apache’s default configuration would overwrite your virtual host. 

- To disable Apache’s default website use `a2dissite` command , run the following command:

```jsx
sudo a2dissite 000-default
```
- To make sure the configuration file doesn’t contain syntax errors, run:
```jsx
sudo apache2ctl configtest
```

- The output should resemble the below:
```
Syntax OK
```

- Finally, reload Apache so the changes take effect:
```jsx
sudo systemctl reload apache2
```

The new website is now active, but the web root `/var/www/dockertask` is still empty.

Now we move the required folders, `form.html` and `form_submit.php`, to this directory from the home directory where the files exist

- To move the files, run the below command:
```jsx
sudo mv form.html /var/www/dockertask
sudo mv form_submit.php /var/www/dockertask
```

By default, Apache prioritises the `index.html` file over `index.php`, making it the landing page for the application. After maintenance, simply renaming or removing the index.html from the document root restores the regular application page.

To change this behavior, we need to edit the `/etc/apache2/mods-enabled/dir.conf` file and change the order in which the index.php file is listed within the DirectoryIndex directive: 

```jsx
sudo nano /etc/apache2/mods-enabled/dir.conf
```
- Change the existing configuration to look like the below:
```
<IfModule mod_dir.c>
        #Change this:
        #DirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm
        #To this:
        DirectoryIndex form.html form_submit.php index.cgi index.pl index.xhtml index.htm
</IfModule>
```
- After saving and closing the file, reload Apache for the changes to take effect:
```jsx
sudo systemctl reload apache2
```


### Setup Mysql database

- Log into mysql to set up a database
```jsx
sudo mysql -u root -p
```
- Create a new MySQL user:

In the MySQL shell, run the following query to create a new user:
```jsx
CREATE USER 'vagrant'@'192.168.56.2' IDENTIFIED BY 'Strongpassword@123';
```
**NB:** You can choose a different user and password for the above. Also remember to change the server address depending on the environment.

- Grant privileges to the new user:

Next, grant the necessary privileges to the new user and flush the privileges to ensure the changes take effect immediately:
```jsx
GRANT ALL PRIVILEGES ON dev_to.* TO 'vagrant'@'192.168.56.2';
FLUSH PRIVILEGES;
```

**NB:** Troubleshooting

> If you run into access denied error for root user after providing your password, troubleshoot with [this](https://stackoverflow.com/questions/39281594/error-1698-28000-access-denied-for-user-rootlocalhost). It is a common issue with ubuntu/linux systems

- Create a database using the below SQL commands:
```jsx
create database dev_to;
use dev_to;
create table test(
    id int NOT NULL AUTO_INCREMENT,
    name varchar(255),
    email varchar(255),
    message text,
    PRIMARY KEY (id)
); 
```

- Confirm the database was created;
```jsx
describe test;
```

You should see an output resembling the below
```
mysql> describe test;
+---------+--------------+------+-----+---------+----------------+
| Field   | Type         | Null | Key | Default | Extra          |
+---------+--------------+------+-----+---------+----------------+
| id      | int(11)      | NO   | PRI | NULL    | auto_increment |
| name    | varchar(255) | YES  |     | NULL    |                |
| email   | varchar(255) | YES  |     | NULL    |                |
| message | text         | YES  |     | NULL    |                |
+---------+--------------+------+-----+---------+----------------+
```

- Exit the MySQL shell:
```
exit
```

### Fix binding settings for mysql
Skip this step if you are working from a Linux OS with your ServerName as `localhost`.

By default, MySQL is bound to the local host (127.0.0.1). However, since the Vagrant server has its own unique IP address setup, we must configure MySQL to bind to this specific IP address.

- Open the file for mysql conf
```jsx
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
```
- Search for `bind-address` and change it from `0.0.0.0` to `192.168.56.2`, save and exit.

- Restart the mysql server
```jsx
sudo service mysql restart
```
Now, any information filled into the html form will automatically be saved in this database. 

- To run checks, log into the mysql database
```jsx
sudo mysql -u root -p
``` 

- Check for existing databases and identify the one we created earlier, `dev_to`. Once located, we can proceed to examine the tables residing under it, including the `test` table.

```jsx
SHOW DATABASES;
USE dev_to;
SHOW TABLES;
describe test;
SELECT * FROM test;
```

## Conclusion
In this article, we explored the process of setting up a fully functional LAMP stack application and learned how the components communicate with each other. In the next article, we will explore how to containerize the application using Docker.

- Credits to [Adnan Alam](https://dev.to/satellitebots/create-a-web-server-and-save-form-data-into-mysql-database-using-php-beginners-guide-fah)