Alternar tema

Docker Compose para implantar um ambiente PHP com um comando: guia completo de DNMP (Nginx + MySQL + PHP)

Easton editorial illustration: service mesh rail yard

As mensagens de erro rolavam no terminal: “O Nginx não encontra o arquivo de socket do PHP-FPM”. Era o oitavo erro daquela noite. Ao instalar o ambiente LNMP manualmente, alterei três arquivos — nginx.conf, php-fpm.conf e my.cnf — e reiniciei os serviços mais de dez vezes, mas o ambiente ainda não funcionava.

Um colega da mesa ao lado comentou: “No meu computador funciona normalmente. Será que sua versão do PHP está errada?”

Ele usava o PHP 7.4; eu havia instalado o 8.1. O MySQL dele era o 5.7, enquanto eu havia instalado o 8.0 sem perceber. Não era surpresa o código não funcionar.

10 minutos
Tempo de implantação
Tudo pronto com um comando
100%
Consistência do ambiente
A equipe usa o mesmo ambiente
Baixa
Complexidade da configuração
Inicialização com docker-compose

Cada pessoa da equipe usava um ambiente de desenvolvimento diferente. Uma pessoa recém-contratada levava metade do dia só para configurar tudo, enquanto alguém mais experiente precisava orientar cada etapa. O código funcionava no computador A e quebrava no B. Implantar no servidor era ainda mais incerto.

Esses problemas só desapareceram quando testei a implantação do DNMP com Docker Compose. Em 10 minutos, Nginx, MySQL e PHP estavam prontos. Todos passaram a usar o mesmo arquivo de configuração, e ninguém mais precisou dizer “no meu computador funciona”.

Por que usar o Docker Compose para implantar um ambiente PHP

Três grandes problemas da implantação tradicional de LNMP

Problema 1: a instalação e a configuração manuais são trabalhosas; um erro compromete todo o processo

Lembra da primeira vez que você instalou LNMP? Primeiro vem apt-get install nginx, depois php-fpm e, por fim, MySQL. Depois da instalação, ainda é preciso alterar os arquivos de configuração: regras de location no nginx.conf, extensões no php.ini e parâmetros no my.cnf.

Os três arquivos ficam espalhados em diretórios diferentes: /etc/nginx/, /etc/php/ e /etc/mysql/. Um único erro impede o serviço de iniciar. Para investigar, você precisa vasculhar os logs do Nginx, do PHP-FPM e do MySQL até ficar perdido no meio de tanta informação.

Já vi alguém passar três horas investigando por que o Nginx não encontrava o PHP: no php-fpm.conf, listen = 127.0.0.1:9000 havia sido trocado por listen = /run/php/php7.4-fpm.sock.

Problema 2: incompatibilidades entre versões viram uma dor de cabeça

Em toda equipe há quem fique preso a uma versão. Um projeto antigo roda no PHP 5.6, enquanto um novo exige o PHP 8.1. Uma consulta com GROUP BY aceita pelo MySQL 5.7 pode falhar no MySQL 8.0, pois a versão 8.0 ativa o modo ONLY_FULL_GROUP_BY por padrão.

Pior ainda: as máquinas da equipe podem ter combinações completamente diferentes:

  • O Mac de Li usa PHP 7.4 + MySQL 5.7
  • O Ubuntu de Wang usa PHP 8.0 + MySQL 8.0
  • A nova pessoa estagiária usa PHP 8.1 + MariaDB 10.6 no Windows

O código funciona na máquina de Li, é enviado ao Git e falha assim que Wang faz o pull. O ambiente de testes usa outra combinação, e a produção, outra ainda. É uma receita para problemas.

Problema 3: a colaboração da equipe se torna muito ineficiente

No primeiro dia de uma pessoa recém-contratada, alguém mais experiente passa metade do dia ajudando a configurar o ambiente:

  1. Instalar o Nginx (20 minutos)
  2. Instalar o PHP e suas extensões (30 minutos, incluindo compilação)
  3. Instalar o MySQL (15 minutos)
  4. Configurar a comunicação entre os três serviços (1 hora, com vários obstáculos)
  5. Importar os dados de teste (10 minutos)

Com sorte, dá para começar a programar à tarde. Sem sorte? Fica para o dia seguinte.

