12.3 Docker Compose
Resumo
Docker Compose — ferramenta para gerenciar apps multi-container via arquivo YAML.
Seções principais: services (containers), networks (redes), volumes (dados).
docker-compose up -dinicia todos os services em background.No Laravel: nginx + php + mysql + redis + queue worker num docker-compose.yml só.
docker-compose execpara rodar comandos nos containers.
Conteúdo
- O que é
- docker-compose.yml básico
- docker-compose.yml do Laravel
- Configuração dos services
- Exemplos práticos
- Comandos úteis
- Arquivos override
- Na entrevista
- Exercícios práticos
O que é
O que é: Docker Compose gerencia apps multi-container. Você descreve os services num arquivo YAML.
Para que serve:
- Iniciar vários containers com um comando
- Ligar os containers entre si
- Gerenciar networks e volumes
docker-compose.yml básico
Exemplo simples:
version: '3.8'
services:
app:
build: .
ports:
- "8000:8000"
volumes:
- .:/var/www/html
depends_on:
- mysql
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:Comandos:
# Iniciar todos os services
docker-compose up
# Iniciar em background
docker-compose up -d
# Rebuild das images
docker-compose up --build
# Parar
docker-compose down
# Parar e remover volumes
docker-compose down -v
# Ver os logs
docker-compose logs
# Logs de um service específico
docker-compose logs app
# Rodar um comando
docker-compose exec app php artisan migratedocker-compose.yml do Laravel
Stack completo:
version: '3.8'
services:
# Nginx
nginx:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
networks:
- laravel
# PHP-FPM
php:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./:/var/www/html
environment:
- DB_HOST=mysql
- DB_DATABASE=laravel
- DB_USERNAME=laravel
- DB_PASSWORD=secret
- REDIS_HOST=redis
depends_on:
- mysql
- redis
networks:
- laravel
# MySQL
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel
MYSQL_USER: laravel
MYSQL_PASSWORD: secret
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
networks:
- laravel
# Redis
redis:
image: redis:alpine
ports:
- "6379:6379"
networks:
- laravel
# Queue Worker
queue:
build:
context: .
dockerfile: Dockerfile
command: php artisan queue:work --tries=3
volumes:
- ./:/var/www/html
depends_on:
- mysql
- redis
networks:
- laravel
networks:
laravel:
driver: bridge
volumes:
mysql-data:Config do Nginx (docker/nginx/default.conf):
server {
listen 80;
index index.php index.html;
root /var/www/html/public;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}Configuração dos services
build:
services:
app:
# Build simples
build: .
# Com parâmetros
build:
context: .
dockerfile: Dockerfile.prod
args:
PHP_VERSION: 8.2
# Ou usar uma image pronta
image: php:8.2-fpmenvironment:
services:
app:
# Variáveis de ambiente
environment:
- APP_ENV=local
- APP_DEBUG=true
- DB_HOST=mysql
# Ou a partir de um arquivo
env_file:
- .envvolumes:
services:
app:
volumes:
# Bind mount (pasta local)
- ./:/var/www/html
# Named volume
- app-storage:/var/www/html/storage
# Read-only
- ./config:/config:ro
volumes:
app-storage:ports:
services:
app:
ports:
# host:container
- "8080:80"
# Só o container (porta do host aleatória)
- "80"
# IP:host:container
- "127.0.0.1:8080:80"depends_on:
services:
app:
depends_on:
- mysql
- redis
# Inicia mysql e redis antes do app
# MAS não espera o MySQL ficar pronto!networks:
services:
app:
networks:
- frontend
- backend
networks:
frontend:
backend:Exemplos práticos
Setup de development:
version: '3.8'
services:
php:
build: .
volumes:
- ./:/var/www/html
environment:
- APP_ENV=local
- APP_DEBUG=true
ports:
- "8000:8000"
command: php artisan serve --host=0.0.0.0
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
mailhog:
image: mailhog/mailhog
ports:
- "1025:1025"
- "8025:8025"
volumes:
mysql-data:Comandos:
# Iniciar
docker-compose up -d
# Instalar as dependências
docker-compose exec php composer install
# Migrations
docker-compose exec php php artisan migrate
# Criar o controller
docker-compose exec php php artisan make:controller UserController
# Testes
docker-compose exec php php artisan test
# Ver os emails
# http://localhost:8025Setup de production:
version: '3.8'
services:
nginx:
image: nginx:alpine
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./public:/var/www/html/public:ro
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./docker/ssl:/etc/nginx/ssl:ro
depends_on:
- php
php:
build:
context: .
dockerfile: Dockerfile.prod
restart: always
volumes:
- ./storage:/var/www/html/storage
environment:
- APP_ENV=production
- APP_DEBUG=false
env_file:
- .env.production
mysql:
image: mysql:8
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: ${DB_DATABASE}
volumes:
- mysql-data:/var/lib/mysql
redis:
image: redis:alpine
restart: always
queue:
build:
context: .
dockerfile: Dockerfile.prod
restart: always
command: php artisan queue:work --tries=3 --timeout=90
depends_on:
- mysql
- redis
volumes:
mysql-data:Comandos úteis
Gerenciar:
# Recriar os containers
docker-compose up -d --force-recreate
# Parar sem remover
docker-compose stop
# Iniciar os que estão parados
docker-compose start
# Reiniciar
docker-compose restart
# Ver o status
docker-compose ps
# Rodar um comando pontual
docker-compose run --rm php composer installLogs:
# Todos os logs
docker-compose logs
# Últimas 100 linhas
docker-compose logs --tail=100
# Em tempo real
docker-compose logs -f
# Um service específico
docker-compose logs -f phpEscalar:
# Iniciar 3 instâncias do queue worker
docker-compose up -d --scale queue=3Arquivos override
docker-compose.override.yml (aplica sozinho):
# docker-compose.yml
version: '3.8'
services:
php:
image: php:8.2-fpm
# docker-compose.override.yml (dev local)
version: '3.8'
services:
php:
volumes:
- ./:/var/www/html
environment:
- XDEBUG_MODE=debugUsar um arquivo específico:
# Arquivo de production
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -dNa entrevista
Resposta estruturada:
O que é:
- Docker Compose gerencia apps multi-container
- Você descreve todos os services num YAML
- Inicia o stack inteiro com um comando
Seções principais:
- services — containers (app, mysql, redis)
- networks — redes para os containers se falarem
- volumes — dados persistentes
Comandos:
docker-compose up -d— iniciar em backgrounddocker-compose down— parar e removerdocker-compose exec— rodar comando no containerdocker-compose logs— ver os logs--scale— escalar services
Configuração dos services:
- depends_on — dependências (ordem de start)
- environment — variáveis de ambiente
- volumes — montar pastas
- ports — mapear portas (host:container)
- restart: always — restart automático
Arquivos override:
docker-compose.override.yml— aplica sozinho- Configs diferentes para dev/prod
-fpara apontar o arquivo
Laravel:
- Stack: nginx + php + mysql + redis + queue
- Rede compartilhada, conversam pelo nome (DB_HOST=mysql)
- Named volumes para os dados do MySQL
Exercícios práticos
Exercício 1: Monte o stack Laravel completo com docker-compose
Enunciado: Crie um docker-compose.yml para Laravel com nginx, php, mysql, redis e queue worker. Inclua Mailhog para testar email.
Solução
# docker-compose.yml
version: '3.8'
services:
# Nginx
nginx:
image: nginx:alpine
container_name: laravel-nginx
ports:
- "8080:80"
volumes:
- ./:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
networks:
- laravel
# PHP-FPM
php:
build:
context: .
dockerfile: Dockerfile
container_name: laravel-php
volumes:
- ./:/var/www/html
environment:
- APP_ENV=local
- APP_DEBUG=true
- DB_HOST=mysql
- DB_DATABASE=laravel
- DB_USERNAME=laravel
- DB_PASSWORD=secret
- REDIS_HOST=redis
- MAIL_MAILER=smtp
- MAIL_HOST=mailhog
- MAIL_PORT=1025
depends_on:
- mysql
- redis
networks:
- laravel
# MySQL
mysql:
image: mysql:8
container_name: laravel-mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel
MYSQL_USER: laravel
MYSQL_PASSWORD: secret
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
networks:
- laravel
# Redis
redis:
image: redis:alpine
container_name: laravel-redis
ports:
- "6379:6379"
networks:
- laravel
# Queue Worker
queue:
build:
context: .
dockerfile: Dockerfile
container_name: laravel-queue
command: php artisan queue:work --tries=3 --timeout=90
volumes:
- ./:/var/www/html
environment:
- DB_HOST=mysql
- REDIS_HOST=redis
depends_on:
- mysql
- redis
networks:
- laravel
restart: unless-stopped
# Mailhog
mailhog:
image: mailhog/mailhog
container_name: laravel-mailhog
ports:
- "1025:1025" # SMTP
- "8025:8025" # Web UI
networks:
- laravel
networks:
laravel:
driver: bridge
volumes:
mysql-data:# docker/nginx/default.conf
server {
listen 80;
index index.php index.html;
root /var/www/html/public;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}# Dockerfile
FROM php:8.2-fpm-alpine
# Instalar dependências
RUN apk add --no-cache \
git \
curl \
zip \
unzip
# Instalar as PHP extensions
RUN docker-php-ext-install pdo pdo_mysql
# Instalar o Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Definir o diretório de trabalho
WORKDIR /var/www/html
# Permissões
RUN chown -R www-data:www-data /var/www/html
USER www-data# Iniciar o stack inteiro
docker-compose up -d
# Instalar as dependências
docker-compose exec php composer install
# Criar o .env
docker-compose exec php cp .env.example .env
# Gerar a key
docker-compose exec php php artisan key:generate
# Rodar as migrations
docker-compose exec php php artisan migrate
# Conferir a app
# http://localhost:8080
# Ver os emails
# http://localhost:8025
# Logs de todos os services
docker-compose logs -f
# Parar tudo
docker-compose down
# Parar e remover volumes
docker-compose down -vExercício 2: Crie arquivos override para dev e prod
Enunciado: Você tem um docker-compose.yml base. Crie docker-compose.override.yml para dev com Xdebug e docker-compose.prod.yml para production, sem services extras.
Solução
# docker-compose.yml (config base)
version: '3.8'
services:
nginx:
image: nginx:alpine
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
networks:
- laravel
php:
build:
context: .
dockerfile: Dockerfile
networks:
- laravel
depends_on:
- mysql
- redis
mysql:
image: mysql:8
environment:
MYSQL_DATABASE: laravel
networks:
- laravel
redis:
image: redis:alpine
networks:
- laravel
networks:
laravel:
volumes:
mysql-data:# docker-compose.override.yml (dev — aplica sozinho)
version: '3.8'
services:
nginx:
ports:
- "8080:80"
volumes:
- ./:/var/www/html # Bind mount para hot reload
php:
build:
dockerfile: Dockerfile.dev # Dev Dockerfile com Xdebug
volumes:
- ./:/var/www/html
environment:
- APP_ENV=local
- APP_DEBUG=true
- XDEBUG_MODE=debug
- XDEBUG_CONFIG=client_host=host.docker.internal
mysql:
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_USER: laravel
MYSQL_PASSWORD: secret
ports:
- "3306:3306" # Acesso de fora para cliente GUI
volumes:
- mysql-data:/var/lib/mysql
redis:
ports:
- "6379:6379"
# Só no dev
mailhog:
image: mailhog/mailhog
ports:
- "1025:1025"
- "8025:8025"
networks:
- laravel
# Só no dev
phpmyadmin:
image: phpmyadmin/phpmyadmin
environment:
PMA_HOST: mysql
PMA_USER: laravel
PMA_PASSWORD: secret
ports:
- "8081:80"
depends_on:
- mysql
networks:
- laravel# docker-compose.prod.yml (production)
version: '3.8'
services:
nginx:
restart: always
ports:
- "80:80"
- "443:443"
volumes:
# Read-only por segurança
- ./public:/var/www/html/public:ro
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./docker/ssl:/etc/nginx/ssl:ro
php:
build:
dockerfile: Dockerfile.prod # Prod Dockerfile sem Xdebug
restart: always
volumes:
# Só o storage (logs/cache)
- ./storage:/var/www/html/storage
environment:
- APP_ENV=production
- APP_DEBUG=false
env_file:
- .env.production
mysql:
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_USER: ${DB_USERNAME}
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
# Sem publicar porta (só acesso interno)
redis:
restart: always
# Sem publicar porta
# Queue worker para production
queue:
build:
context: .
dockerfile: Dockerfile.prod
restart: always
command: php artisan queue:work --tries=3 --timeout=90
volumes:
- ./storage:/var/www/html/storage
env_file:
- .env.production
depends_on:
- mysql
- redis
networks:
- laravel
# Scheduler para production
scheduler:
build:
context: .
dockerfile: Dockerfile.prod
restart: always
command: sh -c "while true; do php artisan schedule:run; sleep 60; done"
volumes:
- ./storage:/var/www/html/storage
env_file:
- .env.production
depends_on:
- mysql
networks:
- laravel# Development (usa o override sozinho)
docker-compose up -d
# Production
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# Build das images de production
docker-compose -f docker-compose.yml -f docker-compose.prod.yml build
# Ver a config final
docker-compose config
# Para production
docker-compose -f docker-compose.yml -f docker-compose.prod.yml configExercício 3: Escale os queue workers
Enunciado: Você tem uma app Laravel com filas. Configure o docker-compose para rodar vários queue workers e faça load balancing nos web servers.
Solução
# docker-compose.yml
version: '3.8'
services:
# Nginx Load Balancer
nginx-lb:
image: nginx:alpine
container_name: nginx-loadbalancer
ports:
- "80:80"
volumes:
- ./docker/nginx/lb.conf:/etc/nginx/nginx.conf:ro
depends_on:
- nginx-1
- nginx-2
networks:
- laravel
# Nginx Server 1
nginx-1:
image: nginx:alpine
volumes:
- ./:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
networks:
- laravel
# Nginx Server 2
nginx-2:
image: nginx:alpine
volumes:
- ./:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
networks:
- laravel
# PHP-FPM (compartilhado entre os nginx)
php:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./:/var/www/html
environment:
- DB_HOST=mysql
- REDIS_HOST=redis
depends_on:
- mysql
- redis
networks:
- laravel
# MySQL
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel
MYSQL_USER: laravel
MYSQL_PASSWORD: secret
volumes:
- mysql-data:/var/lib/mysql
networks:
- laravel
# Redis
redis:
image: redis:alpine
networks:
- laravel
# Queue Worker (escalável)
queue:
build:
context: .
dockerfile: Dockerfile
command: php artisan queue:work --tries=3 --timeout=90 --sleep=3
volumes:
- ./:/var/www/html
environment:
- DB_HOST=mysql
- REDIS_HOST=redis
depends_on:
- mysql
- redis
networks:
- laravel
restart: unless-stopped
# NÃO defina container_name — senão não escala
# Horizon (alternativa a vários queue workers)
horizon:
build:
context: .
dockerfile: Dockerfile
command: php artisan horizon
volumes:
- ./:/var/www/html
environment:
- DB_HOST=mysql
- REDIS_HOST=redis
depends_on:
- mysql
- redis
networks:
- laravel
restart: unless-stopped
networks:
laravel:
volumes:
mysql-data:# docker/nginx/lb.conf (Load Balancer)
events {
worker_connections 1024;
}
http {
upstream backend {
least_conn; # Balanceia pela menor carga
server nginx-1:80;
server nginx-2:80;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /health {
access_log off;
return 200 "healthy\n";
}
}
}# Iniciar com 3 queue workers
docker-compose up -d --scale queue=3
# Conferir se há 3 instâncias
docker-compose ps
# NAME COMMAND STATUS PORTS
# laravel_queue_1 "php artisan queue:w…" Up
# laravel_queue_2 "php artisan queue:w…" Up
# laravel_queue_3 "php artisan queue:w…" Up
# Ver os logs de todos os workers
docker-compose logs -f queue
# Escalar em tempo real (para 5 workers)
docker-compose up -d --scale queue=5 --no-recreate
# Reduzir para 2 workers
docker-compose up -d --scale queue=2 --no-recreate
# Ver o uso de recursos
docker stats
# Monitorar a queue no Redis CLI
docker-compose exec redis redis-cli
> LLEN queues:default # Quantidade de jobs na fila
# Horizon dashboard (se você usa Horizon no lugar de queue)
# http://localhost/horizon
# Testar o load balancing
for i in {1..10}; do
curl -s http://localhost | grep "Server:"
done
# Devem alternar nginx-1 e nginx-2# config/horizon.php (se você usa Horizon para auto-scale)
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'minProcesses' => 1,
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
'tries' => 3,
'timeout' => 90,
],
],
],# Alternativa: docker-compose com número fixo de workers
# docker-compose.scale.yml
version: '3.8'
services:
queue-1:
build: .
command: php artisan queue:work
networks:
- laravel
queue-2:
build: .
command: php artisan queue:work
networks:
- laravel
queue-3:
build: .
command: php artisan queue:work
networks:
- laravelParte do PHP/Laravel Interview Handbook | Feito com ❤️ pela equipe CodeMate