How to run Nginx and PHP in Docker
Tags: webdev, docker, php, tutorial
Published: 2021-11-22 13:16:00

Today we will try to run nginx web server with PHP 7.4 in Docker. We will use Docker, because we will not install any software on our host system. You should have installed on your system Docker and Docker-compose. If you don’t know how to install Docker and Docker-compose see my previous article [How to install Docker in Linux Mint and Ubuntu](https://varlock.net/how-to-install-docker-in-linux-mint-and-ubuntu/).

All files you need to run Nginx and PHP are on my [Github](https://github.com/texe/nginxphp). Let’s get start!

First we need to clone repository and create network:

```shell
git clone https://github.com/texe/nginxphp
cd nginxphp
docker network create labnet
```

Why we create network?. It’s a very good question. In our case we do it, because we want have a situation where all our containers will be visible by each other. For instance, imagine we have  four containers with four services:

- PHP
- Nginx
- MySQL
- phpMyAdmin

And we want:

- Nginx be able to see PHP server
- PHP server should see MySQL server
- phpMyAdmin should see MySQL server

In order to do it we create a virtual network and every service in this virtual network will be able to see other services. That’s why we have to create a new network in docker environment.

Now we have to build our image and we will do it by this command:

```shell
docker-compose build
```

Docker will is starting building an image and downloading all needed files. It can take some time. How much? It depends on your internet connection. Let’s take a look for details.

Docker checks docker-compose.yml file and and see that we want to build new image described in config/dockerfile. Let’s see what is in this file:

```dockerfile
# 1 Set master image
FROM php:7.4-fpm-alpine

# 2 Set working directory
WORKDIR /var/www/html

# 3 Install Additional dependencies
RUN apk update && apk add --no-cache \
build-base shadow vim curl \
php7 \
php7-fpm \
php7-common \
php7-pdo \
php7-pdo_mysql \
php7-mysqli \
php7-mcrypt \
php7-mbstring \
php7-xml \
php7-openssl \
php7-json \
php7-phar \
php7-zip \
php7-gd \
php7-dom \
php7-session \
php7-zlib

# 4 Add and Enable PHP-PDO Extenstions
RUN docker-php-ext-install pdo pdo_mysql mysqli
RUN docker-php-ext-enable pdo_mysql

# 5 Install PHP Composer
#RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# 6 Remove Cache
RUN rm -rf /var/cache/apk/*

# 7 Add UID '1000' to www-data
RUN usermod -u 1000 www-data

# 8 Copy existing application directory permissions
COPY --chown=www-data:www-data . /var/www/html

# 9 Change current user to www
USER www-data

# 10 Expose port 9000 and start php-fpm server
EXPOSE 9000
CMD ["php-fpm"]
```

I numbered all comment lines and now explain what every line do:

1. This is a base image. I choose official PHP image based on Alpine Linux (php:7.4-fpm-alpine). It is very light and fast distribution. I always take Alpine if it’s possible because images based on Ubuntu are much bigger than Alpine.
2. Setting here working directory. Nothing to explain in this place…
3. Update software repositories and install PHP with extensions.
4. We add and enable database drivers. In this case we add PDO, PDO MySQL and Mysqli (it can be necessary for WordPress).
5. Here we can install Composer. I commented this line because I don’t want to install Composer at this moment. But if you want to use Composer you can uncomment this line.
6. Remove cache in order to save disk quota.
7. Add UUID 1000 to user www-data (Nginx user).
8. Set directory permissions to directory with PHP files. We give access to every user with UUID 1000.
9. Change current user to www-data. It’s a user which can operate on our php files.
10. We expose our service to port 9000 and run php-fpm server.

Now let’s look to docker-compose.yml file:

```yaml
version: '3'
services:

  #PHP App
  app:
    build:
      context: .
      dockerfile: config/dockerfile
    image: christexe/php:7.4-fpm-alpine
    container_name: php_app
    restart: unless-stopped
    tty: true
    environment:
      SERVICE_NAME: app
      SERVICE_TAGS: dev
    working_dir: /var/www/html
    volumes:
      - ./code/:/var/www/html
      - ./config/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
    networks:
      - labnet

  #Nginx Service
  nginx:
    image: nginx:alpine
    container_name: nginx
    restart: unless-stopped
    tty: true
    ports:
      - "80:80"
    volumes:
      - ./code/:/var/www/html
      - ./config/conf.d/:/etc/nginx/conf.d/
    networks:
      - labnet

#Docker Networks
networks:
  labnet: 
    external:
      name: labnet
```

What does mean every line?

```yaml
#PHP App
  app:
    build:
      context: .
      dockerfile: config/dockerfile
```

This means that we create first service “app”. We will build a new image. The new image will be based on dockerfile (in “config” directory). As I wrote above, Docker parses every line in docker-compose.yml, search dockerfile and build new image.

The name of the new image will be: `christexe/php:7.4-fpm-alpine`.

The name of container will be: `container_name: php_app`.

`restart: unless-stopped` means that our PHP container always will start after operating system boot, **unless you stop it**. If you manually stop this container, after rebooting hos system, the container will not start automatically.

`tty: true` means that we want to get access to console (tty) in our PHP server. It can be useful when you need to get in container.

Any `RUN`, `CMD`, `ADD`, `COPY`, or `ENTRYPOINT` command will be executed in the specified working directory.

```
volumes: - ./code/:/var/www/html - ./config/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
```

We map directory `code` to `/var/www/html in our container`. We will put in this directory all php files (index.php etc.).

file `uploads.ini` to `/usr/local/etc/php/conf.d/uploads.ini` This is vasic PH configuration file. In this file we allow to uploads and limit upload file size.

```
networks: - labnet
```

We assign our container to `labnet` network (explained above).

In the nginx service we repeat most of docker parameters. We added there:

```
ports: - "80:80"
```

We forward port 80 in our host machine to port 80 in our container with nginx. The second different thing volumes:

```
volumes: - ./code/:/var/www/html - ./config/conf.d/:/etc/nginx/conf.d/
```

We repeat mapping directory `code` to `/var/www/html` – this is default directory where nginx search html files. We also map directory `config/conf.d` to `/etc/nginx/conf.d/`. This is default directory where nginx search site configuration files. In this directory we have `site.conf` file:

```nginx
server {
    listen 80;
    server_name VB-Mint20;
    
# Log files for Debug
error_log  /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;

# Laravel web root directory
root /var/www/html;
index index.php index.html;

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

# Nginx Pass requests to PHP-FPM
location ~ \.php$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass app:9000;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    client_max_body_size 256M;
}
}
```

As you see nginx is listening on port 80, looking for index.php/index.html and the last section (Nginx Pass requests to PHP-FPM) connect PHP with NGINX. In this section we show what NGINX  should do with php files. The last line in this section client_max_body_size 256M; is not required but this line allow upload big files (256 MB) to server via web browser.

That’s all! You have a dockerized web server with PHP. Open your browser and type localhost in your address bar:

![phpinfo();](https://varlock.net/wp-content/uploads/sites/1/phpinfo.min_.png)

## Bonus!

If you don’t want to build your own image, you can download it from [DockerHub](https://hub.docker.com/r/christexe/php/tags). But warning! You should delete these lines from docker-compose.yml file:

```yaml
 build:
      context: .
      dockerfile: config/dockerfile
```

After removing above lines, when you start container (docker-compose  up) Docker will pull the image automatically from Docker Hub. The  dockerfile is of course unnecessary in this case.

All you read in this article you can watch on my video:

{% youtube rtl0liw05qo %}

------

*It would be great if you will comment or follow me on social media*:

[![Chris Texe Twitter](https://madlon.eu/wp-content/uploads/sites/21/TwitterTexeButton.webp)](https://twitter.com/TexeChris)

[![Chris Texe LinkedIn](https://madlon.eu/wp-content/uploads/sites/21/LinkedInTexeButton.webp)](https://www.linkedin.com/in/texe/)

Also you can visit my websites:

- [Linux blog](https://varlock.net)
- [Web Agency website](https://madlon.eu)