DEV Community

Ranieri Valença
Ranieri Valença

Posted on

Criando e executando um projeto Laravel 12 com (quase) zero dependências usando Docker

TL;DR

O objetivo deste artigo é criar e executar um projeto Laravel 12 sem instalar nenhuma dependência no sistema host. Para isso, criei um Dockerfile baseado na imagem oficial do PHP com o Composer e o Bun. Você pode ir direto para a seção Dockerfile se estiver com pressa.

Uma side quest é instalar a extensão do MongoDB para PHP. Você pode ir direto para a seção Expandindo o Dockerfile e dependências para ver como fiz isso.

Introdução

Como professor de Desenvolvimento Web, em geral tenho que lidar com muitos ambientes, linguagens e frameworks diferentes. Muitas vezes tenho que instalar binários, bibliotecas e dependências no sistema para poder rodar um simples "Hello World". Isso não só consome tempo, mas também pode causar conflitos entre diferentes versões da mesma biblioteca ou binário.

Como sou ~um pouco~ desorganizado, em geral eu esqueço de desinstalar essas dependências e binários depois de terminar meu trabalho. Isso deixa muito lixo no sistema, o que pode deixá-lo mais lento e, mais uma vez, gerar muitos conflitos.

Ontem me deparei com um novo desafio em um projeto de alguns estudantes: eles queriam criar um projeto Laravel 12 no laboratório da faculdade, mas não tinham permissão para instalar nada no sistema. Além disso, a versão do PHP instalada no laboratório estava desatualizada (para o Laravel 12) e seria trabalhoso atualizar. Para completar, eles queriam conectar o Laravel a um banco de dados MongoDB – que também não estava instalado.

Então, propus um desafio: criar um projeto Laravel 12 sem instalar nenhuma dependência. E agora estou compartilhando esse desafio com você — junto com a minha solução.

Neste artigo, apresentarei meu fluxo de trabalho para atingir esse objetivo. Vou compartilhar meus pensamentos e decisões, os problemas que enfrentei e como os resolvi. Até porque além de ser um desenvolvedor bem apaixonado pelo que faço, também sou professor, então tentarei explicar tudo de uma forma mais simples e leve. Espero que goste!

O processo

Depois de algumas considerações, a solução que encontrei foi usar o Docker para criar um contêiner com todas as dependências necessárias para rodar um projeto Laravel 12. Dessa forma, podemos criar um projeto Laravel dentro do contêiner e executá-lo sem instalar nada no sistema host, ou simplesmente usar o contêiner como um ambiente de desenvolvimento e (meio que) rodar o projeto no sistema host.

Claro, dava pra obter o mesmo resultado usando uma máquina virtual, mas o Docker é mais rápido e leve. Também (provavelmente) tinha como usar ambientes virtuais (virtual environments), mas eles seriam menos diretos e mais complexos. Além disso, ou eles não funcionariam no laboratório da faculdade ou os alunos iriam deixar o ambiente virtual rodando indefinidamente, criando problemas para outros alunos (às vezes menos experientes), o que iria criar mais dor de cabeça.

Voltando à ideia de usar o Docker, parece ser um pouco desafiador no início, porque os requisitos do Laravel 12 são: PHP, Composer, o Laravel Installer (um binário que hoje em dia eu normalmente evito instalar no sistema) e Node + NPM. Há alguns outros requisitos que eles não mencionam, mas falaremos sobre isso mais tarde.

Sempre que preciso rodar algo em um contêiner Docker, eu procuro no Docker Hub oficial por uma imagem que já tenha as dependências de que preciso. Também prefiro usar imagens oficiais porque:

  1. Eu confio mais nelas.
  2. Elas geralmente estão mais atualizadas.

Para o desafio proposto e levando em conta minhas preferências pessoais, encontrei as seguintes imagens:

Cada uma delas tem uma imagem oficial no Docker Hub, mas não foram feitas para serem usadas juntas. Então, eu tenho que criar um Dockerfile personalizado para gerar um contêiner com todas as dependências necessárias. E agora eu tenho que decidir qual delas usar como base para facilitar o trabalho.

Atualmente, a imagem latest do Composer é baseada no Alpine Linux, que é muito minimalista e leve, mas exigiria que eu instalasse PHP e Node manualmente. Já as imagens do Node e do PHP são baseadas no Debian (que é minha zona de conforto). Então, decidi usar a imagem do PHP como base para meu Dockerfile.

