Skip to main content

Boas práticas para criar um Dockerfile pronto para produção para aplicações PHP

Escrito por
Headshot of James Walker

James Walker

blog feature multi namespace k8s controller

22 de agosto de 2023

0 minutos de leitura

Docker é uma plataforma de conteinerização que agrupa seu código, suas dependências e seu ambiente de execução em unidades independentes que funcionam da mesma forma em diferentes ambientes.

Conteinerizar uma aplicação PHP com Docker simplifica a implantação, empacotando o runtime do PHP, um servidor web, o código-fonte e as dependências do Composer em um contêiner. Começar a usar o Docker é fácil. No entanto, há algumas armadilhas que você precisa evitar antes de usá-lo com segurança em produção. Escolher uma imagem base adequada, selecionar o runtime do PHP correto para sua carga de trabalho e reforçar a segurança da imagem são medidas essenciais para evitar problemas. Neste tutorial, você vai aprender a escrever um Dockerfile confiável para aplicações PHP.

Como criar um Dockerfile para PHP pronto para produção

Se você está lendo isto, provavelmente já escreveu sua aplicação PHP e agora quer conteinerizá-la com Docker para implantá-la. Veja a seguir dez boas práticas para maximizar o desempenho, a segurança, a confiabilidade e a facilidade de desenvolvimento:

1. Use uma imagem base específica

A comunidade Docker publica uma imagem oficial do Docker para todas as versões compatíveis do PHP. Há várias variantes de imagem disponíveis, que abrangem diferentes sistemas operacionais base, versões da linguagem e integrações com servidores web (como Apache ou FastCGI Process Manager (FPM)).

É importante selecionar a imagem específica que melhor atende às suas necessidades. Evite usar tags genéricas, como php:latest, pois elas podem incluir mudanças indesejadas que quebram o código e bibliotecas desnecessárias, exigidas apenas por um sistema operacional completo. Por exemplo, a imagem php:apache inclui atualmente o PHP 8.2, mas passará imediatamente para o PHP 9.0 quando essa versão for lançada. Selecionar php:8.2-apache garante que sua imagem sempre execute o PHP 8.2, mesmo quando essa já não for a versão atual:

FROM php:8.2-apache
WORKDIR /var/www/html

COPY index.php .

O mesmo princípio se aplica a quaisquer outras imagens das quais você dependa, como composer, para instalar dependências. Selecione a tag de imagem que corresponde à versão principal do Composer que você usa na sua máquina: composer:2 em vez de composer:latest.

2. Use variáveis de ambiente para a configuração

Os contêineres Docker foram projetados para não manter estado. O ideal é configurar sua aplicação usando variáveis de ambiente definidas quando o contêiner é iniciado. Isso é melhor do que depender de um arquivo de configuração, que exigiria que você sempre configurasse armazenamento persistente para o contêiner usando um volume do Docker.

As variáveis de ambiente são definidas com a flag -e ao iniciar um contêiner usando docker run:

$ docker run -d -e DATABASE_HOST=172.26.0.2 php-app:latest

Seu código PHP pode obter o valor da variável chamando a função getenv():

// 172.26.0.2
echo getenv("DATABASE_HOST");

Essas são variáveis de runtime definidas em cada contêiner. Às vezes, você pode querer definir uma variável no Dockerfile para que ela seja fornecida automaticamente aos contêineres. Para isso, use a instrução ENV:

FROM php:8.2-apache
WORKDIR /var/www/html

ENV DATABASE_DRIVER=sqlite

COPY index.php .

Também é possível preencher variáveis de ambiente no momento da compilação e referenciá-las em outras instruções do Dockerfile usando a palavra-chave ARG:

FROM php:8.2-apache
WORKDIR /var/www/html

ARG DEFAULT_DATABASE_DRIVER

RUN echo '{"db":"$DEFAULT_DATABASE_DRIVER"}' > config.json
COPY index.php .

Para definir o valor do argumento, use a flag --build-arg ao executar docker build:

$ docker build --build-arg DEFAULT_DATABASE_DRIVER=sqlite -t php-app:latest .

3. Avalie PHP FPM versus Nginx/Apache

FPM é uma implementação alternativa do PHP FastCGI para integrar o PHP a servidores web, incluindo Apache e Nginx. Uma alternativa ao FPM é usar o servidor web Apache com mod_php. Em comparação com a opção do Apache, o FPM oferece gerenciamento avançado de processos sem bloqueio, a capacidade de criar diferentes pools de processos de trabalho e tratamento de erros mais robusto. O uso do FPM costuma proporcionar um ganho significativo de desempenho em aplicações grandes.

