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:
- Eu confio mais nelas.
- 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
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');"
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
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
😒! O Bun precisa do unzip
, que não está instalado no contêiner. Vamos instalar.
apt-get update && apt-get install -y unzip
Agora, de volta à instalação do Bun.
curl -fsSL https://bun.sh/install | bash
Funcionou! Só mais um comando que o Bun pediu para rodar, e está instalado.
source /root/.bashrc
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
Lindo! 🎉 Agora podemos criar um projeto Laravel, conforme recomendado pela documentação oficial.
composer global require laravel/installer
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
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
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
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
Hmm, lá está um arquivo executável chamado laravel
. Vamos tentar rodá-lo.
~/.composer/vendor/bin/laravel --version
Funcionou! Agora preciso adicionar o diretório ~/.composer/vendor/bin
à variável de ambiente PATH
.
export PATH=$PATH:/root/.composer/vendor/bin
Outra forma de acessar o executável
laravel
é pelo comando Composer exec.
composer global exec laravel -- --version
Agora posso (com sorte) criar um projeto Laravel.
laravel new my-app
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
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
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
Agora vamos tentar rodar o comando composer run dev
novamente.
composer run dev
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
E agora posso tentar novamente.
composer run dev
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
Agora vamos construir a imagem.
docker build -t laravel12 .
Dá pra ver que eu adicionei mais uns comandos ao Dockerfile criando "aliases" para o
npm
e onpx
(redirecinando para obun
e obunx
, 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
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
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
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
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, comosed -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
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
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
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
E agora a extensão mongodb
está habilitada. Vamos testar.
php -m | grep mongodb
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
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
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
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)