Prova de conceito

Eu invejo aquelas pessoas que conseguem diretamente escrever um Dockerfile sem testar nada no mundo real — pessoas que realmente sabem o que estão fazendo e quais comandos e pacotes precisam. Eu não sou uma dessas pessoas. Então, antes de ir para o Dockerfile, decidi criar um contêiner com a imagem PHP e tentar instalar o Composer e o Bun manualmente.

docker run -it --rm php bash
Enter fullscreen mode Exit fullscreen mode

Esse comando vai criar um contêiner com a imagem PHP e executar o comando bash dentro dele. O parâmetro --rm vai remover o contêiner assim que ele for encerrado. Assim, posso testar as coisas sem deixar lixo (contêiners parados) no meu sistema.

Composer

Beleza, agora estou dentro do contêiner. Vamos tentar instalar o Composer.

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php composer-setup.php
php -r "unlink('composer-setup.php');"
Enter fullscreen mode Exit fullscreen mode

Funcionou! Agora só falta um comando para mover o arquivo composer.phar para o diretório /usr/local/bin e torná-lo disponível globalmente (não no meu sistema, mas no contêiner).

mv composer.phar /usr/local/bin/composer
Enter fullscreen mode Exit fullscreen mode

Bun

Ótimo. Agora, para aumentar um pouco o desafio, vamos tentar instalar o Bun — um gerenciador de pacotes alternativo ao Node.js. Não sei se vai funcionar, mas vamos tentar.

curl -fsSL https://bun.sh/install | bash
Enter fullscreen mode Exit fullscreen mode

😒! O Bun precisa do unzip, que não está instalado no contêiner. Vamos instalar.

apt-get update && apt-get install -y unzip
Enter fullscreen mode Exit fullscreen mode

Agora, de volta à instalação do Bun.

curl -fsSL https://bun.sh/install | bash
Enter fullscreen mode Exit fullscreen mode

Funcionou! Só mais um comando que o Bun pediu para rodar, e está instalado.

source /root/.bashrc
Enter fullscreen mode Exit fullscreen mode

Na verdade, ele já estava instalado, mas para que funcionasse na sessão atual do shell, eu tive que rodar esse comando para recarregar o arquivo .bashrc.

Agora vamos testar se tudo está funcionando até agora.

composer --version
bun --version
Enter fullscreen mode Exit fullscreen mode

Lindo! 🎉 Agora podemos criar um projeto Laravel, conforme recomendado pela documentação oficial.

composer global require laravel/installer
Enter fullscreen mode Exit fullscreen mode

Esse comando instalará o Laravel Installer globalmente, para que possamos executá-lo de qualquer lugar.

Ok, agora, o próximo passo:

laravel new example-app
# bash: laravel: command not found
Enter fullscreen mode Exit fullscreen mode

Wat? Isso não estava no plano. Por que isso está acontecendo? Vamos tentar descobrir.

De acordo com a documentação do Composer, o comando composer global que acabei de rodar instala pacotes no diretório COMPOSER_HOME. Vamos verificar isso.

echo $COMPOSER_HOME
Enter fullscreen mode Exit fullscreen mode

Ah, ótimo - não tem a variável de ambiente COMPOSER_HOME. Vamos voltar para a documentação do Composer e ver o que ela diz. Ah, certo, por padrão ela é ~/.composer. Vamos verificar.

ls -la ~/.composer
Enter fullscreen mode Exit fullscreen mode

Bom, tem um diretório vendor lá, e ao verificá-lo (ls -la ~/.composer/vendor) eu vejo um diretório Laravel e um diretório bin dentro dele. Vamos dar uma olhada no diretório bin.

ls -la ~/.composer/vendor/bin
Enter fullscreen mode Exit fullscreen mode

Hmm, lá está um arquivo executável chamado laravel. Vamos tentar rodá-lo.

~/.composer/vendor/bin/laravel  --version
Enter fullscreen mode Exit fullscreen mode

Funcionou! Agora preciso adicionar o diretório ~/.composer/vendor/bin à variável de ambiente PATH.

export PATH=$PATH:/root/.composer/vendor/bin
Enter fullscreen mode Exit fullscreen mode

