Ambiente PHP e Ngnix com Docker
Tags: docker, php, nginx, devops
Published: 2021-11-19 16:56:38

Há alguns dias atrás precisei criar ou recriar um ambiente PHP semelhante ao ambiente de produção, em produção era utilizado o PHP na versão 7.2, com o Nginx na versão 1.14.0 e também o PHP-FPM que é o cara responsável por interpretar o código PHP já que o Nginx não consegue fazer isso, mais isso tudo diretamente pelo sistema usando unix sockets, tudo instalado diretamente no sistema. 

Porém tive algumas dificuldades para criar o ambiente usando o fastcgi e então resolvi escrever este artigo para ajudar outras pessoas que venham a precisar e também para o meu eu do futuro que pode vir a precisar fazer algo semelhante.

## Dockerfile

Bem, primeiro precisamos criar um Dockerfile na raiz do projeto para termos uma imagem com o nosso projeto php, e já com o php-fpm. Podemos ter o Dockerfile o seguinte conteúdo:

```yml
FROM php:7.2.24-fpm
COPY . /var/www/html
WORKDIR /var/www/html
RUN chmod -R 777 /var/www/html
EXPOSE 9000
CMD ["php-fpm"]
```

### Explicando o Dockerfile

- `FROM php:7.2.24-fpm`: Com isso vamos ter uma imagem com o php-fpm na versão 7.2.24.

- `COPY . /var/www/html`: Aqui fazemos a cópia de todos os arquivos do projeto (`.`) para a pasta `/var/www/html` do container.

- `WORKDIR /var/www/html`: E precisamos mudar o diretório de trabalho para que seja o primeiro e consigamos ter acesso aos arquivos nesse container.

- `RUN chmod -R 777 /var/www/html`: Esse comando é para evitar possíveis erros caso o projeto precise criar ou gerenciar arquivos dentro do container.

- `EXPOSE 9000`: Aqui liberamos a porta 9000 para podermos acessar de fora, ou no caso em outros conatiners.

- `CMD ["php-fpm"]`: Por fim, executamos o php-fpm.

## docker-compose

Agora que temos o Dockerfile, precisamos criar um `docker-compose.yml` na raiz do projeto para usarmos o Dockerfile criado anteriormente. E nesse docker-compose temos o seguinte conteúdo:

```yml
version: '3.7'

services:
  php:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: dev-site
    volumes:
      - ./nginx/php-fpm.conf:/etc/php/7.2/fpm/php-fpm.conf
    networks:
      - site-network

  nginx:
    image: nginx:1.14.0
    container_name: dev-nginx
    ports:
      - "8001:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/sites.conf/:/etc/nginx/conf.d/default.conf
    command: [ nginx-debug, '-g', 'daemon off;' ]
    networks:
      - site-network

networks:
  site-network:
    driver: bridge
```

### Explicando o docker-compose

- `services`: Temos dois serviços, um para o PHP e o outro para o Nginx.

- Serviço `php`: Nesse bloco fazemos o build na imagem a partir do nosso Dockerfile criado anteriormente. Adicionamos um nome ao container para facilitar a utilização do mesmo. Também fizemos um bind do arquivo de configuração do php-fpm (`php-fpm.conf`) para o container. E por fim adicionamos a rede que é criada no bloco `networks` que é o último bloco do arquivo para podermos fazer a comunicação entre ambos os containers.

- Serviço `Nginx`: Nesse bloco usamos uma imagem pronta do Nginx na versão 1.14.0 que é a versão que estava sendo executada em produção, também adicionamos um nome para facilitar o uso. E liberamos a porta `8001` da máquina para a porta `80` do container, isso porque podemos já utilizar a porta 80 da máquina por algum outro motivo. E adicionamos os volumes, na verdade fazemos apenas o bind do `nginx.conf` e do `sites.conf` que ambos estão dentro da pasta `nginx` na raiz do projeto com o `/etc/nginx/nginx.conf` e `/etc/nginx/conf.d/default.conf` respectivamente. Você pode estar se perguntando sobre esses arquivos, mas calma, ainda vamos ver esses arquivos mais adiante. Também executamos um comando para habilitar o modo de `debug` do nginx para podemos ver possíveis problemas quando executarmos o mesmo. E por fim a rede para comunicação com o container PHP.

- `networks`: Criamos esse bloco apenas para criar a rede e conseguimos comunicar ambos os containers.

## Nginx.conf

Como você deve ter visto anteriormente fazemos o bind da configuração do Nginx, isso porque nossa configuração pode ser diferente da configuração default. Mas nesse ponto mantive quase a mesma configuração do Nginx:

```
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

load_module modules/ngx_http_image_filter_module.so;

events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    keepalive_timeout  65;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}
```

Não vou entrar muito nessa parte de configuração do Nginx porque penso que foge um pouco do escopo deste artigo.

## Sites

E chegamos a outro ponto importante de toda essa configuração que é a comunicação entre o Nginx e o php-fpm, e essa configuração pode ser feita de duas formas. 

### Proxy reverso

A primeira forma é usando o proxy reverso do Nginx, e para isso utilizamos o `proxy_pass` para encaminhar a requisição para o nosso outro container.

Então naquele arquivo `sites.conf` que fizemos o bind para o container:
```
- ./nginx/sites.conf/:/etc/nginx/conf.d/default.conf
```

Teríamos o seguinte conteúdo:
```
upstream container {
    least_conn;
    server dev-site:9000;
}

server {
    listen 80;
    location / {
        proxy_pass http://container;
    }
}
```

Explicando: Aqui criamos um `upstream` com o nome `container`, e esse basicamente aponta para o nosso container, que pode ser acessado usando o nome que criamos lá no docker-compose `dev-site` e indicando a porta 9000 que foi a porta que liberamos no container (`EXPOSE 9000`). E em seguida, no bloco `server` passamos para o proxy_pass o `upstream` criado acima porém usando o HTTP, isso porque aqui estamos enviando via HTTP.


### Fastcgi

A outra forma, e nesse caso a mais interessante é usando o fastcgi que é um protocolo para a comunicação entre o PHP e o Nginx. Também não vou entrar nesse foco aqui. Teremos a seguinte configuração naquele arquivo `sites.conf` (no lugar da configuração anterior):

```
server {
    listen 80;

    root /var/www/html;

    location / {
        include fastcgi_params;
        fastcgi_pass dev-site:9000;
        fastcgi_param SCRIPT_FILENAME $document_root/index.php;
    }
}
```

Nesse caso não teremos o `upstream`, apenas o bloco `server` e os pontos importantes aqui são as linhas:

- `include fastcgi_params;`: que inclui os outros parâmetros, que ficam no arquivo `fastcgi_params` dentro dos arquivos de configuração do Nginx.

- `fastcgi_pass dev-site:9000;`: Essa linhas é responsável pelo direcionamento das requisições para o php-fpm, por isso indicamos o nome e a porta do nosso container do php `dev-site:9000`.

- `fastcgi_param SCRIPT_FILENAME $document_root/index.php;`: E aqui indicamos qual o arquivo deve ser executado no container do php.

## Conclusão

Agora é só subir os containers usando o comando `docker-compose up` estando dentro da pasta do projeto e logo em seguida acessar o endereço http://localhost:8001 no navegador.