Pesquisa de site

Como Instalar e Configurar o Laravel com Docker Compose no Ubuntu 22.04


Introdução

Containerizar uma aplicação refere-se ao processo de adaptar uma aplicação e seus componentes para poder executá-la em ambientes leves conhecidos como containers. Esses ambientes são isolados e descartáveis, e podem ser utilizados para desenvolver, testar e implantar aplicações em produção.

Neste guia, usaremos o Docker Compose para containerizar uma aplicação Laravel para desenvolvimento. Quando você terminar, terá uma aplicação Laravel de demonstração rodando em três contêineres de serviço separados:

  • Um serviço app rodando PHP7.4-FPM;
  • Um serviço db executando MySQL 8.0;
  • Um serviço nginx que utiliza o serviço app para processar código PHP antes de servir a aplicação Laravel ao usuário final.

Para permitir um processo de desenvolvimento mais ágil e facilitar a depuração da aplicação, manteremos os arquivos da aplicação sincronizados usando volumes compartilhados. Também veremos como usar os comandos docker compose exec para executar o Composer e o Artisan no contêiner app.

Implante suas aplicações frontend do GitHub usando a Plataforma de Aplicativos do Linux-Console.net. Deixe o Linux-Console.net se preocupar com a escalabilidade do seu app.

Principais Pontos:

  • A conteinerização torna o desenvolvimento previsível: Executar Laravel no Docker garante ambientes consistentes em diferentes máquinas, eliminando problemas do tipo “funciona no meu computador” e facilitando a colaboração.
  • A separação de serviços melhora a manutenibilidade: Dividir PHP, MySQL e Nginx em contêineres separados ajuda a isolar problemas, simplifica atualizações e reflete configurações de produção reais.
  • Variáveis de ambiente são fundamentais para uma configuração flexível: Usar um arquivo .env permite que os desenvolvedores alterem credenciais de banco de dados e outras configurações sem mexer no código da aplicação.
  • Imagens Docker personalizadas melhoram o controle e a segurança: Criar sua própria imagem PHP garante que você instale apenas o que seu projeto precisa, mantendo o ambiente leve e seguro.
  • As permissões de arquivos devem corresponder aos usuários do contêiner: Configurar a propriedade e as permissões corretas para os diretórios graváveis do Laravel evita erros em tempo de execução e garante um desenvolvimento local tranquilo.
  • Usar arquivos de substituição suporta múltiplos ambientes: docker-compose.override.yml permite ajustes locais—como recarga ao vivo—sem alterar as configurações de produção. Isso mantém o desenvolvimento e a implantação claramente separados.
  • A solução de problemas começa com a compreensão do comportamento do contêiner: Saber como inspecionar logs, verificar os serviços em execução e testar a conectividade é essencial para resolver a maioria dos problemas rapidamente em ambientes conteinerizados.
  • Uma configuração baseada em Docker escala conforme seu fluxo de trabalho: Uma vez construída, a mesma configuração pode alimentar ambientes locais, de testes ou de produção, criando uma base estável e reutilizável para futuros projetos Laravel.

Pré-requisitos

  • Acesso a uma máquina local Ubuntu ou servidor de desenvolvimento como um usuário não-root com privilégios de sudo. Se você estiver usando um servidor remoto, é recomendável ter um firewall ativo instalado. Para configurá-los, consulte nosso Guia de Configuração Inicial de Servidor para Ubuntu.
  • Docker instalado no seu servidor, seguindo os Passos 1 e 2 de Como Instalar e Usar Docker no Ubuntu.
  • Docker Compose instalado no seu servidor, seguindo o Passo 1 de Como Instalar e Usar o Docker Compose no Ubuntu.

Passo 1 — Obtendo o Aplicativo Demo

Para começar, vamos buscar a aplicação de demonstração do Laravel em seu repositório no Github. Estamos interessados no branch tutorial-01, que contém a aplicação básica do Laravel que criamos no guia Como Instalar e Configurar o Laravel com Nginx no Ubuntu 22.04 (LEMP).

Para obter o código do aplicativo que é compatível com este tutorial, faça o download da versão tutorial-1.0.1 para o seu diretório home com:

cd ~

curl -L https://github.com/do-community/travellist-laravel-demo/archive/tutorial-1.0.1.zip -o travellist.zip

Vamos precisar do comando unzip para descompactar o código da aplicação. Caso você não tenha instalado esse pacote antes, faça isso agora com:

sudo apt update

sudo apt install unzip

Agora, descompacte o conteúdo do aplicativo e renomeie o diretório descompactado para facilitar o acesso:

unzip travellist.zip

mv travellist-laravel-demo-tutorial-1.0.1 travellist-demo

Navegue até o diretório travellist-demo:

cd travellist-demo

No próximo passo, vamos criar um arquivo de configuração .env para configurar a aplicação.

Passo 2 — Configurando o arquivo .env do aplicativo

Os arquivos de configuração do Laravel estão localizados em um diretório chamado config, dentro do diretório raiz da aplicação. Além disso, um arquivo .env é usado para configurar informações dependentes do ambiente, como credenciais e qualquer informação que possa variar entre implantações. Este arquivo não é incluído no controle de versão.

Aviso: O arquivo de configuração do ambiente contém informações sensíveis sobre o seu servidor, incluindo credenciais do banco de dados e chaves de segurança. Por esse motivo, você nunca deve compartilhar este arquivo publicamente.

Os valores contidos no arquivo .env terão prioridade sobre os valores definidos nos arquivos de configuração regulares localizados no diretório config. Cada instalação em um novo ambiente requer um arquivo de ambiente personalizado para definir itens como configurações de conexão com o banco de dados, opções de depuração, URL da aplicação, entre outros itens que podem variar dependendo do ambiente em que a aplicação está sendo executada.

Agora vamos criar um novo arquivo .env para personalizar as opções de configuração do ambiente de desenvolvimento que estamos configurando. O Laravel vem com um arquivo de exemplo .env que podemos copiar para criar o nosso próprio:

cp .env.example .env

Abra este arquivo usando nano ou o editor de texto de sua preferência:

nano .env

O arquivo .env atual da aplicação de demonstração travellist contém configurações para usar um banco de dados MySQL local, com 127.0.0.1 como host do banco de dados. Precisamos atualizar a variável DB_HOST para que ela aponte para o serviço de banco de dados que criaremos em nosso ambiente Docker. Neste guia, chamaremos nosso serviço de banco de dados de db. Vá em frente e substitua o valor listado de DB_HOST pelo nome do serviço de banco de dados:

APP_NAME=Travellist
APP_ENV=dev
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8000

LOG_CHANNEL=stack

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=travellist
DB_USERNAME=travellist_user
DB_PASSWORD=password
...

Sinta-se à vontade para também alterar o nome do banco de dados, o nome de usuário e a senha, se desejar. Essas variáveis serão usadas em uma etapa posterior, onde configuraremos o arquivo docker-compose.yml para configurar nossos serviços.

Salve o arquivo quando terminar de editar. Se você usou nano, pode fazer isso pressionando Ctrl+x, depois Y e Enter para confirmar.

Passo 3 – Configurando o Dockerfile da Aplicação

Embora nossos serviços MySQL e Nginx sejam baseados em imagens padrão obtidas do Docker Hub, ainda precisamos construir uma imagem personalizada para o container da aplicação. Vamos criar um novo Dockerfile para isso.

Nossa imagem travellist será baseada na imagem oficial do PHP php:7.4-fpm do Docker Hub. Sobre esse ambiente básico do PHP-FPM, vamos instalar alguns módulos PHP adicionais e a ferramenta de gerenciamento de dependências Composer.