A imagem base do Docker do PHP está disponível nas variantes FPM e mod_php:

  • php:8.2-fpm: esta imagem e tags semelhantes disponibilizam uma conexão FPM.

  • php:8.2-apache: essas imagens incluem uma instalação do Apache e usam mod_php para processar solicitações PHP.

Se você usa Apache e está satisfeito com o mod_php, selecione uma das imagens base :apache. Você pode adicionar seu código PHP ao diretório /var/www/html e começar a executá-lo imediatamente.

Para usar o FPM, você precisará de um contêiner separado que execute um servidor como o Nginx. Ele deve ser configurado como um proxy reverso, que encaminha as solicitações PHP para a porta exposta pelo processo FPM no seu contêiner PHP. O número padrão da porta é 9000.

Use o FPM se você busca o máximo desempenho e não se importa em gerenciar dois contêineres: um contêiner FPM e um servidor web na frente dele. As imagens baseadas em Apache são um ótimo ponto de partida para começar rapidamente, sem configurar contêineres adicionais nem ajustar as configurações dos pools de processos de trabalho do FPM.

4. Desative logs de depuração e relatórios de erros desnecessários em produção

Aplicações em produção não devem divulgar publicamente informações que possam ser úteis a um invasor. Desative as configurações display_errors e display_startup_errors do PHP para evitar que exceções não tratadas sejam exibidas no seu site.

Para alterar esses valores, modifique o arquivo php.ini no seu contêiner.

Comece criando o arquivo modificado:

display_errors = Off
display_startup_errors = Off

Em seguida, ajuste seu Dockerfile para copiar o php.ini para o local correto no sistema de arquivos do contêiner:

FROM php:8.2-apache
WORKDIR /var/www/html

COPY php.ini /usr/local/etc/php/php.ini
COPY index.php .

Talvez seja necessário alterar o nível de error_reporting do PHP para uso em produção. Erros de baixo nível, como notificações e avisos de recursos obsoletos, podem rapidamente encher os logs com informações que não ajudam a resolver problemas. Você pode definir esse valor dinamicamente no início do script, com base em uma variável de ambiente:

if (getenv("IS_PRODUCTION")) {
    // Disable error reports which are not an immediate issue
    error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
}

5. Execute seu contêiner PHP como um usuário não root

Por padrão, o Docker executa contêineres como root. O processo em execução dentro do contêiner tem os mesmos privilégios que o root no host. Se o contêiner for comprometido e sua barreira de isolamento for rompida, processos maliciosos poderão executar comandos arbitrários no host.

Executar seu contêiner como um usuário dedicado sem privilégios de root ajuda a reduzir esses riscos. Use a instrução USER no Dockerfile para mudar para outro usuário a partir desse ponto:

FROM php:8.2-apache
WORKDIR /var/www/html
USER appuser

COPY index.php .

6. Configure verificações de integridade para contêineres PHP

O Docker tem um mecanismo integrado de verificação de integridade para detectar falhas nos contêineres. Você pode configurá-lo para executar automaticamente um comando de verificação a cada trinta segundos. Se o comando terminar com o código 0, o contêiner será considerado saudável. Já o código 1 indica que a aplicação falhou.

Você pode configurar verificações de integridade para suas aplicações usando a instrução HEALTHCHECK no Dockerfile. Em um serviço PHP, você pode usar curl para fazer uma solicitação de rede a um dos endpoints da aplicação. Se o curl terminar com 0, significa que recebeu uma resposta com um código de status HTTP na faixa 2xx (sucesso):

FROM php:8.2-apache
WORKDIR /var/www/html
USER appuser

HEALTHCHECK CMD curl --fail http://localhost/index.php || exit 1
COPY index.php .

A integridade dos seus contêineres é exibida na saída do comando docker ps:

$ docker ps
CONTAINER ID	IMAGE  		COMMAND              		CREATED    	STATUS
335889ed4698	php-demo-app		"docker-php-entrypoi…"   	2 mins ago 	Up 2 mins (healthy)

Quando uma verificação de integridade falha, o status do contêiner passa a ser unhealthy. Orquestradores de contêineres, como o Kubernetes, usam esse sinal para reiniciar ou substituir automaticamente os contêineres após uma falha.

7. Limite o número de portas usadas

Evite expor portas desnecessárias nos seus contêineres. Exponha apenas as portas que precisam ser acessadas por processos externos. São essas as portas às quais outros contêineres e usuários públicos se conectarão.

Em aplicações PHP, geralmente é a porta 80 quando você usa uma imagem com servidor web incluído, como php:8.2-apache. Imagens baseadas em FPM devem expor a porta 9000 para que um contêiner separado de servidor web se conecte a ela. A porta do FPM não deve ficar aberta ao público.

