Implementing Robust Web Applications with Docker, PHP, Nginx, and MySQL: A Practical Guide

Manasbhole
12 min readMar 5, 2023

--

Photo by Ben Griffiths on Unsplash

Introduction

PHP is a popular server-side scripting language used for developing web applications, while Nginx is a lightweight web server that can also be used as a reverse proxy, load balancer, and HTTP cache. In this blog, we will explore how to set up a PHP and Nginx server on Ubuntu 20.04 and configure it for optimal performance.

Prerequisites Before we begin, you will need to have a few things in place:

  • An Ubuntu 20.04 server with sudo access
  • Nginx and PHP installed
  • A domain name pointing to the server’s IP address

Step 1: Installing PHP and Nginx First, update the package index on your Ubuntu server:

sudo apt update

Then, install Nginx and PHP-FPM (FastCGI Process Manager) with the following command:

sudo apt install nginx php-fpm

Step 2: Configuring Nginx Next, we need to configure Nginx to work with PHP. Open the default Nginx configuration file with your favorite text editor:

sudo nano /etc/nginx/sites-available/default

In this file, find the following lines:

# pass PHP scripts to FastCGI server
#
#location ~ \.php$ {
# include snippets/fastcgi-php.conf;
#
# # With php-fpm (or other unix sockets):
# fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
# # With php-cgi (or other tcp sockets):
# # fastcgi_pass 127.0.0.1:9000;
#}

Uncomment these lines by removing the ‘#’ symbol and update the fastcgi_pass line to use the correct PHP version (in this example, PHP 7.4):

location ~ \.php$ {
include snippets/fastcgi-php.conf;

fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}

Save and close the file, then test the Nginx configuration and restart the service:

sudo nginx -t
sudo systemctl restart nginx

Step 3: Creating a PHP Test File To test that PHP is working with Nginx, create a PHP file in the default web root directory (/var/www/html/) with the following command:

sudo nano /var/www/html/info.php

Add the following code to this file:

<?php
phpinfo();
?>

Save and close the file.

Step 4: Testing the Server To test that everything is working correctly, open a web browser and visit your server’s domain name or IP address followed by ‘/info.php’. For example:

http://your-domain.com/info.php

You should see a page displaying detailed information about your PHP installation.

Step 5: Optimizing PHP and Nginx for Performance To optimize PHP and Nginx for performance, we can make some additional configuration changes. First, let’s modify the default PHP-FPM configuration file:

sudo nano /etc/php/7.4/fpm/php.ini

Find the following lines:

;cgi.fix_pathinfo=1

Uncomment this line by removing the ‘;’ symbol and set the value to 0:

cgi.fix_pathinfo=0

Save and close the file.

Next, we can modify the Nginx configuration file to enable caching and gzip compression. Open the default Nginx configuration file again:

sudo nano /etc/nginx/sites-available/default

Add the following lines to the

location ~* .(js|css|png|jpg|jpeg|gif|ico)$ {
expires 1y;
add_header Cache-Control "public, no-transform";
gzip_static on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
gzip_vary on;
try_files $uri $uri/ /index.php?$query_string;
}

Save and close the file, then test the Nginx configuration and restart the service:

sudo nginx -t
sudo systemctl restart nginx

These changes will enable caching for static files and gzip compression for better performance.

Next, we can modify the Nginx configuration file to enable caching and gzip compression. Open the default Nginx configuration file again and add the following changes to enable Simple HTTP Load Balancing and Subrequest Authentication:

upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}

