Containerizing a LAMP Stack Application
Published: 2023-08-06 15:24:23

In the previous [article](https://dev.to/danielfavour/setting-up-a-lamp-stack-application-1nii?preview=e2110a9d5a3ab5f630a9c4593ffe1e85442cb794f746025c28255cc9fec88a504b99fe00aa3ce3573de172c0b086d2e6b87ebfb8b868130e28b04efd#installation), we learned how to set up a LAMP stack application. In this article, we will be looking at containerizing the LAMP stack application.
 
Containerization of an application involves packaging it along with its dependencies, configurations, and runtime environment into a Docker container. This encapsulation ensures that the application can run consistently across different environments and eliminates potential compatibility issues.


For this process, our primary focus will be on the PHP script. Initially, we'll create a Dockerfile for the PHP application, allowing us to build the Docker image and subsequently run the container. To establish a database connection, we'll attach a MySQL container. Additionally, we'll introduce an extra container, phpMyAdmin, which will serve as a user-friendly GUI for accessing and managing the database.

## Container Architecture diagram
![container architecture](https://user-images.githubusercontent.com/89241109/256974658-d62771cc-a614-4b45-acb6-903cb6119a63.png)

Please check the below embed to view the image in higher resolution:
{% embed https://user-images.githubusercontent.com/89241109/256974658-d62771cc-a614-4b45-acb6-903cb6119a63.png %}

The diagram above illustrates the container architecture utilizing Docker Compose. The applications are executed with Docker Compose and are encapsulated within a Docker bridge network, facilitating smooth communication among them. The host OS provides a Docker Engine, making Docker functionality available for seamless container management and orchestration.

## Container Communication Diagram

![Container Communication](https://user-images.githubusercontent.com/89241109/257053008-1f06824c-f91f-42c0-9e43-5ea46aaa80a4.png)

Please check the below embed to view the image in higher resolution:
{% embed https://user-images.githubusercontent.com/89241109/257053008-1f06824c-f91f-42c0-9e43-5ea46aaa80a4.png %}

### Approach to Running the Containers
We will be employing 2 different methods to run the containers, without Docker Compose and with Docker Compose.

In the "without Docker Compose" approach, we will rely on Makefiles to automate the container building and running processes. The Makefiles will handle tasks like pulling the required Docker images, creating and configuring containers, setting up networking, and other necessary actions. This method allows us to manage the containerization workflow efficiently, automating key steps with concise and easily maintainable scripts.

On the other hand, the "with Docker Compose" approach involves utilizing Docker Compose, a powerful tool for defining and managing multi-container applications. With Docker Compose, we can specify the services, networks, volumes, and configurations required for our application within a single YAML file. This streamlines the entire process, making it easier to deploy and manage multiple containers in a cohesive manner.


### Project Structure
The project structure should be identical to the below:
```
.
├── .env
├── docker-compose.yml
├── install.sh
├── mysql
│   ├── db.sql
│   └── makefile
├── php
│   ├── .env
│   ├── Dockerfile
│   ├── form.html
│   ├── form_submit.php
│   └── makefile
├── setup.sh
└── vagrantfile
```

## Without Docker Compose (Makefile Approach)
A Makefile is a file used with the Unix utility "make" to automate the build process of software projects. It contains instructions, typically written in shell commands, and is named "makefile" or "Makefile" depending on the system. Each rule in the Makefile has a target, dependencies, and commands. When you run the "make" command, it reads the Makefile and executes the specified commands to build the software according to the defined rules.

In the context of containerizing our application, a Makefile serves as a valuable automation tool. It streamlines the entire containerization process by automating the necessary tasks. The Makefile is designed to install essential tools, lint both the Dockerfile and PHP application code, build the Docker image, and run the container image.

The primary objective behind using a Makefile is to achieve seamless automation, making the development workflow more efficient and consistent.

### Create PHP directory

In the root of the project folder, create a `php` directory and move the `form_submit.php` and `form.html` files into it:
```jsx
mkdir php
mv form_submit.php /php
mv form.html /php
```
### Environment Variables
We will create environment variables in the php folder. These variables will be used for the form_submit.php. 

- Create a `.env file`:
```jsx
touch .env
```
- Paste the below into the file:
```
MYSQL_PASSWORD=Strongpassword@123
DB_HOST=mysql
MYSQL_DATABASE=dev_to
DB_USER=root
```
Remember to change the values and use values of your choice but be consistent with it across the environment. These variables are what php will use to communicate with the MySQL server for data storage.

### Write the Dockerfile
To containerize the PHP application, we need to create a Dockerfile. In the php directory, create a new file called Dockerfile without any file extension. Dockerfiles don't require extensions.

Now, paste the following content into the Dockerfile:
```jsx
FROM php:7.4-apache

WORKDIR /var/www/html

RUN docker-php-ext-install mysqli pdo pdo_mysql && docker-php-ext-enable mysqli

COPY form.html /var/www/html/index.html
COPY form_submit.php /var/www/html

RUN chown -R www-data:www-data /var/www/html \
    && a2enmod rewrite

EXPOSE 80
```
Breakdown of what each line means:

- **`FROM php:7.4-apache`:** This line sets the base image for our container. It uses the official PHP image with Apache server, version 7.4. This base image already includes PHP and Apache, making it convenient for hosting PHP applications.

- **`WORKDIR /var/www/html`:** The `WORKDIR /var/www/html` instruction sets the working directory inside the container to `/var/www/html`. It provides a context for file operations, and subsequent commands like `COPY` or `RUN` will be executed relative to this directory. In this case, it is set to the common location for web application files in Apache servers, and the following COPY commands copy the PHP application files into this directory inside the container for use by the Apache web server.

- **`RUN docker-php-ext-install mysqli pdo pdo_mysql && docker-php-ext-enable mysqli`:** This line uses the `RUN` instruction to execute commands during the Docker image build process. Here, we are installing PHP extensions mysqli, pdo, and pdo_mysql required for database connections. The `docker-php-ext-enable` command is used to enable the mysqli extension.

- **`COPY form.html /var/www/html/index.html`:** The COPY instruction copies the `form.html` file from the host (your local machine) to the container's `/var/www/html` directory. In this case, it is renamed to index.html, serving as the default HTML page when accessing the root URL.

- **`COPY form_submit.php /var/www/html`:** Similarly, this line copies the `form_submit.php` file from the host to the container's `/var/www/html` directory. This PHP file handles the form submissions from the `form.html` page.

- **`RUN chown -R www-data:www-data /var/www/html && a2enmod rewrite`:** Here, we use the `RUN` instruction to set the ownership of the `/var/www/html` directory to the www-data user and group. Apache typically runs under the www-data user, so this ensures proper permissions for serving the website content.

The second part of this line uses the `a2enmod` command to enable the Apache module rewrite. The rewrite module is needed to allow URL rewriting, enabling cleaner URLs and better routing for the PHP application.

- **`EXPOSE 80`:** The `EXPOSE` instruction is a metadata declaration that indicates which network ports the container will listen on during runtime. In this case, we specify that the container will listen on port 80. However, this does not actually publish the port to the host machine; it only serves as documentation for the user.

### Create the Environment Variables File
In the php folder directory, create a file called `.env`, it will be used to store environment variables for the containers:
```
touch .env
```
- Copy the below contents into it, ensure to fill it up with your desired values but leave the DB_Host as mysql and the DB_USER as root:
```
MYSQL_PASSWORD=
DB_HOST=mysql
MYSQL_DATABASE=
DB_USER=root
```

### Write the Makefile

- To create the `Makefile` file, run the following command:
```jsx
touch Makefile
```

- Paste the below configuration into the makefile:
```jsx
# Makefile for PHP Dockerfile and PHP Code

.SILENT:
install:
	# Check if Homebrew is installed otherwise install it
	@if ! command -v brew &> /dev/null; then \
		echo "Installing Homebrew..."; \
		/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"; \
		echo "Homebrew installed!"; \
	else \
		echo "Homebrew is already installed."; \
	fi

	# Check if Hadolint is installed otherwise install it
	@if ! command -v hadolint &> /dev/null; then \
		echo "Installing Hadolint..."; \
		brew install hadolint; \
		echo "Hadolint installed!"; \
	else \
		echo "Hadolint is already installed."; \
	fi

	# Check if wget is installed otherwise install it
	@if ! command -v wget &> /dev/null; then \
		echo "Installing wget..."; \
		brew install wget; \
		echo "wget installed!"; \
	else \
		echo "wget is already installed."; \
	fi

	# Check if php is installed otherwise install it
	@if ! command -v php &> /dev/null; then \
		echo "Installing PHP..."; \
		brew install php; \
		echo "PHP installed!"; \
	else \
		echo "PHP is already installed."; \
	fi

	# Install Xcode Developer Tools but check if they are already installed first
	@if ! xcode-select -p > /dev/null 2>&1; then \
		echo "Installing Xcode Developer Tools..."; \
		xcode-select --install; \
	else \
		echo "Xcode Developer Tools are already installed."; \
	fi

.SILENT:
lint_dockerfile:
	# Lint the Dockerfile using hadolint
	# See local hadolint install instructions: https://github.com/hadolint/hadolint
	hadolint Dockerfile

	# Print a successful message
	@echo "Dockerfile linting completed successfully. No errors found, Dockerfile follows best practices."



.SILENT:	
lint_php:
	set -x  # Enable verbose output
	# Check if PHPCS and PHPCBF linter files are downloaded, otherwise download them
	@if [ ! -f "phpcs.phar" ]; then \
		echo "Downloading phpcs.phar..."; \
		wget -q https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar; \
		echo "phpcs.phar downloaded!"; \
	else \
		echo "phpcs.phar is already downloaded."; \
	fi

	# For PHPCBF
	@if [ ! -f "phpcbf.phar" ]; then \
		echo "Downloading phpcbf.phar..."; \
		wget -q https://squizlabs.github.io/PHP_CodeSniffer/phpcbf.phar; \
		echo "phpcbf.phar downloaded!"; \
	else \
		echo "phpcbf.phar is already downloaded."; \
	fi

	# Make the downloaded PHAR files executable
	chmod +x phpcs.phar
	chmod +x phpcbf.phar

	# Lint the php code and check for any errors
	php phpcs.phar --standard=PSR12 form_submit.php || true

	# Continue with other targets by recursively invoking `make`
	$(MAKE) build run


build:
	# Build the docker image using the Dockerfile in this directory and create a network
	docker build -t php-test .
	
	# Create docker network but check if the network exists first
	if ! docker network inspect test_network > /dev/null 2>&1; then \
		docker network create test_network; \
	fi


run:
	# for php
	docker run -d --name php --network test -p 80:80 --env-file .env php-test:latest

	# for phpmyadmin
	docker run -d --name phpmyadmin --network test -p 8000:80 -e PMA_ARBITRARY=1 -e PMA_HOST=mysql phpmyadmin


# Target to run the entire setup process
all: install lint_dockerfile lint_php build run
```

Let's breakdown the configurations:

- **`.SILENT`:** In a Makefile, the .SILENT special target is used to suppress the normal echoing of commands that are executed during the build process. When you include .SILENT in your Makefile, it tells Make to operate in silent mode for the rules that follow it.

By default, when you run make, it displays each command it executes in the terminal. This can be helpful for understanding what's happening during the build process, but it can also lead to a lot of noise, especially for simple and repetitive commands.

- **`install`:** This target automates the installation of essential tools and dependencies, including Homebrew, Hadolint, wget, php, and Xcode Developer Tools. The `@` symbol before each command suppresses the output of the command, providing a cleaner output during the installation process.

For each tool, the Makefile checks if it is already installed using command -v followed by the tool's name. If the tool is not found, it proceeds with the installation by fetching the necessary installation script or using Homebrew to install the tool. This automation ensures a smooth and efficient setup of the development environment, allowing developers to focus on the project without the hassle of manual tool installations.

For example, when you run the command `make install`, it should return the below output if the tools are already installed, if otherwise, it will proceed to install them:
```
User-demo:php daniel$ make install
Homebrew is already installed.
Hadolint is already installed.
wget is already installed.
PHP is already installed.
Xcode Developer Tools are already installed.
```
- **`lint_dockerfile`:** This target employs [Hadolint](https://github.com/hadolint/hadolint), a Dockerfile linter, to enforce best practices for writing Dockerfiles. When the make lint_dockerfile command is executed, it automatically lints the Dockerfile in the current directory, ensuring it adheres to industry-standard guidelines.

The purpose of this target is to provide developers with a quick and automated way to validate their Dockerfiles. If the Dockerfile is free from issues or errors, Hadolint does not produce any output, potentially leading to confusion. To address this, the target includes a specified echo message to indicate that the Dockerfile has passed the linting process successfully. 
In case the Dockerfile contains issues or errors, the target will display the relevant error output in the terminal as specified by Hadolint, providing developers with valuable feedback to rectify any non-compliant code.

- **`lint_php`:** This target is responsible for linting the PHP code using [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) (PHPCS). This target serves as a linter for PHP code, ensuring it adheres to PHP coding standards, particularly the PSR-12 standard. To provide more insight into the execution process, the `set -x`  command enables verbose output, displaying the actual commands executed in the terminal during the target's execution.

The target begins by checking if the `phpcs.phar` and `phpcbf.phar` files exist in the current directory. If not, it proceeds to download them using the wget command from their official URLs. The `chmod +x` command is then used to make the downloaded PHAR files (phpcs.phar and phpcbf.phar) executable, allowing them to be run as commands.

Next, the linter executes the phpcs.phar command with the `--standard=PSR12` option, analyzing the `form_submit.php` file for any violations of the PSR-12 standard. If there are errors or warnings, they will be displayed in the terminal. The `|| true` at the end ensures that the Makefile execution continues even if this command encounters failures. This is done to prevent the linter's failure from halting the entire Makefile execution, allowing the focus to remain on the Docker-related tasks. If we have interest in fixing the code errors, we will use the `phpcbf.phar` to fix them.

- **`build`:** This target automates the process of building a Docker image based on the Dockerfile located in the current working directory. The image is tagged with the name php-test. Additionally, it checks for the existence of a Docker network named test_network, and if it doesn't exist, the target creates it.

- **`run`:** This target is responsible for starting two Docker containers: one for PHP and another for phpMyAdmin

For the php container, the command uses docker run to start a new container, `-d` flag runs the container in detached mode, meaning it runs in the background. The `--name php` option assigns the name "php" to the container. The `--network test option` connects the container to the Docker network named `test`. The `-p 80:80` option maps port 80 from the host to port 80 in the container, allowing access to the web server running inside the container. The `--env-file .env` option specifies the location of an environment file (.env), which is in the current directory, that contains environment variables for the container. `php-test` specifies the Docker image to use for the container. 

For phpmyadmin container, the command uses docker run to start another container, this time for phpMyAdmin. The `-d` flag runs the container in detached mode. The `--name phpmyadmin-test` option assigns the name `phpmyadmin-test` to the container. The `--network test` option connects the container to the same Docker network `test` as the PHP container. The `-p 8000:80` option maps port 8000 from the host to port 80 in the container, allowing access to phpMyAdmin's web interface.
The `-e PMA_ARBITRARY=1` and `-e PMA_HOST=mysql` options are environment variables for phpMyAdmin container to configure its behaviour. `phpmyadmin` specifies the Docker image to use for the phpMyAdmin container. The image is pulled from the Docker registry.

- **`all`:** This target acts as a meta-target, referencing all the targets defined in the Makefile. When you execute the command `make all`, it will sequentially execute each target in the order they are defined in the Makefile.

- To execute the Makefile and automate the build process, run the following command in the terminal:
```jsx
make all
```
The Makefile will automatically execute all the defined targets in the specified order, as described previously. This one-liner command saves you from manually performing individual tasks and ensures that the complete workflow, including installation, linting, Docker image building, and running containers, is handled seamlessly. 

After executing all the targets in the Makefile using make all, you can check your browser, `http://localhost:8000`, to access the phpMyAdmin container. However, please note that the PHP and phpMyAdmin containers are not fully functional at this point since they still need to be connected to a MySQL database for full functionality.

### Create the MySQL database
In the root of the project folder, create a folder called `mysql`, cd into the directory and create two files: `db.sql` and `makefile`
```jsx
mkdir mysql
cd mysql/
touch db.sql
touch makefile
```
- Paste the below contents into the `db.sql` file:
```
CREATE TABLE IF NOT EXISTS test (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(255),
    email VARCHAR(255),
    message TEXT,
    PRIMARY KEY (id)
);
```
The above is an SQL script that creates a table named `test` with specific column definitions. 

- **`CREATE TABLE IF NOT EXISTS`:** This statement creates the test table only if it doesn't already exist in the database. This prevents any potential errors from re-creating an existing table.

The test table has four columns:
- **`id:`** An integer column with the NOT NULL constraint, meaning it cannot contain null values. It is also defined as `AUTO_INCREMENT`, which automatically generates a unique value for each new row.
- **`name:`** A variable-length character column with a maximum length of 255 characters.
- **`email:`** Another variable-length character column with a maximum length of 255 characters.
- **`message:`** A column of the TEXT data type, which can store large amounts of text.

- Paste the below configuration into the `makefile` file:
```jsx
# Makefile for mysql

pull:
	docker pull mysql:latest

run:
	docker run -d --name mysql --network test -v ./data:/var/lib/mysql -v ./db.sql:/docker-entrypoint-initdb.d/db.sql -e MYSQL_USER=favour -e MYSQL_PASSWORD=mypassword -e MYSQL_ROOT_PASSWORD=Strongpassword@123 -e MYSQL_DATABASE=dev_to -p 3306:3306 mysql:latest

# Target to run the entire setup process
all: pull run
```

This Makefile provides a convenient way to set up a MySQL container using Docker and automates the process with the following targets:

- **`pull:`** The pull target is used to pull the latest MySQL Docker image (mysql:latest) from the Docker registry. This ensures that the latest version of the MySQL image is available locally for running the container.

- **`run:`** The run target is responsible for starting the MySQL container. It uses docker run to create and run a new container named `mysql-test`. The container is connected to a network called `test` (--network test) and mounts a local SQL file (db.sql) to the container's `/docker-entrypoint-initdb.d/db.sql` path. This allows the SQL file to be executed during container initialization, populating the database with any initial data. 

Additionally, environment variables are provided for configuration:
- **`-e MYSQL_ROOT_PASSWORD=Strongpassword@123:`** Sets the root user's password to `Strongpassword@123.`
- **`-e MYSQL_DATABASE=dev_to:`** Creates a database named `dev_to.`
- **`all:`** The `all` target acts as a meta-target, referencing both pull and run. 

**NB:** Keep it in mind that important credentials shouldn't be passed directly on the command line, we are passing them directly here just to show the process.

- To create the database, run the makefile with the following command from inside the mysql directory:
```jsx
make all
```
When you execute make all, it will execute both targets in the specified order. This allows you to pull the latest MySQL image and then run the MySQL container with the necessary configurations in a single command.

Now on your browser, visit `localhost` to see the form.html file being served by apache. When you fill the form, if the input you gave is successful saved in the database, you will be redirected to `localhost:80` where php is running. You will get a php message saying it was successful, if it wasn't, php will still print out an error message.


## With Docker-Compose
After using Makefiles to run the containers as well as other processes, we will use Docker Compose which is an effective container orchestration tool to run the containers.

### Environment Variables
We need to first create environment variables for the MySQL container to use since it is not good practice to pass sensitive information directly.

In the root of the folder, create a `.env` file:
```jsx
touch .env
```
- Paste the below into it
```
MYSQL_PASSWORD=mypassword
MYSQL_ROOT_PASSWORD=Strongpassword@123
DB_HOST=mysql
MYSQL_DATABASE=dev_to
DB_USER=favour
```
Replace with the appropriate attributes.

### Docker Compose

Now that the environment variables have been set, we can proceed to writing the Docker Compose file.

In the root of the project folder, create a file called `docker-compose.yml`:
```jsx
touch docker-compose.yml
```
- Paste the below contents into the file:
```jsx
version: '3'
services:
  php:
    env_file:
      - .env
    build:
      context: ./php
      args:
        - --no-cache
      dockerfile: Dockerfile
    ports:
      - "80:80"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/"]
      interval: 10s
      timeout: 5s
      retries: 3
    container_name: php
    networks:
      - backend

  mysql:
    env_file:
      - .env
    image: mysql:latest
    restart: always
    ports:
      - "3306:3306"
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      timeout: "5s"
      interval: "10s"
      start_period: "3s"
      retries: 5
    environment:
      MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}"
      MYSQL_DATABASE: "${MYSQL_DATABASE}"
      MYSQL_USER: "${DB_USER}"
      MYSQL_PASSWORD: "${MYSQL_PASSWORD}"
    container_name: mysql
    volumes:
      - ./mysql/db.sql:/docker-entrypoint-initdb.d/db.sql
      - ./mysql/mysql_data:/var/lib/mysql
    networks:
      - backend

  phpmyadmin:
    image: phpmyadmin:latest
    container_name: phpmyadmin
    ports:
      - 8000:80 # Expose phpMyAdmin on port 8000
    restart: always
    environment:
      PMA_ARBITRARY: 1 # Use the value '1' for arbitrary hostname resolution
      PMA_HOST: "${DB_HOST}" # Use the container name of the mysql service as the host
    depends_on:
      - mysql     
    networks:
      - backend

networks:
  backend:
    driver: bridge
```
The above docker-compose file defines a multi-container environment using Docker Compose, allowing you to run and manage multiple services (containers) together as part of a single application.

Breaking down the files to bits:

- **`Version:`** The file specifies the version of Docker Compose syntax being used. In this case, it's using version 3.

- **`Services:`** This section defines three services (containers): php, mysql, and phpmyadmin.

- **`php service:`** The php service is built using the Dockerfile located in the ./php directory. It uses environment variables defined in the .env file. The service is accessible on port 80 and has a health check to test the health of the container by making an HTTP request to `http://localhost/`.

- **`mysql service:`** The mysql service uses the official MySQL image (mysql:latest) from Docker Hub. It reads environment variables from the .env file to set up MySQL's root password, database, user, and password. The container is accessible on port 3306, and it has a health check to verify the health of the MySQL server by pinging it.

- **`phpmyadmin service:`** The phpmyadmin service uses the official phpMyAdmin image (phpmyadmin:latest) from Docker Hub. It exposes phpMyAdmin on port 8000 and depends on the mysql service. Environment variables are set to configure phpMyAdmin to connect to the MySQL container.

- **`Volumes:`** The mysql service mounts two volumes: `./mysql/db.sql` to initialize the database with an SQL file and `./mysql/mysql_data` to persist MySQL data.

- **`Networks:`** The backend network is created to allow communication between the services (php, mysql, phpmyadmin).

- To run the containers, in the root directory where the docker compose file exists, run the following command:
```
docker compose up -d
```
This command automatically builds the Docker image and runs the containers.

 == we didn't persist data for makefile command

- To bring down the containers:
```
docker compose down
```

### Container Health Checks
Going by standard and best practices, containers should have health checks to ensure they are running properly and responding to requests as expected. Health checks have already been defined in the Docker Compose file, ensuring that the containers are periodically monitored for their health status. When you run the command `docker ps -a`, you should see the containers listed with their health status displayed under the `STATUS` column.
```
CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS                    PORTS                               NAMES
eb6e3ddc3e53   phpmyadmin:latest     "/docker-entrypoint.…"   17 seconds ago   Up 15 seconds (healthy)   0.0.0.0:8000->80/tcp                phpmyadmin
1f1abdcbd82d   mysql:latest          "docker-entrypoint.s…"   17 seconds ago   Up 15 seconds (healthy)   0.0.0.0:3306->3306/tcp, 33060/tcp   mysql
2b91dfbbb74d   module-2-php          "docker-php-entrypoi…"   17 seconds ago   Up 15 seconds (healthy)   0.0.0.0:80->80/tcp                  php
```
At initial start of the containers, the STATUS column will show (health: starting) since the container is in the process of starting, and the health check is currently being evaluated. The status of a container can also be `unhealthy`. This status indicates that the container's health check has failed, signaling that there might be an issue with the container or its underlying application. If the status shows exited, it means that the container has stopped running, either because it has completed its task or due to an error.

Health checks can also be done manually
- To run health checks manually on your container, for the `mysql` container, run:
```
docker inspect --format='{{json .State.Health.Status}}' <container name>
```
and it should return an output of the container's health status
<img width="819" alt="Screenshot 2023-07-26 at 11 01 58" src="https://user-images.githubusercontent.com/89241109/257146802-4cf90e33-0a92-40b4-ab66-ae122b56a3a2.png">

- Do the same for the php container and then the phpmyadmin container

<img width="819" alt="Screenshot 2023-07-26 at 11 07 50" src="https://user-images.githubusercontent.com/89241109/257146481-dbdbd767-c3ed-4443-9223-282a76567b09.png">