Outra forma de acessar o executável laravel é pelo comando Composer exec.

composer global exec laravel -- --version
Enter fullscreen mode Exit fullscreen mode

Agora posso (com sorte) criar um projeto Laravel.

laravel new my-app
Enter fullscreen mode Exit fullscreen mode

O processo de criação de um projeto Laravel agora faz umas perguntas a mais do que antes (já faz mais de um ano desde o meu último projeto Laravel), mas (meio que) funcionou. No final do processo, ele perguntou se eu queria rodar npm install e npm run dev. Eu disse "sim", mas falhou porque optei por usar Bun e ele não tem o npm instalado.

O projeto Bun parece promissor (essa é a minha primeira vez usando), mas o Laravel ainda parece otimizado para Node.js e NPM. Vou tentar instalar as dependências usando Bun.

cd my-app
bun i # ou bun install
Enter fullscreen mode Exit fullscreen mode

Bem, funcionou. Não tenho certeza se essa abordagem funcionará para todos os projetos Laravel. Mas, por enquanto, estou feliz com os resultados.

composer run dev
Enter fullscreen mode Exit fullscreen mode

E... não. Parece que a versão 12 do Laravel está no meio de uma migração do método "antigo" com php artisan ... para os scripts do Composer (até porque dentro destes scripts ele ainda roda o php artisan) e, embora sugira Bun como uma alternativa ao Node.js+NPM, ainda usa scripts NPM. O último comando falhou porque ele tentou rodar npx ... e recebi um erro sh: 1: npx: not found. Que pena.

Agora tenho algumas opções aqui:

  • Instalar Node.js e npx neste contêiner.
  • Mudar o projeto Laravel para usar Bun em vez de NPM.
  • Descobrir qual é o substituto do Bun para npx e usá-lo.

Vou optar pela última. A documentação do Bun CLI apresenta bunx ou bun x como substituto do npx. Verificando a origem do bunx dentro do contêiner (which bunx) e listando (ls -la $(which bunx)), posso ver que é um symlink para o executável bun. Então, vamos tentar criar outro symlink para ele, chamado — você adivinhou — npx.

ln -s $(which bunx) /usr/local/bin/npx
Enter fullscreen mode Exit fullscreen mode

Agora vamos tentar rodar o comando composer run dev novamente.

composer run dev
Enter fullscreen mode Exit fullscreen mode