Em uma startup onde trabalhei, uma diferença de ambiente causou uma falha em um recurso simples de cadastro. Os testes passaram no desenvolvimento, mas a produção retornou “falha na conexão com o banco de dados”. O motivo: no desenvolvimento, o usuário root do MySQL não tinha senha; em produção, tinha, mas esqueceram de alterar o arquivo de configuração. Tivemos de fazer um rollback de emergência durante a madrugada, e a reação do chefe quase não foi nada agradável.

Três grandes vantagens do Docker Compose

Vantagem 1: configuração como código, compartilhada por toda a equipe em um único arquivo

O Docker Compose reúne todas as configurações em um arquivo docker-compose.yml:

  • Qual versão do Nginx usar? (nginx:1.25-alpine)
  • Quais extensões instalar no PHP? (mysqli, pdo_mysql e redis)
  • Quais parâmetros configurar no MySQL? (gerenciados de forma centralizada no my.cnf)
  • Como os serviços se comunicam? (resolução DNS automática)

Basta versionar esse arquivo no Git para que toda a equipe obtenha exatamente o mesmo ambiente. Chegou alguém novo? É só clonar o projeto e executar docker-compose up -d. Quando voltar do café, o ambiente estará funcionando.

Não é mais necessário perguntar “qual é a senha do seu MySQL?”, “onde está o seu php.ini?” ou “você pode me mostrar sua configuração do Nginx?”. Um arquivo resolve tudo.

Vantagem 2: iniciar e remover tudo com um comando, sem deixar resíduos

Para iniciar todos os serviços:

docker-compose up -d

Para parar e remover todos os contêineres:

docker-compose down

Quer trocar a versão do PHP? Basta alterar uma linha, de image: php:7.4-fpm para image: php:8.1-fpm, e executar o up novamente.

Diferentemente do método tradicional, você não precisa desinstalar uma versão antiga do PHP nem limpar vários arquivos residuais com medo de apagar algo importante do sistema. Os contêineres Docker são totalmente isolados; ao removê-los, tudo fica limpo.

Vantagem 3: isolamento de ambiente e várias versões lado a lado

Quer executar PHP 7.4 e PHP 8.1 ao mesmo tempo? No método tradicional, seria preciso instalar duas versões, configurar portas diferentes e alterar o Nginx para usar valores distintos de fastcgi_pass. É trabalhoso.

Com Docker Compose, basta definir dois serviços PHP no arquivo de configuração:

php74:
  image: php:7.4-fpm

php81:
  image: php:8.1-fpm

Na configuração do Nginx, aponte para o nome do serviço correspondente. O projeto antigo continua com php74, enquanto o novo usa php81, sem interferência entre eles.

O projeto DNMP com mais estrelas no GitHub (imeepo/dnmp) tem mais de 5.000 estrelas, o que mostra que a abordagem já foi validada por muitas equipes. Não é uma novidade experimental, mas uma solução madura e confiável.

Arquitetura do DNMP em detalhes

O que é DNMP

DNMP é, na prática, a versão Docker do LNMP. Cada letra representa um componente:

  • D = Docker: plataforma de contêineres que coloca cada serviço em um “contêiner” independente
  • N = Nginx: servidor web que recebe requisições HTTP e as encaminha para o PHP
  • M = MySQL: banco de dados relacional que armazena os dados (também pode ser substituído por MariaDB ou PostgreSQL)
  • P = PHP: ambiente PHP-FPM que executa o código PHP

Além desses quatro componentes principais, é comum adicionar:

  • Redis: serviço de cache para acelerar a leitura de dados
  • PHPMyAdmin: ferramenta gráfica para administrar o MySQL

No LNMP tradicional, todos esses serviços são instalados diretamente e ficam misturados no sistema. No DNMP, cada um fica em seu próprio contêiner Docker, como se cada software estivesse em uma máquina virtual diferente, mas com muito menos sobrecarga.

Arquitetura de orquestração dos serviços

Imagine montar um sistema de som com amplificador, caixas e player. Cada aparelho funciona de forma independente, mas todos precisam estar conectados para reproduzir o áudio juntos.

O DNMP segue a mesma lógica. Nginx, PHP e MySQL ficam em três contêineres independentes, conectados por uma rede Docker:

Fluxo de uma requisição:

  1. O navegador envia uma requisição HTTP para localhost:80
  2. O contêiner Nginx recebe a requisição e identifica um arquivo PHP
  3. O Nginx encaminha a requisição pela porta 9000 ao contêiner PHP (usando o nome de serviço php)
  4. O contêiner PHP executa o código e, quando precisa consultar o banco, conecta-se ao contêiner MySQL (usando o nome de serviço mysql)
  5. O PHP devolve o resultado ao Nginx
  6. O Nginx envia o HTML ao navegador

