Criando um ambiente PHP com Nginx e Docker sem invocar entidades misteriosas.

Compartilhe!

Ionan Santos

Se você já abriu um projeto com Docker e pensou “beleza, agora só preciso entender 47 camadas de abstração e vender minha alma pro YAML”, calma. Nesse caso aqui a ideia foi bem mais direta: montar um ambiente simples com Ubuntu + PHP-FPM + Nginx dentro de um container e deixar tudo funcionando de forma objetiva.

Os arquivos principais dessa brincadeira são:

  • Dockerfile
  • nginx/default.conf

E sim, a graça toda está justamente em entender como esses dois se conversam sem começar uma guerra civil dentro do container.

A visão geral da arquitetura

A estrutura montada aqui segue um padrão clássico:

  • Nginx recebe as requisições HTTP
  • quando a requisição é para um arquivo PHP, ele repassa para o PHP-FPM
  • PHP-FPM interpreta o código PHP
  • o resultado volta para o Nginx
  • o navegador recebe a resposta e finge que foi tudo mágico

Na prática, o Docker empacota tudo isso em uma imagem para que você consiga rodar o projeto de forma previsível. Ou pelo menos mais previsível do que “na minha máquina funciona”.

O que foi feito no Dockerfile?

Arquivo: Dockerfile

Esse arquivo define a imagem do ambiente. É ele que diz: “container, você vai nascer assim”.

4. Instalação dos pacotes

 

				
					RUN apt-get update && apt-get install -y --no-install-recommends \
    bash ca-certificates curl git nano unzip zip iputils-ping net-tools lsof \
    nginx \
    php-fpm php-cli \
 && rm -rf /var/lib/apt/lists/*

				
			
1. Imagem base

FROM ubuntu:24.04

Aqui foi escolhida a imagem base do Ubuntu 24.04. Isso significa que o container começa como um sistema Ubuntu limpo, e depois a gente instala tudo o que precisa em cima dele.

Foi uma escolha bem direta: em vez de usar uma imagem pronta com PHP e Nginx, foi montado o ambiente manualmente. Isso é ótimo para aprendizado porque você entende o que está entrando no container, em vez de aceitar um pacote mágico e rezar para dar certo.

2. Evitando perguntas interativas no build

Essa variável evita que o apt-get pare no meio da instalação pedindo confirmação ou tentando abrir interface interativa.

Traduzindo: o build continua sem drama. O Docker gosta de automação. Ele não quer bater papo.

3. Primeiro diretório de trabalho
WORKDIR /workspace
 

Aqui foi definido um diretório temporário de trabalho como /workspace.

Na prática, esse diretório não foi muito explorado depois, porque mais adiante o WORKDIR é alterado para /var/www. Então esse primeiro WORKDIR funciona mais como uma etapa preparatória.

Esse é o coração do setup.

O que entra aqui?
  • utilitários básicos como bashcurlgitnanounzipzip
  • ferramentas úteis para diagnóstico como pingnet-tools e lsof
  • nginx, que vai servir a aplicação
  • php-fpm e php-cli, que permitem executar PHP
Um detalhe importante

Foi usado –no-install-recommends, o que é uma boa prática porque evita instalar um monte de dependência extra que nem sempre será necessária.

Em outras palavras: o container traz o que precisa sem virar um armário acumulador.

5. Diretório principal da aplicação
WORKDIR /var/www

Agora sim o diretório de trabalho passa a ser /var/www, que é um caminho bem comum para aplicações web em ambientes Linux.

A partir daqui, tudo gira em torno dessa pasta.

O que foi feito no nginx/default.conf

Arquivo: default.conf

Esse arquivo diz como o Nginx deve responder às requisições.

1. Porta e servidor padrão
				
					server {
    listen 80 default_server;
    listen [::]:80 default_server;

				
			

Aqui o Nginx foi configurado para ouvir:

  • conexões IPv4 na porta 80
  • conexões IPv6 na porta 80

default_server indica que esse bloco será o servidor padrão para requisições recebidas nessa porta.

Ou seja: se chegar tráfego e não houver outra regra mais específica, é essa config que entra em ação.

6. Cópia da configuração do Nginx
				
					COPY nginx/default.conf /etc/nginx/sites-available/default
RUN ln -sf /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default

				
			

Aqui a configuração customizada do Nginx é copiada para dentro da imagem.

Depois, é criado um link simbólico para ativar esse site. Isso segue o padrão tradicional do Nginx em distribuições Debian/Ubuntu:

  • sites-available guarda as configurações disponíveis
  • sites-enabled guarda as que estão ativas

Ou seja: não basta criar o arquivo. Também precisa “ligar a chave”.

7. Criação de uma página PHP simples
				
					RUN rm -f /var/www/html/index.nginx-debian.html && \
    printf "%s\n" "<?php phpinfo(); ?>" > /var/www/html/index.php

				
			

Aqui foram feitas duas coisas:

  • removeu-se a página padrão do Nginx
  • criou-se um index.php com phpinfo()

Esse phpinfo() é o famoso “teste de vida” do PHP. Se abrir no navegador e aparecer aquela página cheia de informações do ambiente, pronto: o PHP está funcionando.

É quase um eletrocardiograma do servidor, só que mais feio.

8. Exposição da porta
EXPOSE 80
 
Isso documenta que o container usa a porta 80, que é a porta padrão HTTP.

Importante: EXPOSE não publica automaticamente a porta para fora. Ele só informa a intenção. Na hora de rodar o container, você ainda precisa mapear a porta com -p.

9. Inicialização dos serviços
				
					CMD ["bash", "-lc", "php-fpm8.3 -D && nginx -g 'daemon off;'"]

				
			

Esse comando sobe os dois processos necessários:

  • php-fpm8.3 -D inicia o PHP-FPM em segundo plano
  • nginx -g ‘daemon off;’ mantém o Nginx rodando em primeiro plano

E isso é importante porque o container precisa de um processo principal ativo. Se tudo rodar em background, o container encerra.

O Nginx aqui assume o papel de “processo principal da casa”. O PHP-FPM fica trabalhando nos bastidores.

 

O que foi feito no nginx/default.conf

Arquivo: default.conf

Esse arquivo diz como o Nginx deve responder às requisições.

1. Porta e servidor padrão
				
					server {
    listen 80 default_server;
    listen [::]:80 default_server;

				
			

Aqui o Nginx foi configurado para ouvir:

  • conexões IPv4 na porta 80
  • conexões IPv6 na porta 80

default_server indica que esse bloco será o servidor padrão para requisições recebidas nessa porta.

Ou seja: se chegar tráfego e não houver outra regra mais específica, é essa config que entra em ação.

2. Nome do servidor e raiz do projeto
				
					    server_name _;
    root /var/www/public;
    index index.php index.html;

				
			
server_name _;

Isso funciona como um curinga. Na prática, aceita qualquer host.

root /var/www/public;

Aqui foi definida a raiz pública da aplicação como /var/www/public.

Esse ponto é muito importante: é essa pasta que o Nginx tenta servir.

index index.php index.html;

Se alguém acessar a raiz do site, o Nginx vai procurar nessa ordem:

  • index.php
  • index.html

O detalhe técnico que merece honestidade

Existe uma inconsistência entre os arquivos atuais:

  • Dockerfile cria o arquivo em /var/www/html/index.php
  • nginx/default.conf aponta o root para /var/www/public

Ou seja: do jeito que está hoje, o Nginx vai procurar o arquivo em um lugar, mas o PHP de teste foi criado em outro.

Na prática, isso tende a causar erro ou página não encontrada, a menos que exista manualmente uma pasta /var/www/public com o index.php dentro.

Isso não é o fim do mundo. É só aquele clássico momento em que dois arquivos acharam que estavam combinados, mas claramente não estavam.

Como corrigir

Você tem dois caminhos:

Opção 1: manter o Nginx apontando para /var/www/public

Nesse caso, o ideal seria criar o arquivo PHP em /var/www/public/index.php.

Opção 2: manter o arquivo em /var/www/html

Aí seria melhor alterar o root do Nginx para:

root /var/www/html;

Se a ideia é seguir um padrão mais moderno de projetos PHP, especialmente frameworks, o mais comum é usar /public. Então eu diria que faz mais sentido ajustar o Dockerfile para acompanhar isso.

Como essa configuração funciona na prática

O fluxo fica assim:

  1. você acessa o container pelo navegador
  2. o Nginx recebe a requisição
  3. se for um arquivo estático, ele tenta entregar direto
  4. se for rota PHP, ele redireciona para o PHP-FPM
  5. o PHP-FPM processa
  6. o Nginx devolve a resposta

O trecho responsável por isso é este:

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

				
			

Esse bloco faz o Nginx tentar:

  • o arquivo exato requisitado
  • a pasta requisitada
  • se não encontrar nada, cair em index.php passando a query string

Isso é excelente para aplicações com roteamento centralizado, como Laravel, Slim e afins.

Agora a parte do PHP:

				
					location ~ \.php$ {
    include fastcgi_params;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;

    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param DOCUMENT_ROOT $document_root;
}

				
			

Aqui o Nginx detecta arquivos .php e envia o processamento para o socket do PHP-FPM:

 
				
					fastcgi_pass unix:/run/php/php8.3-fpm.sock;

				
			

Isso quer dizer que o Nginx e o PHP-FPM estão conversando internamente por socket Unix, o que é bem comum quando ambos estão no mesmo container.

Depois, esses parâmetros ajudam o PHP-FPM a entender qual arquivo precisa executar.

Como buildar e rodar a imagem

Com esses arquivos no projeto, o processo básico é:

Build da imagem
				
					docker build -t seuusuario/php-kit:latest .

				
			
				
					docker run -d -p 8080:80 --name php-kit ionan/php-kit:latest

				
			

Isso faz o seguinte:

  • -d roda em background
  • -p 8080:80 mapeia a porta 8080 da sua máquina para a porta 80 do container
  • –name php-kit dá um nome mais humano ao container
  • ionan/php-kit:latest é a imagem que será usada

Depois disso, basta acessar:

http://localhost:8080

Se tudo estiver alinhado, você verá a saída do PHP.

Se não estiver alinhado, você verá erro. O Docker também ensina humildade.

Como publicar no Docker Hub

Agora vem a parte de colocar a imagem no Docker Hub para qualquer máquina conseguir baixar.

1. Criar conta no Docker Hub

Se ainda não tiver conta:

  • acesse o Docker Hub
  • crie seu usuário
  • pense bem no nome, porque depois você vai vê-lo em tag, repositório, push e possivelmente em momentos de arrependimento

Como essa configuração funciona na prática

O fluxo fica assim:

  1. você acessa o container pelo navegador
  2. o Nginx recebe a requisição
  3. se for um arquivo estático, ele tenta entregar direto
  4. se for rota PHP, ele redireciona para o PHP-FPM
  5. o PHP-FPM processa
  6. o Nginx devolve a resposta

O trecho responsável por isso é este:

				
					docker login

				
			

Ele vai pedir:

  • usuário
  • senha ou token

Se estiver tudo certo, você ficará autenticado localmente.

3. Nomear a imagem corretamente

O Docker Hub espera o formato:

				
					usuario/nome-do-repositorio:tag

				
			
				
					ionan/php-kit:latest

				
			

Se você buildou com outro nome, pode retaggear:

				
					docker tag php-kit:latest ionan/php-kit:latest

				
			

4. Enviar para o Docker Hub

				
					docker push ionan/php-kit:latest

				
			

Pronto. A imagem vai subir para o repositório da sua conta.

5. Testar em outra máquina

Depois, qualquer pessoa com acesso pode baixar a imagem com:

				
					docker pull ionan/php-kit:latest

				
			

E rodar com:

				
					docker run -d -p 8080:80 ionan/php-kit:latest

				
			

Uma sugestão prática para o artigo ficar ainda melhor

Se você quiser deixar o post tecnicamente redondo, vale mostrar uma versão corrigida do Dockerfile para combinar com o root /var/www/public.

Exemplo:

				
					RUN mkdir -p /var/www/public && \
    rm -f /var/www/html/index.nginx-debian.html && \
    printf "%s\n" "<?php phpinfo(); ?>" > /var/www/public/index.php

				
			

Assim o arquivo criado bate com a configuração do Nginx, e o exemplo fica coerente do começo ao fim.

Fechando a conta

No fim das contas, o que foi feito aqui foi montar uma imagem Docker simples, mas muito útil para entender a base de um ambiente PHP com Nginx:

  • o Ubuntu serve como base
  • o PHP-FPM processa o PHP
  • o Nginx recebe e encaminha as requisições
  • o Docker empacota tudo
  • o Docker Hub vira a vitrine para distribuir a imagem

É um setup pequeno, didático e honesto. E isso já vale bastante. Melhor um container simples que você entende do que uma monstruosidade “enterprise” que parece ter sido montada por um culto secreto de YAML.

Se quiser, no próximo passo eu posso transformar isso em:

  1. um artigo ainda mais “blog pronto”, com título, introdução e conclusão polidas
  2. uma versão em Markdown
  3. uma versão corrigida já baseada na inconsistência entre public e html para publicar sem ressalvas