Também criaremos um novo usuário do sistema; isso é necessário para executar os comandos artisan e composer enquanto desenvolve a aplicação. A configuração uid garante que o usuário dentro do contêiner tenha o mesmo uid que o seu usuário do sistema na máquina host, onde você está executando o Docker. Dessa forma, quaisquer arquivos criados por esses comandos são replicados no host com as permissões corretas. Isso também significa que você poderá usar o editor de código de sua preferência na máquina host para desenvolver a aplicação que está rodando dentro dos contêineres.

Crie um novo Dockerfile com:

nano Dockerfile

Copie o seguinte conteúdo para o seu Dockerfile:

FROM php:7.4-fpm

# Arguments defined in docker-compose.yml
ARG user
ARG uid

# Install system dependencies
RUN apt-get update && apt-get install -y \
    git \
    curl \
    libpng-dev \
    libonig-dev \
    libxml2-dev \
    zip \
    unzip

# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd

# Get latest Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Create system user to run Composer and Artisan Commands
RUN useradd -G www-data,root -u $uid -d /home/$user $user
RUN mkdir -p /home/$user/.composer && \
    chown -R $user:$user /home/$user

# Set working directory
WORKDIR /var/www

USER $user

Não se esqueça de salvar o arquivo quando terminar.

Nosso Dockerfile começa definindo a imagem base que estamos usando: php:7.4-fpm.

Após instalar os pacotes do sistema e as extensões PHP, instalamos o Composer copiando o executável composer da sua imagem oficial mais recente para a nossa própria imagem de aplicação.

Um novo usuário do sistema é então criado e configurado usando os argumentos user e uid que foram declarados no início do Dockerfile. Esses valores serão injetados pelo Docker Compose durante a construção.

Finalmente, configuramos o diretório de trabalho padrão como /var/www e mudamos para o usuário recém-criado. Isso garantirá que você esteja se conectando como um usuário comum e que esteja no diretório correto ao executar os comandos composer e artisan no contêiner da aplicação.

Etapa 4 — Configurando a Configuração do Nginx e os Arquivos de Dump do Banco de Dados

Ao criar ambientes de desenvolvimento com Docker Compose, muitas vezes é necessário compartilhar arquivos de configuração ou de inicialização com os containers de serviço, a fim de configurar ou iniciar esses serviços. Essa prática facilita a realização de alterações nos arquivos de configuração para ajustar seu ambiente enquanto você desenvolve a aplicação.

Agora vamos configurar uma pasta com arquivos que serão usados para configurar e inicializar nossos contêineres de serviço.

Para configurar o Nginx, vamos compartilhar um arquivo travellist.conf que configurará como a aplicação será servida. Crie a pasta docker-compose/nginx com:

mkdir -p docker-compose/nginx

Abra um novo arquivo chamado travellist.conf dentro desse diretório:

nano docker-compose/nginx/travellist.conf

Copie a seguinte configuração do Nginx para esse arquivo:

server {
    listen 80;
    index index.php index.html;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/public;
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
    location / {
        try_files $uri $uri/ /index.php?$query_string;
        gzip_static on;
    }
}

Este arquivo irá configurar o Nginx para escutar na porta 80 e usar index.php como página inicial padrão. Ele definirá o diretório raiz para /var/www/public e, em seguida, configurará o Nginx para usar o serviço app na porta 9000 para processar arquivos *.php.

Salve e feche o arquivo quando terminar de editar.

Para configurar o banco de dados MySQL, iremos compartilhar um dump do banco de dados que será importado quando o contêiner for inicializado. Esta é uma funcionalidade fornecida pela imagem MySQL 8.0 que usaremos nesse contêiner.

Crie uma nova pasta para seus arquivos de inicialização do MySQL dentro da pasta docker-compose:

mkdir docker-compose/mysql

Abra um novo arquivo .sql:

nano docker-compose/mysql/init_db.sql

O seguinte dump do MySQL é baseado no banco de dados que configuramos em nosso guia Laravel no LEMP. Ele criará uma nova tabela chamada places. Em seguida, irá popular a tabela com um conjunto de lugares de exemplo.

Adicione o seguinte código ao arquivo:

DROP TABLE IF EXISTS `places`;

CREATE TABLE `places` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `visited` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO `places` (name, visited) VALUES ('Berlin',0),('Budapest',0),('Cincinnati',1),('Denver',0),('Helsinki',0),('Lisbon',0),('Moscow',1),('Nairobi',0),('Oslo',1),('Rio',0),('Tokyo',0);

A tabela places contém três campos: id, name e visited. O campo visited é uma marcação usada para identificar os lugares que ainda estão a visitar. Fique à vontade para alterar os lugares de exemplo ou incluir novos. Salve e feche o arquivo quando terminar.

Terminamos de configurar o Dockerfile da aplicação e os arquivos de configuração dos serviços. Em seguida, vamos configurar o Docker Compose para usar esses arquivos ao criar nossos serviços.

Passo 5 — Criando um Ambiente Multi-Container com Docker Compose

O Docker Compose permite criar ambientes de múltiplos contêineres para aplicações que rodam no Docker. Ele utiliza definições de serviço para construir ambientes totalmente personalizáveis com múltiplos contêineres que podem compartilhar redes e volumes de dados. Isso permite uma integração perfeita entre os componentes da aplicação.

Para configurar nossas definições de serviço, criaremos um novo arquivo chamado docker-compose.yml. Normalmente, este arquivo está localizado na raiz da pasta da aplicação e define seu ambiente conteinerizado, incluindo as imagens base que você usará para construir seus containers e como seus serviços irão interagir.

Vamos definir três serviços diferentes em nosso arquivo docker-compose.yml: app, db e nginx.

O serviço app irá construir uma imagem chamada travellist, baseada no Dockerfile que criamos anteriormente. O contêiner definido por este serviço executará um servidor php-fpm para interpretar o código PHP e enviar os resultados de volta para o serviço nginx, que estará rodando em um contêiner separado. O serviço mysql define um contêiner executando um servidor MySQL 8.0. Nossos serviços irão compartilhar uma rede do tipo bridge chamada travellist.

Os arquivos da aplicação serão sincronizados em ambos os serviços app e nginx através de bind mounts. Bind mounts são úteis em ambientes de desenvolvimento porque permitem uma sincronização bidirecional eficiente entre a máquina host e os containers.

Crie um novo arquivo docker-compose.yml na raiz da pasta da aplicação:

nano docker-compose.yml

Um arquivo típico docker-compose.yml começa com uma definição de versão, seguida por um nó services, sob o qual todos os serviços são definidos. Redes compartilhadas geralmente são definidas na parte inferior desse arquivo.

Para começar, copie este código base para o seu arquivo docker-compose.yml:

version: "3.9"
services:


networks:
  travellist:
    driver: bridge

Agora vamos editar o nó services para incluir os serviços app, db e nginx.

O Serviço app

O serviço app configurará um contêiner chamado travellist-app. Ele cria uma nova imagem Docker com base em um Dockerfile localizado no mesmo caminho do arquivo docker-compose.yml. A nova imagem será salva localmente com o nome travellist.

Apesar de a raiz do documento que está sendo servida como a aplicação estar localizada no contêiner nginx, precisamos que os arquivos da aplicação também estejam em algum lugar dentro do contêiner app, para que possamos executar tarefas de linha de comando com a ferramenta Laravel Artisan.

Copie a seguinte definição de serviço sob o seu nó services, dentro do arquivo docker-compose.yml:

  app:
    build:
      args:
        user: sammy
        uid: 1000
      context: ./
      dockerfile: Dockerfile
    image: travellist
    container_name: travellist-app
    restart: unless-stopped
    working_dir: /var/www/
    volumes:
      - ./:/var/www
    networks:
      - travellist