Da mesma forma, tenha cuidado com as informações que você expõe por meio de variáveis de ambiente. Remova as variáveis não utilizadas para reduzir o risco de exposição. Ferramentas como docker inspect e qualquer software malicioso dentro do contêiner sempre podem acessar a lista completa de variáveis definidas.

8. Atualize as dependências regularmente

Todos os componentes da sua stack devem ser atualizados regularmente para receber correções de bugs e atualizações de segurança. Isso inclui:

  • Atualizações do sistema operacional base da sua imagem Docker.

  • Atualizações da versão e das extensões do PHP que você usa.

  • Pacotes do Composer instalados como dependências no seu projeto.

Isso é fundamental para que você fique protegido contra novas vulnerabilidades. Reconstrua sua imagem com docker build --pull a cada compilação para obter as atualizações. Assim, será baixada a versão mais recente da imagem base, incluindo pacotes atualizados do sistema operacional e novas versões do PHP.

Você também deve executar periodicamente composer update no seu projeto para instalar as correções mais recentes das dependências. Ferramentas como o Snyk ajudam a encontrar dependências desatualizadas ou vulneráveis e sugerem como atualizá-las para uma versão corrigida. Depois de atualizar os pacotes com o Composer, reconstrua sua imagem Docker para que as implantações em produção usem as novas versões.

9. Limite os recursos do contêiner ao mínimo necessário

As capacidades do kernel do Linux restringem as ações potencialmente sensíveis que os processos podem executar. O Docker já executa contêineres com um conjunto de capacidades sem privilégios, mas, mesmo assim, há permissões em excesso para cargas de trabalho PHP típicas. Por exemplo, as configurações padrão permitem que os processos dos contêineres alterem permissões de arquivos, encerrem processos e manipulem IDs de usuário.

Você pode adicionar e remover capacidades do contêiner usando as flags --cap-add e --cap-drop no docker run. Para ter mais segurança, remova todas as capacidades com --cap-drop=all e adicione de volta apenas as que sua aplicação exige.

O exemplo a seguir concede ao contêiner apenas a capacidade CHOWN. O contêiner poderá alterar a propriedade de arquivos e pastas, mas não poderá executar nenhuma outra ação privilegiada:

$ docker run -d --cap-drop=all --cap-add=CHOWN php-app:latest

10. Use builds em vários estágios para separar compilação e runtime

Builds em vários estágios facilitam a criação e a manutenção de pipelines de build mais complexos. Eles permitem usar várias imagens base durante o processo de compilação e, ao mesmo tempo, manter o resultado pequeno e preciso.

O padrão de vários estágios é ideal para Dockerfiles PHP, pois normalmente você vai querer instalar as dependências do Composer durante a compilação. As imagens oficiais do Docker para PHP não incluem o Composer: o próprio projeto publica suas imagens. Com um build em vários estágios, você pode referenciar facilmente o binário do Composer no Dockerfile:

FROM php:8.2-apache
WORKDIR /var/www/html

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
COPY composer.json .
COPY composer.lock .
RUN composer install --no-dev
RUN rm /usr/bin/composer

COPY index.php .

A imagem composer:2 será descartada quando o build terminar. Essa abordagem mantém uma boa separação entre compilação e runtime sem aumentar o tamanho da imagem final.

Como identificar e corrigir vulnerabilidades com o Snyk

O uso do Docker pode melhorar a segurança ao oferecer certo isolamento entre suas aplicações PHP e os ambientes host. No entanto, as imagens de contêiner frequentemente contêm vulnerabilidades de segurança por causa de pacotes desatualizados do sistema operacional e dependências do Composer:

Destaque da página inicial da Snyk com o título “Adorada por desenvolvedores, confiável para a segurança”, uma interface de análise de segurança e uma ilustração estilizada do cachorro da Snyk

Analisar sua imagem em busca de vulnerabilidades é uma etapa essencial antes de implantá-la em produção. Você pode usar o Snyk para analisar sua imagem Docker do PHP e identificar e corrigir vulnerabilidades. O Snyk Vulnerability Database contém registros dos sistemas operacionais e das dependências mais usados, incluindo pacotes PHP publicados no Packagist.

Para começar a identificar vulnerabilidades com o Snyk, crie um arquivo PHP simples e salve-o como index.php:

<?php

echo "Hello World";

?>

Em seguida, escreva um Dockerfile para sua aplicação:

FROM php:8.2-apache
WORKDIR /var/www/html

COPY index.php

Crie a imagem usando o Docker:

$ docker build -t php-app:latest .

Em seguida, acesse o site da Snyk e clique em Comece grátis para criar sua conta. Siga as etapas de configuração e instale a Snyk CLI. Você também precisará ter o Node.js e o npm instalados:

$ npm install -g snyk

Em seguida, execute o comando snyk auth. Uma nova aba do navegador será aberta para você entrar na Snyk e autenticar a CLI. Depois, volte ao terminal.

Para analisar sua imagem, execute o seguinte comando:

$ snyk container test php-app:latest

A análise pode levar alguns instantes. Em seguida, os resultados serão exibidos no terminal. Você verá uma lista de todas as vulnerabilidades detectadas, incluindo o pacote em que estão presentes, as versões vulneráveis e um link para saber mais:

Relatório do terminal mostrando 110 vulnerabilidades em uma imagem Docker de PHP e Apache, incluindo duas críticas e recomendações de imagens-base alternativas.

O resumo ao final mostra o total de problemas encontrados e sugere imagens base alternativas com menos vulnerabilidades.

Corrigir as vulnerabilidades detectadas

Se forem encontradas vulnerabilidades, avalie cada uma para determinar se é necessário tomar alguma medida. Nem toda vulnerabilidade representa necessariamente um problema para seu caso de uso. Comece resolvendo os problemas de severidade crítica, alta e média.

Uma das maneiras mais eficazes de começar é recriar sua imagem usando uma imagem base atualizada. Imagens populares mantidas pela comunidade, como php:8.2-fpm, são republicadas regularmente com novas versões que incluem correções de vulnerabilidades. Depois de recriar sua imagem, repita a análise para verificar o impacto na pontuação.

O relatório pode listar muitas vulnerabilidades em componentes de baixo nível do sistema operacional. Considere usar uma imagem base menor e mais enxuta para reduzir sua superfície de ataque. As variantes do Alpine Linux, como php:fpm-alpine, incluem bem menos pacotes, o que geralmente significa menos vulnerabilidades.

No momento da publicação deste artigo, a imagem php:8.2-fpm contém 98 vulnerabilidades detectadas, por exemplo:

Saída do terminal mostrando uma análise de uma imagem Docker com PHP 8.2 que encontrou 98 vulnerabilidades em dependências, incluindo 1 crítica, 2 altas, 1 média e 94 baixas.

A variante php:8.2-fpm-alpine tem apenas dez, pois inclui bem menos pacotes do sistema operacional:

Saída do terminal mostrando uma imagem Docker PHP 8.2 Alpine com 10 vulnerabilidades em dependências, incluindo problemas de gravidade crítica, alta, média e baixa.

Talvez não seja possível corrigir algumas vulnerabilidades no seu projeto. Por exemplo, problemas em pacotes do sistema operacional podem não afetar sua carga de trabalho, ou talvez ainda não haja uma correção disponível. Você pode ignorar uma vulnerabilidade em um relatório da Snyk passando o ID dela para o comando snyk ignore. Para encontrar o ID, consulte o trecho final da URL da vulnerabilidade, como SNYK-ALPINE317-CURL-3320725 em https://security.snyk.io/vuln/SNYK-ALPINE317-CURL-3320725:

$ snyk ignore --id=SNYK-ALPINE317-CURL-3320725

Por fim, lembre-se de que a segurança de um contêiner depende de vários componentes. Você precisa de uma imagem base segura, uma versão atualizada e com suporte do ambiente de execução PHP, além de versões corrigidas das dependências, dos componentes da cadeia de ferramentas e do código da aplicação. Analise sua imagem regularmente em busca de vulnerabilidades para se manter a par da sua situação de segurança.

Resumo

Ao terminar de ler este artigo, você deverá ter uma imagem Docker pronta para produção para executar sua aplicação PHP. Este guia abordou algumas práticas recomendadas para escrever seu Dockerfile e usar as imagens criadas, incluindo escolher uma imagem base específica, usar builds em vários estágios para instalar dependências e analisar a imagem em busca de vulnerabilidades para garantir que suas cargas de trabalho estejam prontas para implantação em produção.

Para mais recomendações sobre imagens Docker e PHP, consulte a documentação do Docker e outros artigos disponíveis no blog da Snyk.

A Snyk é uma plataforma de segurança fácil de usar para desenvolvedores e todos que são responsáveis por proteger o código. Ela analisa suas imagens Docker para encontrar vulnerabilidades nas dependências, nos pacotes do sistema operacional e no código PHP. A Snyk também oferece um plugin para IDE que realiza análise estática para detectar vulnerabilidades assim que elas surgem. Inscreva-se agora para encontrar e corrigir automaticamente vulnerabilidades nas imagens de contêiner PHP.

Segurança de contêineres que prioriza os desenvolvedores

O Snyk encontra e corrige automaticamente vulnerabilidades em imagens de contêiner e workloads do Kubernetes.