server {
listen 80;
server_name your-domain.com;

location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

location /admin/ {
auth_request /auth;

proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

location = /auth {
internal;
proxy_pass http://localhost:8081/auth;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}

location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
expires 1y;
add_header Cache-Control "public, no-transform";
gzip_static on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
gzip_vary on;
try_files $uri $uri/ /index.php?$query_string;
}
}

This configuration sets up a simple HTTP load balancer with two backend servers (192.168.1.10 and 192.168.1.11) and enables subrequest authentication for the /admin/ location using an internal request to a separate authentication server running on port 8081. The location block for static files has also been updated to enable caching and gzip compression.

Save and close the file, then test the Nginx configuration and restart the service:

sudo nginx -t
sudo systemctl restart nginx
Photo by Rubaitul Azad on Unsplash

Docker can be used in conjunction with the PHP and Nginx server we have set up: Docker is a powerful tool for containerization, which allows developers to package applications and dependencies into lightweight, portable containers that can be deployed easily across different environments. By using Docker to deploy the PHP and Nginx server, we can ensure that the environment is consistent across different machines, making it easier to deploy and scale the application.

To use Docker with our PHP and Nginx server, we will need to create a Dockerfile that defines the environment and dependencies needed to run the application. Here’s an example Dockerfile that builds on the configuration we’ve set up so far:

FROM ubuntu:20.04

# Update package repositories and install necessary packages
RUN apt-get update && apt-get install -y \
nginx \
php-fpm \
php-mysql \
php-curl \
php-gd \
php-xml \
php-mbstring \
php-zip \
supervisor

# Copy the Nginx configuration file and PHP test file to the container
COPY nginx.conf /etc/nginx/nginx.conf
COPY index.php /var/www/html/index.php

# Copy the supervisor configuration file to the container
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# Expose ports 80 and 443 for Nginx
EXPOSE 80 443

# Start supervisor
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

This Dockerfile sets up a Ubuntu 20.04 base image and installs Nginx, PHP-FPM, and other necessary PHP extensions. It also copies over the Nginx configuration file, PHP test file, and supervisor configuration file we created earlier. Finally, it exposes ports 80 and 443 for Nginx and starts supervisor to manage the Nginx and PHP processes.

To build the Docker image, save the Dockerfile in a directory along with the Nginx configuration file, PHP test file, and supervisor configuration file, and run the following command:

docker build -t my-php-app .

This will create a Docker image named “my-php-app” based on the Dockerfile in the current directory.

To run the Docker container, use the following command:

docker run -p 80:80 -p 443:443 my-php-app

This will start the container and map ports 80 and 443 on the host machine to the same ports in the container.

By using Docker to deploy the PHP and Nginx server, we can ensure that the environment is consistent and reproducible across different machines and environments. This can make it easier to deploy and scale the application, as well as simplify testing and development. Additionally, by using Docker with our PHP and Nginx server, we can take advantage of Docker’s built-in load balancing and scaling features, which can further improve the performance and scalability of the application.

There are several benefits of using Docker specifically with PHP and Nginx:

  1. Consistent environment: Docker provides a consistent environment for running applications, regardless of the underlying host system. By using Docker with PHP and Nginx, developers can ensure that their application runs in the same environment across different machines, making it easier to deploy and manage the application.
  2. Portability: Docker containers are portable and can be deployed on any system that supports Docker. This makes it easier to move the application between different environments, such as from development to testing or production.
  3. Isolation: Docker containers provide isolation between the application and the host system, which can help to prevent conflicts and ensure that the application runs smoothly. This can also improve the security of the application, as any vulnerabilities or exploits in the container will be contained and isolated from the host system.
  4. Scalability: Docker provides built-in load balancing and scaling features, which can make it easier to scale the application horizontally across multiple containers. This can improve the performance and scalability of the application, and allow it to handle more traffic and users.
  5. Efficiency: Docker containers are lightweight and consume fewer resources than traditional virtual machines. This can improve the efficiency and performance of the application, and reduce the costs associated with running and managing the infrastructure.

Overall, using Docker with PHP and Nginx can provide significant benefits in terms of consistency, portability, isolation, scalability, and efficiency. It is a powerful tool for containerizing and deploying applications, and can help developers to create fast, efficient, and scalable web servers that are easy to deploy and manage.

How you can use Nginx, PHP, and Docker together to serve a PHP application:

Create a Dockerfile that defines the environment and dependencies needed to run the PHP application, along with the Nginx server. Here’s an example Dockerfile:

FROM php:7.4-fpm

# Install Nginx and other necessary packages
RUN apt-get update && apt-get install -y \
nginx \
supervisor

# Copy the Nginx configuration file to the container
COPY nginx.conf /etc/nginx/nginx.conf

# Copy the PHP application files to the container
COPY index.php /var/www/html/index.php

# Copy the supervisor configuration file to the container
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# Expose ports 80 and 443 for Nginx
EXPOSE 80 443

# Start supervisor
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

This Dockerfile sets up a PHP-FPM environment with Nginx, installs necessary packages, and copies the Nginx configuration file, PHP application files, and supervisor configuration file to the container.

Create an Nginx configuration file that defines how Nginx should serve the PHP application. Here’s an example Nginx configuration file:

server {
listen 80;
listen [::]:80;

root /var/www/html;
index index.php;

server_name localhost;

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

location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}

This Nginx configuration file sets up a server block that listens on port 80 and serves the PHP application. It defines the root directory for the PHP application, sets the index file to index.php, and defines the server name. The location blocks define how Nginx should handle requests for static files and PHP files.

Build the Docker image using the Dockerfile:

docker build -t my-php-app .

This will create a Docker image named “my-php-app” based on the Dockerfile in the current directory.

Run the Docker container:

docker run -p 80:80 my-php-app

This will start the container and map port 80 on the host machine to port 80 in the container.

With these steps, you can use Nginx, PHP, and Docker together to serve a PHP application. The Docker container provides a consistent and isolated environment for the application, while Nginx serves the application and provides efficient load balancing and scaling.

You can use Docker to handle the MySQL database configuration for your PHP and Nginx application. Docker provides official MySQL images that you can use to create a containerized MySQL instance.

Here are the steps to set up a MySQL database using Docker:

Pull the official MySQL Docker image:

docker pull mysql

Run the MySQL container with a specified root password and a volume to persist the data:

docker run --name mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -v /my/own/datadir:/var/lib/mysql -d mysql

This will create a new container named “mysql” with a specified root password and a volume mounted at “/my/own/datadir” to persist the data.

Connect to the MySQL container and create a new database and user:

docker exec -it mysql mysql -uroot -p

mysql> CREATE DATABASE my_database;
mysql> CREATE USER 'my_user'@'%' IDENTIFIED BY 'my_password';
mysql> GRANT ALL PRIVILEGES ON my_database.* TO 'my_user'@'%';
mysql> FLUSH PRIVILEGES;
mysql> exit

This will connect to the MySQL container, create a new database named “my_database”, create a new user named “my_user” with the password “my_password”, grant all privileges on the “my_database” to the “my_user”, flush the privileges, and exit the MySQL console.

Configure your PHP application to use the MySQL database:

$host = 'mysql';
$username = 'my_user';
$password = 'my_password';
$database = 'my_database';

$conn = mysqli_connect($host, $username, $password, $database);

This will create a new mysqli connection to the MySQL container using the hostname “mysql”, the username “my_user”, the password “my_password”, and the database “my_database”.

With these steps, you can use Docker to handle the MySQL database configuration for your PHP and Nginx application. This provides a consistent and isolated environment for the database, and makes it easier to manage and deploy the application.

  1. Benefits of using Docker with PHP and Nginx
  • Consistency: Docker allows you to create a consistent environment for your application, including the operating system, web server, and database. This ensures that the application runs the same way in every environment, which simplifies deployment and troubleshooting.
  • Isolation: Docker containers provide an isolated environment for each component of the application, which reduces conflicts and improves security. For example, if one container is compromised, it cannot affect other containers or the host system.
  • Scalability: Docker makes it easy to scale the application by adding or removing containers as needed. This can be done manually or automatically based on metrics such as CPU usage or incoming traffic.
  • Portability: Docker containers can be easily moved between environments, such as development, testing, and production. This makes it easier to test and deploy the application across different platforms and configurations.

2. Using Docker Compose to manage the containers

Docker Compose is a tool that allows you to define and manage multiple containers as a single application. You can use a YAML file to define the services (i.e., containers) that make up the application, along with their configuration, dependencies, and networking.

Here’s an example docker-compose.yml file for a PHP-Nginx-MySQL application:

version: '3'
services:
web:
build: .
ports:
- "8080:80"
depends_on:
- db
db:
image: mysql:latest
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: example
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:

In this example, we define two services: web for the PHP-Nginx application, and db for the MySQL database. We specify the build context for the web service (i.e., the location of the Dockerfile), the port mapping from the host to the container for the web service, and the MySQL configuration for the db service. We also define a named volume for the MySQL data to persist between container restarts.

You can then use the docker-compose command to start, stop, and manage the application as a whole:

docker-compose up -d     # start the application in detached mode
docker-compose ps # show the status of the containers
docker-compose logs web # show the logs of the web container
docker-compose down # stop and remove the containers

Handling the MySQL database configuration with Docker

As mentioned earlier, you can use the official MySQL Docker image to create a containerized MySQL instance. You can also use environment variables to configure the MySQL instance, such as the root password, database name, and user credentials.

Here’s an updated docker-compose.yml file that includes the MySQL service:

version: '3'
services:
web:
build: .
ports:
- "8080:80"
depends_on:
- db
db:
image: mysql:latest
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: example
MYSQL_USER: example
MYSQL_PASSWORD: example
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:

In this example, we define the MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER, and MYSQL_PASSWORD environment variables for the MySQL container. We also specify a named volume for the MySQL data to persist between container restarts. To connect to the MySQL instance from the PHP-Nginx application, you can use the mysqli extension in PHP. Here's an example index.php file that connects to the database and retrieves some data:

<?php
$host = 'db';
$user = 'example';
$pass = 'example';
$dbname = 'example';

$conn = new mysqli($host, $user, $pass, $dbname);

if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}