Estas configurações fazem o seguinte:

  • build: Esta configuração informa ao Docker Compose para construir uma imagem local para o serviço app, usando o caminho especificado (contexto) e o Dockerfile para instruções. Os argumentos user e uid são inseridos no Dockerfile para personalizar os comandos de criação de usuário no momento da construção.
  • imagem: O nome que será usado para a imagem que está sendo criada.
  • container_name: Define o nome do contêiner para este serviço.
  • restart: Sempre reinicie, a menos que o serviço esteja parado.
  • working_dir: Define o diretório padrão para este serviço como /var/www.
  • volumes: Cria um volume compartilhado que irá sincronizar o conteúdo do diretório atual com /var/www dentro do container. Observe que este não é o seu diretório raiz de documentos, já que ele estará no container nginx.
  • networks: Configura este serviço para usar uma rede chamada travellist.

O serviço db

O serviço db utiliza uma imagem pré-construída do MySQL 8.0 do Docker Hub. Como o Docker Compose carrega automaticamente os arquivos de variáveis .env localizados no mesmo diretório do arquivo docker-compose.yml, podemos obter nossas configurações de banco de dados do arquivo .env do Laravel que criamos em uma etapa anterior.

Inclua a seguinte definição de serviço no seu nó services, logo após o serviço app:

  db:
    image: mysql:8.0
    container_name: travellist-db
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_USER: ${DB_USERNAME}
      SERVICE_TAGS: dev
      SERVICE_NAME: mysql
    volumes:
      - ./docker-compose/mysql:/docker-entrypoint-initdb.d
    networks:
      - travellist

Estas configurações fazem o seguinte:

  • image: Define a imagem do Docker que deve ser usada para este contêiner. Neste caso, estamos usando uma imagem MySQL 8.0 do Docker Hub.
  • container_name: Define o nome do contêiner para este serviço: travellist-db.
  • restart: Sempre reinicie este serviço, a menos que ele seja explicitamente parado.
  • environment: Define variáveis de ambiente no novo contêiner. Estamos usando valores obtidos do arquivo .env do Laravel para configurar nosso serviço MySQL, que criará automaticamente um novo banco de dados e usuário com base nas variáveis de ambiente fornecidas.
  • volumes: Cria um volume para compartilhar um dump de banco de dados .sql que será usado para inicializar o banco de dados da aplicação. A imagem do MySQL importará automaticamente os arquivos .sql colocados no diretório /docker-entrypoint-initdb.d dentro do container.
  • networks: Configura este serviço para usar uma rede chamada travellist.

O serviço nginx

O serviço nginx utiliza uma imagem Nginx pré-construída baseada no Alpine, uma distribuição Linux leve. Ele cria um contêiner chamado travellist-nginx e utiliza a definição ports para criar um redirecionamento da porta 8000 no sistema hospedeiro para a porta 80 dentro do contêiner.

Inclua a seguinte definição de serviço no seu nó services, logo após o serviço db:

  nginx:
    image: nginx:1.17-alpine
    container_name: travellist-nginx
    restart: unless-stopped
    ports:
      - 8000:80
    volumes:
      - ./:/var/www
      - ./docker-compose/nginx:/etc/nginx/conf.d
    networks:
      - travellist

Estas configurações fazem o seguinte:

  • image: Define a imagem Docker que deve ser usada para este contêiner. Neste caso, estamos usando a imagem Alpine Nginx 1.17.
  • container_name: Define o nome do container para este serviço: travellist-nginx.
  • restart: Sempre reinicie este serviço, a menos que ele seja explicitamente parado.
  • ports: Configura uma redireção de porta que permitirá o acesso externo pela porta 8000 ao servidor web que está rodando na porta 80 dentro do contêiner.
  • volumes: Cria dois volumes compartilhados. O primeiro irá sincronizar os conteúdos do diretório atual com /var/www dentro do contêiner. Dessa forma, quando você fizer alterações locais nos arquivos da aplicação, elas serão rapidamente refletidas na aplicação servida pelo Nginx dentro do contêiner. O segundo volume garantirá que nosso arquivo de configuração do Nginx, localizado em docker-compose/nginx/travellist.conf, seja copiado para a pasta de configuração do Nginx do contêiner.
  • networks: Configura este serviço para usar uma rede chamada travellist.

Arquivo docker-compose.yml concluído

É assim que nosso arquivo docker-compose.yml final fica:

version: "3.9"
services:
  app:
    build:
      args:
        user: sammy
        uid: 1000
      context: ./
      dockerfile: Dockerfile
    image: travellist
    container_name: travellist-app
    restart: unless-stopped
    working_dir: /var/www/
    volumes:
      - ./:/var/www
    networks:
      - travellist

  db:
    image: mysql:8.0
    container_name: travellist-db
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_USER: ${DB_USERNAME}
      SERVICE_TAGS: dev
      SERVICE_NAME: mysql
    volumes:
      - ./docker-compose/mysql:/docker-entrypoint-initdb.d
    networks:
      - travellist

  nginx:
    image: nginx:1.17-alpine
    container_name: travellist-nginx
    restart: unless-stopped
    ports:
      - 8000:80
    volumes:
      - ./:/var/www
      - ./docker-compose/nginx:/etc/nginx/conf.d/
    networks:
      - travellist

networks:
  travellist:
    driver: bridge

Certifique-se de salvar o arquivo quando terminar.

Passo 6 — Executando a Aplicação com Docker Compose

Agora usaremos os comandos docker compose para construir a imagem da aplicação e executar os serviços que especificamos em nossa configuração.

Construa a imagem app com o seguinte comando:

docker compose build app

Este comando pode levar alguns minutos para ser concluído. Você verá uma saída semelhante a esta:

Building app
Sending build context to Docker daemon  377.3kB
Step 1/11 : FROM php:7.4-fpm
 ---> 8c08d993542f
Step 2/11 : ARG user
 ---> e3ce3af04d87
Step 3/11 : ARG uid
 ---> 30cb921ef7df
Step 4/11 : RUN apt-get update && apt-get install -y     git     curl     libpng-dev     libonig-dev     libxml2-dev     zip     unzip
. . .
 ---> b6dbc7a02e95