Mecanismo de comunicação:
O Docker Compose cria automaticamente uma rede bridge e conecta todos os serviços a ela. Cada serviço tem seu próprio nome, e o Docker faz a resolução DNS de forma automática.

Por exemplo, para o PHP acessar o MySQL, não é necessário usar 127.0.0.1:3306: basta usar mysql:3306. O Docker resolve mysql para o endereço IP do contêiner MySQL automaticamente.

Mapeamento de portas:

  • Contêiner Nginx: porta 80 → porta 80 do host (acesse localhost para ver o site)
  • Contêiner MySQL: porta 3306 → porta 3306 do host (permite usar ferramentas como Navicat)
  • Contêiner PHPMyAdmin: porta 80 → porta 8080 do host (acesse localhost:8080 para administrar o banco)
  • Contêiner Redis: porta 6379 → porta 6379 do host

Montagem de volumes:
Os dados somem quando o contêiner é removido? Não. Com volumes, os diretórios importantes do contêiner são mapeados para o host:

  • ./www/var/www/html nos contêineres Nginx e PHP (diretório do código)
  • ./mysql/data/var/lib/mysql no contêiner MySQL (arquivos do banco)
  • ./nginx/logs/var/log/nginx no contêiner Nginx (logs de acesso)

Assim, mesmo que um contêiner seja removido e recriado, os dados continuam no host.

Estrutura de diretórios

Um projeto DNMP padrão tem a seguinte estrutura:

dnmp/
├── docker-compose.yml       # Arquivo principal de orquestração, define todos os serviços
├── .env                     # Variáveis de ambiente (senhas, portas etc.)
├── .gitignore               # Arquivos ignorados pelo Git (.env não é enviado)
├── nginx/
│   ├── conf.d/
│   │   └── default.conf    # Configuração do site (diretório raiz e encaminhamento PHP)
│   └── logs/               # Logs de acesso e erros
│       ├── access.log
│       └── error.log
├── php/
│   ├── Dockerfile          # Personalização da imagem PHP (instalação de extensões)
│   ├── php.ini             # Configuração do PHP (limite de memória e tamanho de upload)
│   └── php-fpm.conf        # Configuração do PHP-FPM (número de processos)
├── mysql/
│   ├── data/               # Diretório persistente dos arquivos do banco
│   └── my.cnf              # Configuração do MySQL (charset e limite de conexões)
└── www/                    # Diretório do código do projeto
    └── index.php           # Arquivo de teste

Arquivos principais:

  • docker-compose.yml: o arquivo central, que define imagens, volumes e mapeamentos de portas
  • .env: variáveis de ambiente, como MYSQL_ROOT_PASSWORD=123456, mantendo dados sensíveis separados
  • nginx/conf.d/default.conf: configuração do site no Nginx, incluindo a raiz e as regras de encaminhamento ao PHP
  • php/Dockerfile: imagem oficial do PHP complementada com extensões como mysqli e redis
  • mysql/data/: diretório dos arquivos do banco, preservado mesmo quando o contêiner é recriado

Parece haver muitos arquivos, mas cada um tem uma responsabilidade clara. É bem diferente de uma configuração LNMP tradicional espalhada pelo sistema.

Prática de 10 minutos: criando um ambiente DNMP do zero

Pré-requisitos

1. Instale o Docker

  • Mac/Windows: baixe o Docker Desktop e instale. O docker-compose já vem incluído
  • Linux (exemplo com Ubuntu):
    sudo apt update
    sudo apt install docker.io docker-compose -y
    sudo systemctl start docker
    sudo systemctl enable docker

2. Verifique a instalação

docker --version
# Saída: Docker version 24.0.6, build xxx

docker-compose --version
# Saída: Docker Compose version v2.21.0

Se os números das versões aparecerem, a instalação foi concluída.

3. Configuração recomendada

  • Memória: pelo menos 4 GB (é possível ajustar nas configurações do Docker Desktop)
  • Disco: pelo menos 20 GB livres (as imagens ocupam algum espaço)
  • Rede: acesso ao Docker Hub (se a conexão for lenta, configure um mirror adequado à sua região)

Opção 1 de implantação rápida: usar um projeto open source consolidado (recomendado)

Não quer perder tempo com a configuração? Use um projeto open source e conclua tudo em 10 minutos.

1. Clone o projeto