E funcionou! Bem, pelo menos o comando npx funcionou. Agora recebi uma exceção PHP pedindo pela extensão pcntl. Felizmente, escolhi a imagem do PHP como base — diretamente da visão geral dela (https://hub.docker.com/_/php) temos "Como instalar mais extensões PHP" (como eu queria que todas as imagens tivessem esse tipo de documentação). Vamos tentar instalar a extensão que falta.

docker-php-ext-install pcntl
Enter fullscreen mode Exit fullscreen mode

E agora posso tentar novamente.

composer run dev
Enter fullscreen mode Exit fullscreen mode

E funcionou! Agora eu tenho um projeto Laravel 12 rodando em um contêiner com zero dependências instaladas no meu sistema. Estou feliz com os resultados até agora. Também estou feliz com todos os passos que tomei para chegar até aqui. Agora, vamos para o próximo passo e criar um Dockerfile para automatizar todos esses passos.

O Dockerfile

Agora me sinto confiante o suficiente para criar um Dockerfile para automatizar todos esses passos. Vamos criar um arquivo chamado Dockerfile (touch Dockerfile se você estiver em um sistema Unix (Linux ou MacOS), ou type nul > Dockerfile se você estiver no Windows) e colar o seguinte conteúdo.

FROM php:latest

# Instalar unzip
RUN apt update && apt install -y unzip

# Instalar Composer de outra forma, mas com o mesmo resultado
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Configurar as variáveis de ambiente
ENV HOME="/root"
ENV PATH="/root/.composer/vendor/bin:${PATH}:/root/.bun/bin"

# Instalar Bun
RUN curl -fsSL https://bun.sh/install | bash

# Criar alguns symlinks para o executável bun
RUN ln -s $(which bun) /usr/local/bin/npm
RUN ln -s $(which bunx) /usr/local/bin/npx

# Instalar o Laravel Installer
RUN composer global require laravel/installer

# Instalar a extensão pcntl
RUN docker-php-ext-install pcntl
Enter fullscreen mode Exit fullscreen mode

Agora vamos construir a imagem.

docker build -t laravel12 .
Enter fullscreen mode Exit fullscreen mode

Dá pra ver que eu adicionei mais uns comandos ao Dockerfile criando "aliases" para o npm e o npx (redirecinando para o bun e o bunx, respectivamente).

Este comando criará uma imagem chamada laravel12 baseada no Dockerfile que acabamos de criar. Neste ponto, o arquivo não é mais necessário, então, fique à vontade para excluí-lo.

Neste passo, acabamos de criar uma imagem no nosso daemon Docker local. Se você quiser compartilhar essa imagem com outros, pode enviá-la para um Docker registry. Mas esse é um assunto para outro momento. De qualquer forma, enviei essa imagem para minha conta no Docker Hub e ela está disponível para você usar.

docker pull ranierivalenca/laravel12

Testando

Com a imagem criada e registrada no daemon Docker local, agora podemos criar um contêiner com ela.

docker run -it --rm laravel12 bash
Enter fullscreen mode Exit fullscreen mode

Se tudo correr bem, você deve estar dentro do contêiner. Agora podemos criar um projeto Laravel.

Criando um projeto Laravel

Ok, vamos dar uma pausa para recapitular o que fizemos até aqui. Criamos um Dockerfile que cria um contêiner com todas as dependências necessárias para rodar um projeto Laravel 12. Construímos a imagem e conseguimos criar um contêiner com ela. Agora, se criarmos um projeto Laravel dentro desse contêiner, todos os arquivos do projeto ficarão dentro do contêiner e serão perdidos quando o contêiner for interrompido. Para evitar isso, podemos criar um volume para armazenar os arquivos do projeto no sistema host.

docker run -it --rm -v $(pwd):/app laravel12 laravel new my-app
Enter fullscreen mode Exit fullscreen mode

Dessa forma, estamos criando um volume que mapeia o diretório atual ($(pwd)) para o diretório /app dentro do contêiner. Assim, o diretório my-app será criado dentro do diretório atual no sistema host. Agora podemos rodar o projeto.

cd my-app
docker run -it --rm -v $(pwd):/app -w /app laravel12 composer run dev
Enter fullscreen mode Exit fullscreen mode

Esse comando cria um contêiner com o diretório my-app (agora o diretório atual graças ao comando cd) montado no diretório /app dentro do contêiner. Ele também define o diretório de trabalho como /app (para não precisarmos digitar /app em cada comando ou rodar um comando cd).

Tadah! Está rodando. Mas ainda não podemos acessar o projeto no navegador porque não expusemos nenhuma porta. E, além disso, por padrão (pelo menos neste momento), o comando composer run dev inicia tanto o servidor PHP quanto o Vite (JS) vinculados ao 127.0.0.1 (localhost). Isso funciona quando estamos rodando o projeto diretamente no sistema host, mas não quando estamos rodando dentro de um contêiner. Mesmo se expusermos as portas, o aplicativo dentro do contêiner não responderá a solicitações vindas do systema host porque ele está vinculado ao localhost do contêiner.

Tudo isso pode parecer um pouco confuso, mas não se preocupe. Vamos resolver isso. Resumidamente, tudo que precisamos fazer é alterar o comando artisan serve para escutar em 0.0.0.0 e adicionar a opção --host ao comando npm run dev. A gente pode fazer isso apenas alterando o arquivo composer.json do projeto.

sed -i 's/artisan serve/artisan serve --host 0.0.0.0/g' composer.json
sed -i 's/run dev/run dev --host/g' composer.json
Enter fullscreen mode Exit fullscreen mode

Se você estiver usando Windows, você pode instalar o Git Bash ou o WSL para rodar esses comandos. Se você estiver usando o MacOS, o comando sed -i espera um argumento para criar um backup do arquivo original. Se você não quiser criar um backup, adicione um argumento vazio, como sed -i ''. Claro que você também pode fazer essa edição manualmente usando um editor de texto.

Agora a gente pode rodar o projeto de novo, expondo as portas necessárias.

docker run -it --rm -v $(pwd):/app -w /app -p 8000:8000 -p 5173:5173 laravel12 composer run dev
Enter fullscreen mode Exit fullscreen mode

Agora você pode acessar o projeto Laravel no navegador em http://localhost:8000.

Expandindo o Dockerfile e dependências

Como eu falei antes, este projeto específico requer uma conexão com o MongoDB. Então, temos que instalar a extensão mongodb para o PHP. Também vamos precisar ter o servidor de MongoDB rodando. Vamos por partes.

Adicionando a extensão PHP MongoDB

Para nossa sorte, a imagem do PHP que estamos usando vem com alguns scripts para ajudar a instalar extensões PHP. Vamos usar um desses scripts para instalar a extensão mongodb. Como antes, vamos criar um contêiner para testar a instalação manualmente.

docker run -it --rm laravel12 bash
# Dentre do contêiner
docker-php-ext-install mongodb
Enter fullscreen mode Exit fullscreen mode

Epa. Não funcionou. A extensão mongodb não está disponível em /usr/src/php/ext, diretório do contêiner que vêm com algumas extensões pré-instaladas. Vamos tentar instalar a extensão manualmente. Vamos tentar com o PECL.

pecl install mongodb
Enter fullscreen mode Exit fullscreen mode

O processo de instalação vai perguntar algumas coisas. Apenas vá apertando Enter para acetar os valores padrão. Você pode ler mais sobre as opções no site do PECL, no site do MongoDB ou no site do PHP.

Agora (depois de algum tempo - essa extensão é grande e o processo de instalação é lento), a extensão mongodb está instalada, mas não está habilitada. Vamos habilitá-la.

docker-php-ext-enable mongodb
Enter fullscreen mode Exit fullscreen mode

E agora a extensão mongodb está habilitada. Vamos testar.

php -m | grep mongodb
Enter fullscreen mode Exit fullscreen mode

Pronto. Podemos sair do contêiner e criar o Dockerfile para automatizar a instalação da extensão mongodb.

FROM laravel12

# Instalar a extensão mongodb
RUN pecl install mongodb && docker-php-ext-enable mongodb
Enter fullscreen mode Exit fullscreen mode

Você pode notar que esse processo é bem simiar ao que usamos pra instalar a extensão pcntl antes. A principal diferença é que a extensão pcntl já está disponível no diretório /usr/src/php/ext e pode ser instalada com o comando docker-php-ext-install. Já a extensão mongodb não está disponível lá e tem que ser instalada com o PECL.

Usando esse novo Dockerfile, podemos construir uma nova imagem.

docker build -t laravel12:with-mongodb .
docker run -it --rm laravel12:with-mongodb php -m | grep mongodb
Enter fullscreen mode Exit fullscreen mode

Usando esses conhecimentos, podemos expandir o Dockerfile para instalar qualquer extensão PHP que precisarmos, seja usando o docker-php-ext-install ou o PECL.

Se essa imagem for exatamente o que você precisa (ou seja, algo capaz de criar / executar um projeto em Laravel 12 com MongoDB), você pode encontrá-la no meu Docker Hub e usá-la.

docker pull ranierivalenca/laravel12:with-mongodb

Adicionando o servidor MongoDB

Para rodar o servidor MongoDB, podemos usar a imagem oficial do MongoDB. Vamos criar um contêiner com essa imagem.

docker run -d --name mongodb -p 27017:27017 --network-alias mongo mongo
Enter fullscreen mode Exit fullscreen mode

Com esse comando, estamos criando um contêiner chamado mongodb com a imagem oficial do MongoDB. Estamos expondo a porta 27017 para o host (caso queiramos acessar o servidor MongoDB diretamente) e estamos adicionando um alias de rede mongo para o contêiner (para que possamos nos referir a ele pelo nome mongo em vez de pelo IP) dentro da rede padrão do Docker. Agora, podemos acessar o servidor MongoDB pelo nome mongo - lembre que o projeto Laravel vai rodar dentro de um contêiner e, por padrão, os contêineres podem se comunicar entre si pelo nome ou pelo IP (ambos atribuídos pelo Docker).

Conclusão

Eu espero que você tenha aproveitado essa jornada tanto quanto eu. Eu aprendi muito enquanto escrevia este artigo e espero que você também tenha aprendido algo. Se você tiver alguma dúvida, sugestão ou correção, fique à vontade para comentar.

Top comments (0)