$sql = "SELECT * FROM users";
$result = $conn->query($sql);

if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
echo "Name: " . $row["name"] . " - Email: " . $row["email"] . "<br>";
}
} else {
echo "0 results";
}

$conn->close();
?>

In this example, we use the mysqli constructor to connect to the MySQL instance using the db hostname (which is the name of the MySQL service in the docker-compose.yml file), the example user and password, and the example database. We then execute a simple SELECT query and display the results.

Note that we use the hostname db to connect to the MySQL service, rather than localhost or 127.0.0.1. This is because Docker creates a separate network for the containers, and each container can resolve the hostname of other containers by their service name.

Conclusion:

We explored how to set up a PHP and Nginx server on Ubuntu 20.04 and configure it for optimal performance, including Simple HTTP Load Balancing and Subrequest Authentication. We installed PHP and Nginx, configured Nginx to work with PHP, created a PHP test file, and optimized PHP and Nginx for performance. We also added Simple HTTP Load Balancing and Subrequest Authentication to the configuration for improved security and scalability. By following these steps, you can create a fast, efficient, and secure web server for your PHP applications. By using Docker with our PHP and Nginx servers, we can create a fast, efficient, and scalable web server that is easy to deploy and manage across different environments. By following the steps outlined in this blog, you can set up a PHP and Nginx server with Docker and configure it for optimal performance and scalability. Implementing Docker with PHP, Nginx, and MySQL can provide many benefits, such as consistency, isolation, scalability, and portability. Docker Compose allows you to define and manage multiple containers as a single application, while the official Docker images for PHP, Nginx, and MySQL make it easy to get started. By using environment variables, you can configure the containers to meet your specific requirements, including the MySQL database. Finally, by using the mysqli extension in PHP, you can connect to the MySQL instance and retrieve data from your application.

--

--