Recomendo o imeepo/dnmp, que oferece suporte a CPUs Arm (incluindo os chips Apple da série M):

git clone https://github.com/imeepo/dnmp.git
cd dnmp

2. Configure as variáveis de ambiente

Copie o arquivo de exemplo:

cp .env.example .env

Abra o arquivo .env e altere as configurações principais:

# Senha do root do MySQL (não use 123456, pois é muito fraca)
MYSQL_ROOT_PASSWORD=your_strong_password

# Fuso horário
TZ=Asia/Shanghai

# Mapeamento de portas (se a porta 80 estiver ocupada, use 8080)
NGINX_HTTP_PORT=80
MYSQL_PORT=3306

3. Inicie tudo com um comando

docker-compose up -d

O parâmetro -d executa os serviços em segundo plano. Na primeira inicialização, as imagens serão baixadas, o que pode levar alguns minutos. Se aparecer uma saída como esta, deu certo:

Creating network "dnmp_default" with the default driver
Creating dnmp_mysql_1 ... done
Creating dnmp_php_1   ... done
Creating dnmp_nginx_1 ... done
Creating dnmp_redis_1 ... done

4. Verifique a instalação

Abra http://localhost no navegador. Você deve ver a página do phpinfo com a versão do PHP, as extensões instaladas e outras informações.

Se essa página aparecer, Nginx e PHP estão funcionando.

Acesse também http://localhost:8080. A tela de login do PHPMyAdmin deve aparecer:

  • Servidor: mysql (não use localhost)
  • Usuário: root
  • Senha: a senha definida no arquivo .env

Se o login funcionar, o MySQL também está pronto.

5. Veja o estado dos contêineres

docker-compose ps

A saída deve ser parecida com esta:

Name               Command              State           Ports
--------------------------------------------------------------------
dnmp_nginx_1   nginx -g daemon off;   Up      0.0.0.0:80->80/tcp
dnmp_php_1     php-fpm                Up      9000/tcp
dnmp_mysql_1   docker-entrypoint...   Up      0.0.0.0:3306->3306/tcp
dnmp_redis_1   redis-server           Up      6379/tcp

Se State exibir Up, o contêiner está em execução.

Opção 2: escrever o docker-compose.yml manualmente (avançado)

Quer entender o funcionamento em profundidade? Escreva o arquivo de configuração.

1. Crie o diretório do projeto

mkdir my-dnmp && cd my-dnmp
mkdir -p nginx/conf.d php mysql/data www

2. Escreva o docker-compose.yml

Crie o arquivo docker-compose.yml com o conteúdo abaixo:

version: '3.8'

services:
  # Serviço Nginx
  nginx:
    image: nginx:1.25-alpine  # A versão alpine produz uma imagem menor
    container_name: dnmp-nginx
    ports:
      - "80:80"  # Mapeia a porta 80 para o host
    volumes:
      - ./www:/var/www/html  # Diretório do código
      - ./nginx/conf.d:/etc/nginx/conf.d  # Configuração do site
      - ./nginx/logs:/var/log/nginx  # Diretório de logs
    depends_on:
      - php  # Depende do serviço PHP; inicia o PHP antes do Nginx
    networks:
      - dnmp-network

  # Serviço PHP
  php:
    build: ./php  # Cria a imagem usando o Dockerfile
    container_name: dnmp-php
    volumes:
      - ./www:/var/www/html  # Mesmo diretório de código usado pelo Nginx
    networks:
      - dnmp-network

  # Serviço MySQL
  mysql:
    image: mysql:8.0
    container_name: dnmp-mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: root123456  # Senha do root
      MYSQL_DATABASE: test_db  # Banco de dados criado por padrão
      TZ: Asia/Shanghai  # Fuso horário
    volumes:
      - ./mysql/data:/var/lib/mysql  # Persistência de dados
    networks:
      - dnmp-network

  # Serviço Redis (opcional)
  redis:
    image: redis:7-alpine
    container_name: dnmp-redis
    ports:
      - "6379:6379"
    networks:
      - dnmp-network

networks:
  dnmp-network:
    driver: bridge  # Rede bridge para a comunicação entre contêineres

3. Crie o Dockerfile do PHP

Crie php/Dockerfile e instale as extensões mais usadas:

FROM php:8.1-fpm

# Instala as dependências do sistema
RUN apt-get update && apt-get install -y \
    libzip-dev \
    zip \
    unzip

