How to Develop a Simple Web Application Using Docker-compose, Nginx, PHP 8, and MongoDB 6
Tags: webdev, mongodb, php, beginners
Published: 2023-03-14 19:16:06

I will give you a brief introduction to setting up an environment for developing a PHP application using MongoDB. 

I use the environment for local development and release the application on the server if the application is not business-critical.

For example, one application reads and processes AWS price lists (in JSON format), stores them in a database, and uses the data to process, report, and publish them to the Web site for review.

The environment uses
- Docker-compose
- Nginx web server
- PHP 8.2 (PHP-FPM) + Composer
- MongoDB 6

## Preparing Docker image with PHP + Composer + packages for MongoDB

You need to create a Dockerfile in the project directory

_Dockerfile_
```
FROM php:8.2-fpm

RUN apt-get -y update \
    && apt-get install -y libssl-dev pkg-config libzip-dev unzip git

RUN pecl install zlib zip mongodb \
    && docker-php-ext-enable zip \
    && docker-php-ext-enable mongodb

# Install composer (updated via entry point)
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
```

Run Image build from Dockerfile

```
docker build -t php8.2-fpm-mongo .
```

After completing the build, this Image can be used in the docker-compose.yml file, there will already be Composer and the libraries needed to run MongoDB.

## Preparing Docker-compose with Nginx + PHP

You need to create a docker-compose.yml file in the application directory.

_docker-compose.yml_
```
version: '3.9'

services:
  web:
    image: nginx:latest
    ports:
      - '80:80'
    volumes:
      - ./app:/var/www/html
      - ./config/default.conf:/etc/nginx/conf.d/default.conf

  php-fpm:
    image: php8.2-fpm-mongo
    volumes:
      - ./app:/var/www/html
```

You also need to create a directory `app/` and an `index.php` file in it, which will be our application.

_app/index.php_
```
<?php

phpinfo();

exit;
```

You will also need to create a Nginx default.conf configuration file. I placed it in the config/ folder in the project directory

_config/default.conf_
```
server {
    listen 80;
    server_name localhost;
   
    index index.php index.html;

    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/html;

    rewrite ^/(.*)/$ /$1 permanent; 

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

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php-fpm:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_buffering off;
    }
}
```

Structure of files and folders
```
.
├── app
│    └── index.php
├── config
│    └── default.conf
├── Dockerfile
└── docker-compose.yml
```

## Checking Nginx + PHP

Run docker-compose
```
docker-compose up -d
```

Open `localhost` in the browser.

If everything is correct, you should see phpinfo.

## Installing PHP libraries into the project with Composer required by MongoDB 

You need to create a `composer.json` file in the app folder to install the packages and php extensions that are required for MongoDB.

app/composer.json
```
{
    "require": {
        "mongodb/mongodb": "^1.6",
        "ext-mongodb": "^1.6"
    }
}
```

To install packages you need to connect to the container with php-fpm: 
```
docker exec -it [php-fpm-container] bash
```
The container name becomes known when you run `docker-compose up -d`

Once you are connected to the container, install composer packages with the command:
```
composer install
```

I think all went well and your project now has a vendor directory with installed packages and libraries for MongoDB

## Adding MongoDB image to Docker-compose

You need to add the `mongodb` service to the previously created `docker-compose.yml`

Stop launched containers with the command
```
docker-compose down
```

Add a new service to docker-compose.yml
```
  mongodb:
    image: "percona/percona-server-mongodb:6.0.4"
    # image: "percona/percona-server-mongodb:6.0.4-3-arm64" # For Apple M1/M2
    volumes:
      - ./data:/data/db
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: secret
      MONGO_INITDB_DATABASE: tutorial
    ports: 
      - "27017:27017"
```

I use [Percona Server for MongoDB](https://www.percona.com/software/mongodb/percona-server-for-mongodb?utm_source=percona-community&utm_medium=blog&utm_campaign=daniil) as a free open-source alternative to MongoDB.

You will also need to modify the php-fpm service a bit to include environment variables

```
  php-fpm:
    image: php8.2-fpm-mongo
    volumes:
      - ./app:/var/www/html
    environment:
      DB_USERNAME: root
      DB_PASSWORD: secret
      DB_HOST: mongodb # matches the service with mongodb
```

This way the final `docker-compose.yml` will be
```
version: '3.9'

services:
  web:
    image: nginx:latest
    ports:
      - '80:80'
    volumes:
      - ./app:/var/www/html
      - ./config/default.conf:/etc/nginx/conf.d/default.conf

  php-fpm:
    image: php8.2-fpm-mongo
    volumes:
      - ./app:/var/www/html
    environment:
      DB_USERNAME: root
      DB_PASSWORD: secret
      DB_HOST: mongodb # matches the service with mongodb

  mongodb:
    image: "percona/percona-server-mongodb:6.0.4"
    # image: "percona/percona-server-mongodb:6.0.4-3-arm64" # For Apple Silicon M1/M2
    volumes:
      - ./data:/data/db
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: secret
      MONGO_INITDB_DATABASE: tutorial
    ports: 
      - "27017:27017"
```

Run docker-compose
```
docker-compose up -d
```

## Let's connect to MongoDB in a PHP application and write the test data

You need to modify the `app/index.php` file
```
<?php

// Enabling Composer Packages
require __DIR__ . '/vendor/autoload.php';

// Get environment variables
$local_conf = getenv();
define('DB_USERNAME', $local_conf['DB_USERNAME']);
define('DB_PASSWORD', $local_conf['DB_PASSWORD']);
define('DB_HOST', $local_conf['DB_HOST']);

// Connect to MongoDB
$db_client = new \MongoDB\Client('mongodb://'. DB_USERNAME .':' . DB_PASSWORD . '@'. DB_HOST . ':27017/');

$db = $db_client->selectDatabase('tutorial');

// Create an index
$db->pages->createIndex(['page_id' => 1]);

// Test insert data
for ($page = 1; $page <= 1000; $page++) {

    $data = [
        'page_id' => $page, 
        'title' => "Page " . $page,
        'date' => date("m.d.y H:i:s"),
        'timestamp' => time(),
        'mongodb_time' => new MongoDB\BSON\UTCDateTime(time() * 1000)
    ];

    $updateResult = $db->pages->updateOne(
        [
            'page_id' => $page // query 
        ],
        ['$set' => $data],
        ['upsert' => true]
    );

    echo $page . " " ;
}
echo '<br/>Finish';
exit;
```

This is a simple script that:
1. Reads environment variables
2. Connects to the database
3. Creates Index by key
4. Writes 1000 test documents in a loop.

You can run `localhost` in your browser.

## Checking database records with MongoDB Compass

Install [MongoDB Compass](https://www.mongodb.com/try/download/compass), it's a great tool to check the results of your experiments.

Use localhost as host and user/pass from docker-compose.yml
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qvqdejqx7w4ntcsnzdjw.png)

## Conclusion

The environment and an example of a minimal application are ready. It can be expanded and improved by adding logic and functions.

Suggestions and comments are welcome. 

The project files and example code are available in [the Github repository](https://github.com/dbazhenov/nginx-php-mongodb-docker-compose)

In the next part, you will learn how to improve the script to make HTTP requests to the API using the GitHub API as an example. [Link to next part](https://dev.to/dbazhenov/how-to-make-http-requests-to-api-in-php-app-using-github-api-example-and-write-to-percona-server-for-mongodb-3gi3)