Step 5/11 : RUN apt-get clean && rm -rf /var/lib/apt/lists/*
 ---> 10ef9dde45ad
. . .
Step 6/11 : RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
. . .
 ---> 920e4f09ec75
Step 7/11 : COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
 ---> dbbcd44e44af
Step 8/11 : RUN useradd -G www-data,root -u $uid -d /home/$user $user
 ---> db98e899a69a
Step 9/11 : RUN mkdir -p /home/$user/.composer &&     chown -R $user:$user /home/$user
 ---> 5119e26ebfea
Step 10/11 : WORKDIR /var/www
 ---> 699c491611c0
Step 11/11 : USER $user
 ---> cf250fe8f1af
Successfully built cf250fe8f1af
Successfully tagged travellist:latest

Quando a build estiver concluída, você pode executar o ambiente em modo de segundo plano com:

docker compose up -d
Creating travellist-db    ... done
Creating travellist-app   ... done
Creating travellist-nginx ... done

Isso fará com que seus contêineres sejam executados em segundo plano. Para mostrar informações sobre o estado dos seus serviços ativos, execute:

docker compose ps

Você verá uma saída assim:

      Name                    Command              State                  Ports                
-----------------------------------------------------------------------------------------------
travellist-app     docker-php-entrypoint php-fpm   Up      9000/tcp                            
travellist-db      docker-entrypoint.sh mysqld     Up      3306/tcp, 33060/tcp                 
travellist-nginx   nginx -g daemon off;            Up      0.0.0.0:8000->80/tcp,:::8000->80/tcp

Seu ambiente está agora ativo e funcionando.

Agora você pode verificar se cada serviço em contêiner está respondendo corretamente usando comandos simples de curl.

  1. Para testar o serviço web Nginx:

    curl -I http://localhost:8000

    Saída esperada:

    HTTP/1.1 200 OK
    Server: nginx/1.17.10
    Content-Type: text/html; charset=UTF-8

    Isso confirma que o contêiner Nginx está em execução e respondendo a solicitações da sua aplicação Laravel.

  2. Para testar o PHP-FPM através do Nginx:

    curl -s http://localhost:8000 | grep "Berlin"

    Se o aplicativo estiver funcionando corretamente, este comando retorna o HTML da página de boas-vindas que inclui a palavra Berlin.

Ainda precisamos executar alguns comandos para finalizar a configuração do aplicativo. Você pode usar o comando docker compose exec para executar comandos nos contêineres do serviço, como um ls -l para mostrar informações detalhadas sobre os arquivos no diretório do aplicativo:

docker compose exec app ls -l
total 256
-rw-r--r-- 1 sammy sammy    737 Apr 18 14:21 Dockerfile
-rw-r--r-- 1 sammy sammy    101 Jan  7  2020 README.md
drwxr-xr-x 6 sammy sammy   4096 Jan  7  2020 app
-rwxr-xr-x 1 sammy sammy   1686 Jan  7  2020 artisan
drwxr-xr-x 3 sammy sammy   4096 Jan  7  2020 bootstrap
-rw-r--r-- 1 sammy sammy   1501 Jan  7  2020 composer.json
-rw-r--r-- 1 sammy sammy 179071 Jan  7  2020 composer.lock
drwxr-xr-x 2 sammy sammy   4096 Jan  7  2020 config
drwxr-xr-x 5 sammy sammy   4096 Jan  7  2020 database
drwxr-xr-x 4 sammy sammy   4096 Apr 18 14:22 docker-compose
-rw-r--r-- 1 sammy sammy   1017 Apr 18 14:29 docker-compose.yml
-rw-r--r-- 1 sammy sammy   1013 Jan  7  2020 package.json
-rw-r--r-- 1 sammy sammy   1405 Jan  7  2020 phpunit.xml
drwxr-xr-x 2 sammy sammy   4096 Jan  7  2020 public
-rw-r--r-- 1 sammy sammy    273 Jan  7  2020 readme.md
drwxr-xr-x 6 sammy sammy   4096 Jan  7  2020 resources
drwxr-xr-x 2 sammy sammy   4096 Jan  7  2020 routes
-rw-r--r-- 1 sammy sammy    563 Jan  7  2020 server.php
drwxr-xr-x 5 sammy sammy   4096 Jan  7  2020 storage
drwxr-xr-x 4 sammy sammy   4096 Jan  7  2020 tests
-rw-r--r-- 1 sammy sammy    538 Jan  7  2020 webpack.mix.js

Agora vamos executar composer install para instalar as dependências da aplicação:

docker compose exec app rm -rf vendor composer.lock

docker compose exec app composer install

Você verá uma saída assim:

No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.
. . .
Lock file operations: 89 installs, 0 updates, 0 removals
  - Locking doctrine/inflector (2.0.4)
  - Locking doctrine/instantiator (1.4.1)
  - Locking doctrine/lexer (1.2.3)
  - Locking dragonmantank/cron-expression (v2.3.1)
  - Locking egulias/email-validator (2.1.25)
  - Locking facade/flare-client-php (1.9.1)
  - Locking facade/ignition (1.18.1)
  - Locking facade/ignition-contracts (1.0.2)
  - Locking fideloper/proxy (4.4.1)
  - Locking filp/whoops (2.14.5)
. . .
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 89 installs, 0 updates, 0 removals
  - Downloading doctrine/inflector (2.0.4)
  - Downloading doctrine/lexer (1.2.3)
  - Downloading dragonmantank/cron-expression (v2.3.1)
  - Downloading symfony/polyfill-php80 (v1.25.0)
  - Downloading symfony/polyfill-php72 (v1.25.0)
  - Downloading symfony/polyfill-mbstring (v1.25.0)
  - Downloading symfony/var-dumper (v4.4.39)
  - Downloading symfony/deprecation-contracts (v2.5.1)
. . .
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.

A última coisa que precisamos fazer antes de testar o aplicativo é gerar uma chave única do aplicativo com a ferramenta de linha de comando artisan do Laravel. Essa chave é usada para criptografar sessões de usuários e outros dados sensíveis:

docker compose exec app php artisan key:generate
Application key set successfully.

Agora vá para o seu navegador e acesse o nome de domínio ou o endereço IP do seu servidor na porta 8000:

http://server_domain_or_IP:8000

Nota: Caso você esteja executando esta demonstração em sua máquina local, use http://localhost:8000 para acessar o aplicativo pelo seu navegador.

Você verá uma página assim:

Você também pode testar a aplicação Laravel de dentro do contêiner do app sem usar um navegador:

docker compose exec app curl http://nginx

Se tudo estiver configurado corretamente, você deverá ver a saída HTML da página de boas-vindas padrão do Laravel.

<html>
<head>
    <title>Travel List</title>
</head>

<body>
    <h1>My Travel Bucket List</h1>
    <h2>Places I'd Like to Visit</h2>
    <ul>
        <li>Berlin</li>
        <li>Budapest</li>
        <li>Denver</li>
        <li>Helsinki</li>
        <li>Lisbon</li>
        <li>Nairobi</li>
        <li>Rio</li>
        <li>Tokyo</li>
    </ul>

    <h2>Places I've Already Been To</h2>
    <ul>
        <li>Cincinnati</li>
        <li>Moscow</li>
        <li>Oslo</li>
    </ul>
</body>
</html>

Você pode usar o comando logs para verificar os registros gerados pelos seus serviços:

docker compose logs nginx
Attaching to travellist-nginx
. . .
travellist-nginx | 172.24.9.1 - - [18/Apr/2022:14:49:16 +0000] "GET / HTTP/1.1" 200 627 "-" "curl/7.82.0"
travellist-nginx | 172.24.9.1 - - [18/Apr/2022:14:51:27 +0000] "GET / HTTP/1.1" 200 627 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0"
travellist-nginx | 172.24.9.1 - - [18/Apr/2022:14:51:27 +0000] "GET /favicon.ico HTTP/1.1" 200 0 "http://localhost:8000/" "Mozilla/5.0 (X11; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0"

Você também pode usar o seguinte comando curl para acionar entradas de log:

curl http://localhost:8000

Então execute novamente:

docker compose logs nginx

Você agora deve ver uma entrada correspondente no registro de acesso para sua solicitação.

Se você quiser pausar seu ambiente Docker Compose mantendo o estado de todos os seus serviços, execute:

docker compose pause
Pausing travellist-db    ... done
Pausing travellist-nginx ... done
Pausing travellist-app   ... done

Você pode então retomar seus serviços com:

docker compose unpause
Unpausing travellist-app   ... done
Unpausing travellist-nginx ... done
Unpausing travellist-db    ... done

Para desligar seu ambiente Docker Compose e remover todos os seus containers, redes e volumes, execute:

docker compose down
Stopping travellist-nginx ... done
Stopping travellist-db    ... done
Stopping travellist-app   ... done
Removing travellist-nginx ... done
Removing travellist-db    ... done
Removing travellist-app   ... done
Removing network travellist-laravel-demo_travellist

Para uma visão geral de todos os comandos do Docker Compose, consulte a referência de linha de comando do Docker Compose.

Corrigindo Permissões de Arquivos e Diretórios no Laravel

Ao executar o Laravel dentro de contêineres Docker, um dos problemas mais comuns que os desenvolvedores enfrentam está relacionado às permissões de arquivos e diretórios. O Laravel exige que certos diretórios sejam graváveis pelo processo PHP-FPM dentro do contêiner app. Se esses diretórios não forem graváveis, a aplicação pode falhar ao registrar erros, armazenar arquivos de configuração em cache ou salvar dados de sessão corretamente.

Você pode encontrar mensagens como:

The stream or file "/var/www/storage/logs/laravel.log" could not be opened in append mode: failed to open stream: Permission denied

ou

file_put_contents(/var/www/bootstrap/cache/config.php): Failed to open stream: Permission denied

Esses erros indicam que o usuário que está executando o PHP dentro do contêiner não tem permissão para escrever nos diretórios necessários.

Os passos a seguir descrevem como diagnosticar e corrigir esses problemas tanto em ambientes locais quanto de produção.

Ajustar permissões do diretório gravável

O Laravel requer que dois diretórios sejam graváveis pelo usuário do servidor web:

  • /var/www/storage
  • /var/www/bootstrap/cache

Se você vir erros de permissão, pode corrigi-los ajustando a propriedade e as permissões de dentro do contêiner:

docker compose exec app chown -R sammy:sammy /var/www/storage /var/www/bootstrap/cache

docker compose exec app chmod -R 775 /var/www/storage /var/www/bootstrap/cache

Se o seu Dockerfile usar um nome de usuário diferente (definido através do argumento de build user), substitua sammy por esse nome de usuário.

Você pode verificar a propriedade com o seguinte comando:

docker compose exec app ls -ld /var/www/storage /var/www/bootstrap/cache

A saída deve mostrar que o usuário e o grupo correspondem ao processo PHP-FPM dentro do contêiner.

Se você quiser ajustar as permissões a partir da máquina host, execute:

sudo chown -R $(id -u):$(id -g) storage bootstrap/cache

sudo chmod -R 775 storage bootstrap/cache

Isso garante que ambos os diretórios do Laravel possam ser gravados pelo contêiner e pelo usuário do seu host.

Definir a Propriedade do Diretório Durante a Construção da Imagem

Para evitar que problemas de permissão ocorram após cada reconstrução, você pode definir a propriedade do diretório durante o processo de construção da imagem Docker. Abra seu Dockerfile e adicione a seguinte linha perto do final, antes de mudar para o usuário não-root:

RUN chown -R $user:www-data /var/www

Este comando garante que todos os arquivos e diretórios sob /var/www sejam de propriedade do usuário que você definiu em seu Dockerfile.

Para uma abordagem mais direcionada e segura, você pode ajustar apenas os diretórios que o Laravel precisa escrever:

RUN chown -R $user:www-data /var/www/storage /var/www/bootstrap/cache

Após fazer essa alteração, reconstrua a imagem e reinicie os contêineres:

docker compose build app

docker compose up -d

Na próxima vez que o contêiner iniciar, ele já terá as permissões corretas.

Verificar a Consistência do ID de Usuário Entre o Host e o Contêiner

Ao usar bind mounts, o Docker compartilha arquivos entre seu host e o contêiner. Se o UID numérico do usuário do host for diferente do UID definido no contêiner, podem ocorrer conflitos de propriedade dos arquivos. Isso é comum em sistemas onde os IDs de usuário variam entre máquinas.

Para verificar os valores UID, execute:

id -u $(whoami)

docker compose exec app id -u

Se esses dois números não coincidirem, atualize seu arquivo docker-compose.yml para passar seu UID real para o processo de build:

build:
  args:
    user: sammy
    uid: 1000

Então reconstrua a imagem:

docker compose build app

docker compose up -d

Isso garante que o usuário do contêiner corresponda ao seu usuário do host, evitando incompatibilidades de permissão em arquivos montados por vínculo.

Corrigir as permissões do diretório de cache do Composer

O Composer armazena dependências baixadas e arquivos de cache no diretório home do usuário dentro do contêiner (/home/sammy/.composer). Se esse diretório não for gravável, você pode ver avisos como:

Cannot create cache directory /home/sammy/.composer/cache

Você pode resolver isso criando o diretório de cache e atribuindo a propriedade correta:

docker compose exec app mkdir -p /home/sammy/.composer/cache

docker compose exec app chown -R sammy:sammy /home/sammy/.composer

Isso garante que o Composer possa armazenar dependências e arquivos em cache sem erros de permissão, o que também acelera futuras execuções de composer install.

Práticas Recomendadas para Ambientes de Produção

Em produção, o gerenciamento de permissões deve ser feito durante a construção da imagem, em vez de em tempo de execução. Isso melhora a consistência e a segurança.

Siga estas práticas para uma configuração pronta para produção:

  • Evite bind mounts para código de aplicação: Em vez disso, copie os arquivos da aplicação para a imagem durante a construção. Bind mounts devem ser reservados para desenvolvimento.

  • Definir permissões durante a construção: Adicione os comandos RUN chown e chmod ao seu Dockerfile para garantir a propriedade e os direitos de acesso corretos antes do deployment.

  • Use o usuário com menos privilégios: Garanta que o processo PHP seja executado sob um usuário que não seja root (a configuração atual com $user já faz isso).

  • Restringir permissões em diretórios não graváveis: A maioria dos arquivos de aplicativos deve ser somente leitura. Você pode aplicar isso com:

    chmod -R 755 /var/www
    
    chmod -R 775 /var/www/storage /var/www/bootstrap/cache

    Essa configuração permite que o Laravel escreva apenas onde é necessário, enquanto protege outros arquivos da aplicação contra modificações.

Seguindo estes passos, seus containers Laravel terão permissões de arquivos estáveis e previsíveis, tanto em ambientes locais quanto de produção. Isso reduz erros recorrentes e garante que o Laravel possa registrar dados, armazenar arquivos em cache e escrever sessões de forma confiável.

Entendendo docker-compose.override.yml para Ambientes Locais e de Produção

O Docker Compose permite que você defina aplicativos com múltiplos contêineres usando um único arquivo de configuração. Na maioria dos casos, essa configuração é armazenada em um arquivo chamado docker-compose.yml. No entanto, gerenciar múltiplos ambientes, como desenvolvimento local e produção, muitas vezes requer ajustes específicos para cada ambiente. É aí que o arquivo docker-compose.override.yml se torna útil.

Por padrão, o Docker Compose procura automaticamente um arquivo chamado docker-compose.override.yml no mesmo diretório do arquivo base do Compose. Quando encontrado, ele mescla as configurações de ambos os arquivos ao executar qualquer comando docker compose. Essa abordagem permite que os desenvolvedores mantenham uma configuração base limpa e reutilizável, enquanto personalizam aspectos específicos do ambiente para uso local ou em produção.

O Papel do docker-compose.override.yml

O arquivo docker-compose.override.yml é uma extensão da configuração base do Compose. Ele modifica ou adiciona propriedades que se aplicam apenas a um ambiente específico, sem a necessidade de alterar o arquivo principal docker-compose.yml. O arquivo de sobrescrição é especialmente útil em fluxos de trabalho de desenvolvimento local, onde os desenvolvedores frequentemente precisam montar o código-fonte, habilitar ferramentas de depuração ou modificar variáveis de ambiente para testes.

Quando você executa o comando docker compose up, o Docker mescla automaticamente o conteúdo de docker-compose.yml e docker-compose.override.yml. Se um serviço, volume ou configuração estiver definido em ambos os arquivos, os valores no arquivo de sobreposição têm precedência.

Para usar um arquivo de substituição diferente, por exemplo, um destinado à produção, você pode especificar o arquivo manualmente usando a flag -f:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Essa abordagem permite que você mantenha configurações separadas para diferentes ambientes enquanto utiliza uma única estrutura de comando consistente.

Estruturando para o Desenvolvimento Local

Ambientes de desenvolvimento local geralmente exigem mudanças frequentes, recarregamento de código em tempo real e registros detalhados. O arquivo docker-compose.override.yml torna possível introduzir essas funcionalidades sem afetar outros ambientes.

Um arquivo de substituição local típico pode incluir:

  • Montagens bind para atualizações de código em tempo real
  • Diretivas de build locais em vez de usar imagens pré-construídas
  • Variáveis de ambiente específicas de desenvolvimento
  • Serviços de depuração e testes

Por exemplo:

version: "3.9"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - ./src:/app
      - ./logs:/app/logs
    environment:
      - DATABASE_URL=postgres://postgres:postgres@db:5432/app_db
      - LOG_LEVEL=debug
    ports:
      - "8000:8000"
    depends_on:
      - db

  db:
    environment:
      - POSTGRES_PASSWORD=postgres

Neste exemplo:

  • O serviço app é construído diretamente a partir de um Dockerfile local (Dockerfile.dev), facilitando o teste de alterações sem precisar reconstruir as imagens por meio de um registro.
  • O código-fonte é montado usando bind mounts, permitindo que o container reflita alterações nos arquivos locais em tempo real.
  • Variáveis de ambiente, como LOG_LEVEL=debug, são configuradas para fornecer uma saída mais detalhada durante o desenvolvimento.
  • O banco de dados utiliza credenciais simples que são adequadas para testes locais, mas não devem ser usadas em produção.

Esta configuração oferece um ambiente de desenvolvimento mais rápido e flexível, mantendo a configuração base inalterada.

Preparando para Produção

Ambientes de produção exigem estabilidade, reprodutibilidade e segurança. Contêineres devem ser executados a partir de imagens pré-construídas ao invés de serem construídos localmente, e informações sensíveis devem ser gerenciadas de forma segura. Diferentemente do arquivo de substituição aplicado automaticamente usado para desenvolvimento local, as substituições de produção devem ser especificadas explicitamente.

Um arquivo de substituição de produção (docker-compose.prod.yml) geralmente é projetado para substituir etapas de construção locais por referências de imagem, usar volumes nomeados em vez de montagens de ligação e definir propriedades relacionadas à implantação, como limites de recursos e escalonamento.

Exemplo:

version: "3.9"

services:
  app:
    image: registry.example.com/myapp:1.2.3
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - LOG_LEVEL=info
    volumes:
      - app-data:/var/lib/app
    ports:
      - "8000:8000"
    restart: unless-stopped
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: "0.5"
          memory: 512M

  db:
    image: postgres:15
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD_FILE=/run/secrets/db_password

secrets:
  db_password:
    external: true

volumes:
  app-data:
  db-data:

Nesta configuração:

  • O serviço app referencia uma imagem pré-construída (myapp:1.2.3) armazenada em um registro, em vez de construir localmente.

  • As montagens vinculadas são substituídas por volumes nomeados para garantir o armazenamento de dados consistente e isolado.

  • Credenciais sensíveis são armazenadas com segurança usando segredos do Docker.

  • A seção deploy define o comportamento de escalonamento e os limites de recursos para melhor estabilidade sob carga.

  • A configuração deve ser aplicada explicitamente durante a implantação:

    docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Essa estrutura garante que as implantações de produção sejam consistentes, controladas e livres de artefatos de desenvolvimento local.

Comparando Substituições Locais e de Produção

Aspecto de ConfiguraçãoLocal (docker-compose.override.yml)Produção (docker-compose.prod.yml)
Aplicado automaticamenteSimNão (é necessário usar -f)
Manipulação de código-fonteMontagens vinculadas para atualizações ao vivoImagens pré-construídas do registro
Variáveis de ambienteValores voltados para desenvolvedoresValores seguros para produção
RegistroVerbose (debug)Conciso (info ou error)
Gestão de segredosValores simples ou .envSegredos do Docker ou segredos gerenciados
EscalonamentoRecipiente únicoMúltiplas réplicas
Otimização de desempenhoNão obrigatórioLimites de recursos definidos
Ferramentas de depuraçãoHabilitadoDesativado
Política de reinícioManual ou nenhum
restart: unless-stopped

Usar múltiplos arquivos Docker Compose fornece uma forma clara de separar as configurações específicas do ambiente da configuração base. O docker-compose.yml base define a estrutura comum da aplicação, enquanto o docker-compose.override.yml a ajusta para o desenvolvimento local. Ao implantar em produção, um arquivo de override explícito garante uma configuração previsível e segura.

Esse padrão permite que as equipes mantenham um fluxo de trabalho consistente. Os desenvolvedores podem iniciar a aplicação localmente usando:

docker compose up

e implantar em produção usando:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Ambos os ambientes compartilham a mesma configuração central, enquanto mantêm configurações independentes adequadas aos seus requisitos específicos. Essa separação melhora a manutenção, reduz erros de configuração e mantém os ambientes local e de produção alinhados.

Solução de Problemas do Seu Ambiente

Esta seção fornece etapas de solução de problemas para problemas comuns que você pode encontrar ao executar seu aplicativo com o Docker Compose.

502 Gateway Inválido (Nginx mostra respostas 502)

Sintomas:

  • O navegador mostra 502 Bad Gateway ao acessar http://localhost:8000.
  • Os logs do Nginx contêm linhas como recv() failed (111: Connection refused) ou connect() failed (111: Connection refused) while connecting to upstream.

Causas prováveis:

  • PHP-FPM (o serviço app) não está em execução ou não está ouvindo no socket/porta esperada.
  • O fastcgi_pass do Nginx aponta para o host/porta errados.
  • Nome do contêiner incompatível ou problemas de rede entre nginx e app.
  • app travou ao iniciar (por exemplo, devido a extensões ausentes ou erros de configuração).

Como confirmar:

  1. Inspecionar os logs do Nginx:

    docker compose logs nginx | tail -n 100

    Procure por mensagens 502 ou erros de conexão com o upstream.

  2. Verifique o status e os logs do contêiner app:

    docker compose ps app
    
    docker compose logs app --tail=200
  3. Confirme se o PHP-FPM está ouvindo dentro do contêiner app:

    docker compose exec app ss -ltnp | grep :9000 || docker compose exec app ss -lxp | grep php-fpm

    Esperado: ou um listener TCP na porta 9000 ou um socket Unix usado pelo nginx (menos comum com este compose).

  4. Confirme se o nginx consegue resolver e alcançar o app na rede do compose:

    docker compose exec nginx ping -c 2 app
    
    docker compose exec nginx wget -qO- --timeout=2 http://app:9000/ || true

    Se o ping falhar ou o wget expirar, há um problema de rede ou uma incompatibilidade de nome de serviço.

Correções comuns:

  • Se o PHP-FPM não estiver em execução, verifique os logs do app em busca de erros fatais do PHP ou falhas na inicialização. Corrija o erro subjacente (frequentemente uma extensão ausente ou problema de permissão) e reinicie:

    docker compose restart app
    
    docker compose logs app --tail=100
  • Se a configuração do Nginx usar um upstream diferente (por exemplo, fastcgi_pass 127.0.0.1:9000;), atualize docker-compose/nginx/travellist.conf para fastcgi_pass app:9000; (correspondendo ao nome do serviço do seu compose) e recarregue o Nginx:

    # After editing config in host
    
    docker compose exec nginx nginx -s reload
    
    docker compose logs nginx --tail=50
  • Se o PHP-FPM estiver ouvindo em um socket Unix (por exemplo, /run/php/php7.4-fpm.sock) mas o Nginx esperar TCP ou vice-versa, padronize para TCP app:9000 em configurações do Compose ou monte o socket corretamente entre os contêineres (recomendado: use TCP em Compose com múltiplos contêineres).

Extensões PHP faltando (erros ao executar o Composer ou Artisan, ou falhas em tempo de execução)

Sintomas:

  • Comandos do Composer ou do Laravel falham com mensagens como Classe "PDO" não encontrada, ext-pdo está ausente ou Chamada para função indefinida json_encode.
  • Erros de PHP nos logs de app referenciando classes ou funções indefinidas.

Como confirmar:

  1. Inspecione a lista de módulos PHP do contêiner app:

    docker compose exec app php -m | sort

    Procure pelos nomes das extensões que você precisa (para extensões típicas do Laravel: pdo_mysql, mbstring, bcmath, openssl, tokenizer, xml, ctype, json, gd).

  2. Verifique o texto de erro do composer, que geralmente indica as extensões necessárias:

    docker compose exec app composer install

Correções:

  • Adicione as extensões que estão faltando em seu Dockerfile e reconstrua a imagem. Exemplo (para pdo_mysql e gd):

    RUN apt-get update && apt-get install -y libpng-dev libxml2-dev \
        && docker-php-ext-install pdo_mysql mbstring xml gd

    Então reconstrua e reinicie:

    docker compose build app
    
    docker compose up -d app
    
    docker compose logs app --tail=100
  • Se uma extensão requer bibliotecas do sistema (por exemplo, gd precisa de libpng-dev), adicione as linhas correspondentes de apt-get install antes de docker-php-ext-install.

  • Se você precisar de uma extensão PHP que não seja nativa, considere instalá-la via PECL e habilitá-la no php.ini.

Falhas na conexão com o banco de dados (Laravel não consegue conectar ao MySQL)

Sintomas:

  • O Laravel exibe erros de conexão com o banco de dados (SQLSTATE[HY000] [2002] Conexão recusada ou Acesso negado para o usuário).
  • As migrações falham ou o aplicativo mostra uma página em branco para páginas baseadas em dados.

Como confirmar:

  1. Verifique os logs do contêiner do banco de dados para erros de inicialização:

    docker compose logs db --tail=200
  2. Verifique se o contêiner do DB está ativo:

    docker compose ps db
  3. Confirme se as credenciais e o host são consistentes entre .env e docker-compose.yml. No .env do Laravel, DB_HOST deve ser o nome do serviço do compose (db), não 127.0.0.1 ou localhost:

    DB_HOST=db
    DB_PORT=3306
    DB_DATABASE=travellist
    DB_USERNAME=travellist_user
    DB_PASSWORD=password
  4. Teste de dentro do contêiner db:

    docker compose exec db mysql -u"${DB_USERNAME}" -p"${DB_PASSWORD}" -e "SHOW DATABASES;"

    Ou, teste a partir do contêiner app para garantir a conectividade de rede:

    docker compose exec app sh -c 'which mysql >/dev/null 2>&1 || echo "no mysql client"; mysql -h db -u"${DB_USERNAME}" -p"${DB_PASSWORD}" -e "SELECT VERSION();"'

Causas comuns e soluções:

  • Host incorreto em .env: atualize DB_HOST=db, recrie os containers ou recarregue os ambientes:

    docker compose down
    
    docker compose up -d
    
    docker compose exec app php artisan migrate
  • Banco de dados não está pronto quando o app inicia: use um mecanismo de tentativas no seu app ou adicione um healthcheck e comece a sincronizar. Você também pode esperar manualmente:

    docker compose exec db mysqladmin -u"${DB_USERNAME}" -p"${DB_PASSWORD}" ping
    
    # expected: mysqld is alive
  • Falha na inicialização do SQL: verifique os arquivos em ./docker-compose/mysql, um erro de sintaxe ou de permissão pode interromper a inicialização do banco de dados. Verifique os logs do banco para erros de importação.

  • Privilégios/credenciais incorretas: confirme se o banco de dados e o usuário existem. Recrie o BD ou execute SQL para conceder privilégios.

  • Uso de porta no nível do host: se você tentou curl telnet://localhost:3306 e o serviço db não expõe uma porta no host, essa conexão será recusada por design. Teste de dentro dos contêineres, ou exponha a porta no docker-compose.yml (ports: - "3306:3306") para depuração apenas local.

Problemas de permissão e de sistema de arquivos (armazenamento, cache, arquivos de log)

Sintomas:

  • O Laravel lança exceções relacionadas à escrita de cache ou logs: O fluxo ou arquivo "/var/www/storage/logs/laravel.log" não pôde ser aberto.
  • Arquivos criados por processos de contêiner são de propriedade do root e não podem ser modificados pelo usuário do seu host.

Como confirmar:

  1. Verifique as permissões do arquivo no host:

    ls -l
    
    ls -l storage bootstrap/cache
    
    docker compose exec app id -u   # user id inside container
  2. Inspecione os logs do contêiner em busca de erros de permissão.

Correções:

  • Certifique-se de que o Dockerfile define o usuário e o UID que correspondem ao seu usuário do host (seu Dockerfile já cria um usuário com ARG uid). Confirme se o uid nos argumentos de build do docker-compose.yml corresponde ao UID do seu host (geralmente 1000).

  • Corrigir a propriedade a partir do host ou dentro do contêiner:

    docker compose exec app chown -R sammy:sammy /var/www/storage /var/www/bootstrap/cache

    ou do anfitrião:

    sudo chown -R $(id -u):$(id -g) storage bootstrap/cache
  • Para desenvolvimento, montagens bind podem causar incompatibilidades de permissão; prefira executar comandos no contêiner para ajustar a propriedade após iniciar os contêineres.

Erros relacionados ao compositor ou fornecedor

Sintomas:

  • composer install falha dentro de app ou a ausência da pasta vendor causa erro de classe não encontrada.
  • composer relata esgotamento de memória no contêiner.

Como confirmar:

  • Execute o composer dentro do contêiner app e capture a saída:

    docker compose exec app composer install --no-interaction --prefer-dist

Correções:

  • Se os limites de memória do composer forem atingidos, permita mais memória ou defina a variável de ambiente do composer:

    docker compose exec app php -d memory_limit=-1 /usr/bin/composer install
  • Certifique-se de que o executável do Composer está presente (seu Dockerfile copia o Composer). Caso não esteja, reconstrua a imagem com a camada do Composer.

  • Se o vendor estiver ausente devido a um overlay de bind mount, certifique-se de que seu bind mount não está escondendo a pasta vendor criada na imagem. A melhor prática é executar composer install após os containers estarem rodando, para que o vendor seja criado na pasta montada, ou adicionar o vendor ao container via build da imagem para produção.

O aplicativo retorna página em branco ou erros 500

Sintomas:

  • O navegador exibe uma resposta em branco ou HTTP 500.
  • Os logs do Nginx mostram 200, mas a página está vazia; os logs do app mostram exceções fatais.

Como confirmar:

  • Inspecionar logs do aplicativo:

    docker compose exec app tail -n 200 storage/logs/laravel.log
    
    docker compose logs app --tail=200
  • Verifique o ambiente: APP_DEBUG=true no .env fará o Laravel mostrar erros detalhados no desenvolvimento (não ative em produção).

Correções:

  • Corrija a exceção subjacente mostrada em laravel.log.

  • Certifique-se de que .env existe e que APP_KEY está definido:

    docker compose exec app php artisan key:generate
  • Executar migrações, limpar cache:

    docker compose exec app php artisan migrate --force
    
    docker compose exec app php artisan config:cache
    
    docker compose exec app php artisan route:cache
    
    docker compose exec app php artisan view:cache

Falhas na construção do container (erros durante docker compose build)

Sintomas:

  • O docker compose build apresenta erros durante o apt ou nas etapas de build.

Como confirmar:

  • Reexecutar build com logs:

    docker compose build app

Correções:

  • Leia a saída da etapa com falha. Problemas típicos:

    • Rede/DNS no ambiente de build: tente novamente ou garanta que o Docker possa acessar a internet.
    • Pacotes ausentes ou fonte do apt incorreta: adicione apt-get update antes das instalações e limpe as listas do apt depois.
    • Permissões em arquivos copiados para a imagem: garanta os caminhos corretos do COPY.

Exemplo de trecho de Dockerfile para instalações robustas:

RUN apt-get update && apt-get install -y --no-install-recommends \
    libpng-dev libxml2-dev zip unzip \
  && docker-php-ext-install pdo_mysql mbstring xml gd \
  && apt-get clean && rm -rf /var/lib/apt/lists/*

Problemas de rede ou DNS entre contêineres

Sintomas:

  • Um contêiner não consegue acessar outro pelo nome do serviço (por exemplo, app não consegue acessar db usando db:3306).
  • ping pelo nome do serviço falha.

Como confirmar:

docker compose exec app ping -c 3 db

docker compose exec app getent hosts db

docker network inspect travellist

Correções:

  • Confirme se os serviços compartilham a mesma rede em docker-compose.yml (ambos devem referenciar travellist).

  • Se você alterou os nomes dos serviços ou das redes, atualize DB_HOST e os upstreams do Nginx de acordo.

  • Recriar rede (isso destrói contêineres efêmeros, então tenha cuidado):

    docker compose down
    
    docker network prune   # optional and destructive
    
    docker compose up -d

Comandos de diagnóstico úteis

Execute estes para inspeção direcionada:

  • Mostrar contêineres em execução e portas:

    docker compose ps
  • Exibir logs de um serviço:

    docker compose logs -f --tail=200 nginx
  • Execute um shell dentro de um contêiner:

    docker compose exec app sh
    
    docker compose exec db bash
  • Inspecionar variáveis de ambiente do contêiner:

    docker compose exec app env | sort
    
    docker compose exec db env | grep MYSQL
  • Verificar lista de processos do contêiner:

    docker compose exec app ps aux
    
    docker compose exec app ss -ltnp
  • Verificar a saúde do MySQL:

    docker compose exec db mysqladmin -u"${DB_USERNAME}" -p"${DB_PASSWORD}" ping

Quando expor portas do host para depuração

Por padrão, o serviço db nesta configuração não está vinculado às portas do host. Exponha portas apenas para depuração local. Para expor temporariamente o MySQL no host, adicione ao docker-compose.yml em db:

ports:
  - "3306:3306"

Então reinicie:

docker compose up -d db

Após a depuração, remova o mapeamento ports para evitar expor o banco de dados à rede.

Perguntas Frequentes

1. Eu preciso ter o Composer instalado no host?

Não. Você pode executar o Composer inteiramente dentro do contêiner do aplicativo, que é a abordagem recomendada para este fluxo de trabalho do Compose. O Dockerfile no tutorial inclui o Composer, então execute comandos como docker compose exec app composer install ou docker compose run --rm app composer require vendor/package. Executar o Composer no contêiner garante que a versão correta do PHP e as extensões sejam usadas, e evita gerar arquivos de vendor com propriedades incompatíveis no host.

2. Posso executar migrações do Laravel de dentro do contêiner?

Sim. Execute as migrações a partir do contêiner do aplicativo para que elas usem o mesmo tempo de execução e rede que sua aplicação: docker compose exec app php artisan migrate. Para tarefas únicas ou de CI, você pode usar docker compose run --rm app php artisan migrate --force. Antes de executar as migrações, certifique-se de que o contêiner do banco de dados está saudável e aceitando conexões, por exemplo, com docker compose exec db mysqladmin -u$ {DB_USERNAME} -p$ {DB_PASSWORD} ping ou tentando novamente até que haja resposta.

3. Como faço para instalar extensões PHP adicionais?

Adicione os pacotes de sistema necessários e os comandos de extensão ao Dockerfile, e então reconstrua a imagem. Por exemplo, instale dependências e construa extensões comuns com algo como:

RUN apt-get update && apt-get install -y libpng-dev libjpeg-dev \
  && docker-php-ext-configure gd --with-jpeg \
  && docker-php-ext-install pdo_mysql mbstring gd

Se você precisar de uma extensão PECL, use pecl install e docker-php-ext-enable. Depois de editar o Dockerfile, execute docker compose build app e então docker compose up -d app para aplicar as alterações.

4. Como faço para expor o contêiner Nginx para HTTPS?

Adicione um bloco de servidor HTTPS à sua configuração do Nginx, monte seu certificado e chave no contêiner e publique a porta 443 no docker-compose. Aqui está um exemplo com alterações mínimas em docker-compose.yml:

services:
  nginx:
    ports:
      - "8000:80"
      - "443:443"
    volumes:
      - ./docker-compose/nginx:/etc/nginx/conf.d
      - ./certs:/etc/nginx/certs:ro

Em seguida, adicione um bloco de servidor SSL que faça referência a /etc/nginx/certs/fullchain.pem e /etc/nginx/certs/privkey.pem. Para produção, use um proxy reverso ou um gerenciador de certificados, como Traefik ou um balanceador de carga externo, para obter e renovar certificados de forma segura, em vez de armazenar chaves privadas na pilha do aplicativo.

5. Como faço para reconstruir o Laravel após alterações no código?

Depende do que mudou. Se você estiver usando bind mounts para o diretório do projeto, as alterações em PHP, templates, rotas e controladores são refletidas imediatamente; execute os comandos de limpeza de cache do Laravel, se necessário, por exemplo:

docker compose exec app php artisan config:clear

docker compose exec app php artisan route:clear

docker compose exec app php artisan view:clear

Se você alterou dependências do Composer ou o Dockerfile (novas extensões ou pacotes do sistema), reinstale ou reconstrua: docker compose exec app composer install para alterações de dependências, e docker compose build app && docker compose up -d para alterações no Dockerfile. Reinicie o serviço afetado quando você alterar a configuração do PHP-FPM ou Nginx: docker compose restart app ou docker compose restart nginx.

Conclusão

Neste guia, configuramos um ambiente Docker com três containers usando o Docker Compose para definir nossa infraestrutura em um arquivo YAML.

A partir deste ponto, você pode trabalhar em sua aplicação Laravel sem precisar instalar e configurar um servidor web local para desenvolvimento e testes. Além disso, você estará trabalhando com um ambiente descartável que pode ser facilmente replicado e distribuído, o que pode ser útil durante o desenvolvimento da sua aplicação e também na transição para um ambiente de produção.

Para mais tutoriais de Laravel, confira os seguintes artigos:

  • Hospede seu app Laravel no Linux-Console.net
  • Como Instalar e Configurar o Laravel com Nginx no Ubuntu (Stack LEMP)
  • Como Criar Recursos de API Eloquent no Laravel para Converter Models em JSON
  • Como Iniciar uma Nova Aplicação Laravel com Docker Compose

Obrigado por aprender com a comunidade Linux-Console.net.