# Instala as extensões do PHP
RUN docker-php-ext-install \
    mysqli \
    pdo_mysql \
    zip \
    opcache

# Instala a extensão Redis
RUN pecl install redis && docker-php-ext-enable redis

# Define o diretório de trabalho
WORKDIR /var/www/html

4. Crie a configuração do site no Nginx

Crie nginx/conf.d/default.conf:

server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php index.html;

    # Logs de acesso e erros
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    # Encaminha arquivos PHP ao PHP-FPM
    location ~ \.php$ {
        fastcgi_pass php:9000;  # php é o nome do serviço; o Docker resolve o IP
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Retorna arquivos estáticos diretamente
    location ~ \.(js|css|png|jpg|gif|ico)$ {
        expires 7d;
    }
}

5. Crie os arquivos de teste

Crie www/index.php:

<?php
phpinfo();

Crie www/db_test.php para testar a conexão com o banco:

<?php
$host = 'mysql';  // Nome do serviço, não localhost
$user = 'root';
$pass = 'root123456';
$db = 'test_db';

try {
    $pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass);
    echo "Conexão com o banco realizada com sucesso!<br />";
    echo "Versão do MySQL: " . $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
} catch(PDOException $e) {
    echo "Falha na conexão: " . $e->getMessage();
}

6. Inicie os serviços

docker-compose up --build -d

O parâmetro --build recria a imagem do PHP. A primeira inicialização demora um pouco mais porque precisa baixar a imagem base e instalar as extensões.

Verificação e testes

1. Teste o PHP

Acesse http://localhost. A página do phpinfo deve aparecer. Confira se:

  • A versão do PHP é 8.1
  • As extensões mysqli, pdo_mysql e redis aparecem como enabled

2. Teste a conexão com o banco de dados

Acesse http://localhost/db_test.php. Você deve ver uma mensagem de conexão bem-sucedida e o número da versão do MySQL.

Se a conexão falhar, confira:

  • Se o contêiner MySQL iniciou: docker-compose ps
  • Se o host foi definido como localhost (deve ser mysql)
  • Se a senha está correta

3. Verifique o estado dos contêineres

docker-compose ps

O State de todos os contêineres deve ser Up.

4. Consulte os logs para investigar erros

Se um contêiner não iniciar, consulte os logs:

docker-compose logs php  # Logs do contêiner PHP
docker-compose logs -f nginx  # Logs do Nginx em tempo real (parâmetro -f)

Os logs mostram o erro específico, como um problema de sintaxe no arquivo de configuração ou uma porta já ocupada.

Boas práticas de colaboração em equipe

Estratégia de controle de versão

Princípio fundamental: envie as configurações, mas não os dados sensíveis

Crie o arquivo .gitignore:

# Não envia dados sensíveis
.env

# Não envia os arquivos do banco (são grandes)
mysql/data/

# Não envia arquivos de log
nginx/logs/*.log
php/logs/*.log

# O diretório de código é gerenciado pelo projeto da aplicação, não pelo DNMP
www/*
!www/.gitkeep  # Preserva a estrutura do diretório

Arquivos enviados ao Git:

git add docker-compose.yml
git add .env.example  # Configuração de exemplo, sem senhas reais
git add nginx/conf.d/
git add php/Dockerfile
git add php/php.ini
git add mysql/my.cnf
git commit -m "feat: add DNMP environment config"
git push

Integração de uma pessoa nova em 3 minutos:

  1. Clone o projeto: git clone xxx
  2. Copie a configuração: cp .env.example .env
  3. Altere a senha: edite .env e mude MYSQL_ROOT_PASSWORD
  4. Inicie o ambiente: docker-compose up -d
  5. Importe os dados: docker exec -i dnmp-mysql mysql -uroot -p < backup.sql

Pronto. Ninguém precisa perguntar “onde está sua configuração do Nginx?” ou “quais extensões você instalou no PHP?”. Está tudo nos arquivos de configuração.

Como usar várias versões do PHP lado a lado

Cenário: o projeto antigo usa PHP 7.4 e o novo exige PHP 8.1. O que fazer?

Opção 1: defina vários serviços PHP no docker-compose.yml

services:
  php74:
    image: php:7.4-fpm
    container_name: dnmp-php74
    volumes:
      - ./www:/var/www/html
    networks:
      - dnmp-network

  php81:
    image: php:8.1-fpm
    container_name: dnmp-php81
    volumes:
      - ./www:/var/www/html
    networks:
      - dnmp-network

Aponte projetos diferentes para versões diferentes do PHP na configuração do Nginx:

nginx/conf.d/old-project.conf (projeto antigo):

server {
    listen 80;
    server_name old.local;
    root /var/www/html/old-project;

    location ~ \.php$ {
        fastcgi_pass php74:9000;  # Aponta para o PHP 7.4
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

nginx/conf.d/new-project.conf (projeto novo):

server {
    listen 80;
    server_name new.local;
    root /var/www/html/new-project;

    location ~ \.php$ {
        fastcgi_pass php81:9000;  # Aponta para o PHP 8.1
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Configure o arquivo hosts:

127.0.0.1 old.local
127.0.0.1 new.local

Ao acessar http://old.local, o projeto usa PHP 7.4; em http://new.local, usa PHP 8.1. Um não interfere no outro.

Persistência e backup de dados

A persistência de dados já é garantida pelo volume: ./mysql/data é mapeado para /var/lib/mysql no contêiner. Mesmo que o contêiner seja removido, os dados continuam lá.

Script de backup do banco de dados:

Crie scripts/backup.sh:

#!/bin/bash
BACKUP_DIR="./backups"
DATE=$(date +%Y%m%d_%H%M%S)
MYSQL_CONTAINER="dnmp-mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="root123456"
DATABASE="test_db"

mkdir -p $BACKUP_DIR

echo "Iniciando backup do banco $DATABASE..."
docker exec $MYSQL_CONTAINER mysqldump -u$MYSQL_USER -p$MYSQL_PASSWORD $DATABASE > $BACKUP_DIR/${DATABASE}_${DATE}.sql

echo "Backup concluído: $BACKUP_DIR/${DATABASE}_${DATE}.sql"

Backup periódico com cron:

# Edita o crontab
crontab -e

# Executa o backup todos os dias às 2h
0 2 * * * /path/to/scripts/backup.sh

Restaure o banco de dados:

docker exec -i dnmp-mysql mysql -uroot -proot123456 test_db < ./backups/test_db_20251218.sql

Problemas comuns e soluções

Problema 1: a porta está ocupada e o contêiner não inicia

Sintoma:

Error starting userland proxy: listen tcp4 0.0.0.0:80: bind: address already in use

Diagnóstico:

# Mac/Linux
lsof -i :80

# Windows
netstat -ano | findstr :80

Soluções:

  • Opção 1: pare o programa que está usando a porta 80, como Apache ou IIS
  • Opção 2: altere o mapeamento no .env para NGINX_HTTP_PORT=8080 e acesse localhost:8080

Problema 2: permissões de arquivos no Linux/Mac

Sintomas:

  • O Nginx retorna 403 Forbidden
  • O PHP não consegue gravar arquivos e retorna Permission denied

Causa:
O usuário www-data no contêiner (UID 33) é diferente do usuário no host (UID 1000), impedindo o acesso aos arquivos montados.

Solução temporária para desenvolvimento:

chmod -R 777 ./www

Solução correta: altere o UID do usuário no contêiner

Edite php/Dockerfile para fazer o UID do usuário no contêiner corresponder ao UID do usuário no host:

FROM php:8.1-fpm

# Altera o UID do www-data para o UID do usuário no host (por exemplo, 1000)
RUN usermod -u 1000 www-data && groupmod -g 1000 www-data

# Outras configurações...

Recrie a imagem:

docker-compose build php
docker-compose up -d

Problema 3: extensões do PHP ausentes

Sintoma:

Fatal error: Call to undefined function mysqli_connect()

Diagnóstico:

docker exec dnmp-php php -m  # Lista as extensões instaladas

Se mysqli não aparecer, a extensão não está instalada.

Solução:

Edite php/Dockerfile e adicione a extensão:

RUN docker-php-ext-install mysqli pdo_mysql

Recrie e reinicie:

docker-compose build php
docker-compose restart php

Problema 4: falha na conexão com o banco de dados

Sintoma:

SQLSTATE[HY000] [2002] Connection refused

Erros comuns:

  • Erro 1: usar localhost ou 127.0.0.1 como host
  • Erro 2: tentar conectar antes de o contêiner MySQL concluir a inicialização
  • Erro 3: usar uma senha incorreta

Forma correta:

$host = 'mysql';  // Use o nome do serviço, não localhost
$user = 'root';
$pass = 'root123456';  // Confira se é igual ao valor do .env

try {
    $pdo = new PDO("mysql:host=$host;dbname=test_db", $user, $pass);
    echo "Conexão realizada com sucesso";
} catch(PDOException $e) {
    echo "Falha: " . $e->getMessage();
}

Se ainda não conectar, verifique se o MySQL terminou de iniciar:

docker-compose logs mysql

Quando aparecer mysqld: ready for connections, a inicialização foi concluída.

Problema 5: o contêiner encerra logo após iniciar

Sintoma:

docker-compose ps
# O Status de algum contêiner mostra Exit 1 ou Exit 127

Comando de diagnóstico:

docker-compose logs nome-do-serviço

Causas comuns:

  1. Erro de sintaxe no arquivo de configuração:

    • Falta um ponto e vírgula ; na configuração do Nginx
    • A indentação do docker-compose.yml está errada (use espaços, nunca Tab)
  2. Variável de ambiente ausente:

    • O MySQL não inicia porque MYSQL_ROOT_PASSWORD não foi definido
  3. Serviço dependente não iniciou:

    • O Nginx depende do PHP, mas o contêiner PHP não consegue iniciar

Solução:
Corrija os arquivos conforme as mensagens dos logs e reinicie:

docker-compose down  # Remove todos os contêineres
docker-compose up -d  # Recria e inicia os contêineres

Conclusão

Depois de tudo isso, o valor central de implantar um ambiente DNMP com Docker Compose pode ser resumido em três pontos:

  1. Economia de tempo: tudo fica pronto em 10 minutos, sobrando tempo para mais duas xícaras de café
  2. Menos problemas: toda a equipe usa o mesmo ambiente, sem investigar durante a madrugada um bug que “funciona no meu computador”
  3. Manutenção simples: os arquivos de configuração também funcionam como documentação; uma pessoa nova entende o ambiente sem orientação individual

Quando eu instalava LNMP manualmente, muitas vezes chegava às duas da manhã sem conseguir fazer o ambiente funcionar. Com Docker Compose, a experiência virou realmente “execute up, tome um café e o ambiente estará pronto”.

Não deixe para depois: reserve 10 minutos antes de encerrar o dia e faça um teste. Primeiro, clone um projeto open source como o imeepo/dnmp e experimente a inicialização com um comando. Quando estiver familiarizado, escreva seus próprios arquivos de configuração para entender tudo em profundidade.

E, se surgir algum problema, não entre em pânico:

  1. Consulte os logs com docker-compose logs
  2. Pesquise nos Issues do GitHub; provavelmente outra pessoa já encontrou o mesmo problema
  3. Se não resolver, abra um Issue no repositório do projeto; a comunidade é bastante ativa

Por fim, o Docker Compose não serve apenas para ambientes PHP. Ele também funciona com Node.js, Python e Go. Depois de aprender essa abordagem, trocar de stack deixa de ser um problema de configuração de ambiente.

Próximos passos:

  • Comece agora: execute git clone https://github.com/imeepo/dnmp.git e coloque seu primeiro ambiente DNMP no ar
  • Aprofunde-se: consulte a documentação oficial do Docker Compose e aprenda recursos avançados, como o docker-compose.override.yml
  • Compartilhe: se este artigo ajudou, envie-o a colegas que ainda instalam ambientes manualmente

Processo completo para implantar um ambiente PHP com Docker Compose

Guia completo de DNMP (Nginx + MySQL + PHP) para configurar tudo em 10 minutos e eliminar as diferenças de ambiente na equipe

⏱️ Estimated time: 10 min

  1. 1

    Step 1: Entender o problema e a solução

    Contexto do problema:
    • Cada pessoa da equipe usa um ambiente de desenvolvimento diferente
    • Uma pessoa recém-contratada leva metade do dia só para configurar o ambiente, com orientação individual de alguém mais experiente
    • O código funciona no computador A, mas falha no computador B
    • A implantação no servidor vira uma questão de sorte
    • As versões do PHP são diferentes (7.4 vs. 8.1)
    • As versões do MySQL são diferentes (5.7 vs. 8.0)

    Solução:
    • Usar o Docker Compose para implantar com um comando um ambiente de desenvolvimento DNMP (Docker + Nginx + MySQL + PHP)
    • Concluir tudo em 10 minutos e eliminar as diferenças de ambiente na equipe
    • Fazer toda a equipe usar a mesma configuração de ambiente
  2. 2

    Step 2: Configurar o docker-compose.yml e iniciar os serviços

    Configuração completa:
    • Configurar os três serviços Nginx, MySQL e PHP no docker-compose.yml
    • Configurar a conexão de rede
    • Configurar a persistência de dados
    • Configurar as variáveis de ambiente
    • Configurar verificações de integridade para garantir que os serviços iniciem corretamente

    Implantação em 10 minutos:
    1) Criar o arquivo docker-compose.yml
    2) Configurar os serviços Nginx, MySQL e PHP
    3) Iniciar os serviços: docker-compose up -d
    4) Verificar o estado dos serviços: docker-compose ps
    5) Testar a aplicação PHP: acessar http://localhost
  3. 3

    Step 3: Resolver problemas comuns e aplicar boas práticas

    Problemas comuns:
    • O Nginx não encontra o arquivo de socket do PHP-FPM
    • Falha na conexão com o MySQL
    • Extensões do PHP ausentes
    • Arquivos de configuração sem efeito

    Soluções:
    • Corrigir os arquivos de configuração conforme as mensagens dos logs
    • Reiniciar em seguida: docker-compose down e docker-compose up -d

    Boas práticas:
    • Fixar versões das imagens com tags
    • Configurar a persistência de dados
    • Configurar verificações de integridade
    • Gerenciar configurações com variáveis de ambiente
    • Atualizar as versões das imagens regularmente
    • Documentar as configurações

    Próximos passos:
    • Comece agora: execute git clone https://github.com/imeepo/dnmp.git e coloque seu primeiro ambiente DNMP no ar
    • Aprofunde-se: consulte a documentação oficial do Docker Compose para aprender recursos avançados
    • Compartilhe: envie este guia a colegas que ainda configuram ambientes manualmente

FAQ

Por que implantar um ambiente PHP com Docker Compose?
Contexto do problema:
• Cada pessoa da equipe usa um ambiente de desenvolvimento diferente
• Uma pessoa recém-contratada leva metade do dia só para configurar o ambiente, com orientação individual de alguém mais experiente
• O código funciona no computador A, mas falha no computador B
• A implantação no servidor vira uma questão de sorte
• As versões do PHP são diferentes (7.4 vs. 8.1)
• As versões do MySQL são diferentes (5.7 vs. 8.0)

Solução: usar o Docker Compose para implantar com um comando um ambiente de desenvolvimento DNMP (Docker + Nginx + MySQL + PHP) em 10 minutos, eliminando as diferenças de ambiente e garantindo a mesma configuração para toda a equipe.
Como implantar um ambiente DNMP com Docker Compose?
Configuração completa:
• Configurar os três serviços Nginx, MySQL e PHP no docker-compose.yml
• Configurar a conexão de rede
• Configurar a persistência de dados
• Configurar as variáveis de ambiente
• Configurar verificações de integridade para garantir que os serviços iniciem corretamente

Implantação em 10 minutos:
1) Criar o arquivo docker-compose.yml
2) Configurar os serviços Nginx, MySQL e PHP
3) Iniciar os serviços: docker-compose up -d
4) Verificar o estado dos serviços: docker-compose ps
5) Testar a aplicação PHP: acessar http://localhost
Quais são os problemas mais comuns ao implantar um ambiente DNMP?
Problemas comuns:
• O Nginx não encontra o arquivo de socket do PHP-FPM
• Falha na conexão com o MySQL
• Extensões do PHP ausentes
• Arquivos de configuração sem efeito

Soluções:
• Corrigir os arquivos de configuração conforme as mensagens dos logs
• Reiniciar em seguida: docker-compose down e docker-compose up -d

Etapas de diagnóstico:
• Ver os logs dos contêineres: docker-compose logs
• Verificar o estado dos serviços: docker-compose ps
• Inspecionar a rede: docker network inspect
• Conferir se os arquivos de configuração estão corretos
Quais são as boas práticas para um ambiente PHP com Docker Compose?
Boas práticas:
• Fixar versões das imagens com tags
• Configurar a persistência de dados
• Configurar verificações de integridade
• Gerenciar configurações com variáveis de ambiente
• Atualizar as versões das imagens regularmente
• Documentar as configurações

Próximos passos:
• Comece agora: execute git clone https://github.com/imeepo/dnmp.git e coloque seu primeiro ambiente DNMP no ar
• Aprofunde-se: consulte a documentação oficial do Docker Compose para aprender recursos avançados
• Compartilhe: envie este guia a colegas que ainda configuram ambientes manualmente

19 min de leitura · Publicado em: 18 dez 2025 · Atualizado em: 4 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog