Ubuntu Docker Compose
Um app de verdade raramente é um container só: tem o site, o programa e o banco de dados. O Docker Compose descreve todos eles num único arquivo, compose.yaml, e sobe tudo com docker compose up. Em vez de três docker run enormes, um arquivo legível que você guarda junto com o projeto.
- Como ler e escrever um
compose.yaml, linha por linha. - Os comandos
up,ps,logs,exec,down. - Como os containers se encontram pelo nome do serviço.
- Como tirar senhas do arquivo com
.env. - O que fazer quando um serviço não sobe.
Pré-requisito: Docker. As saídas são da máquina fictícia notebook e ilustrativas (IDs e versões variam).
Conceito: de comandos para um arquivo
Sem o Compose, subir um site com banco exige lembrar de vários comandos longos, na ordem certa:
$ docker network create loja $ docker run -d --name db --network loja -e POSTGRES_PASSWORD=segredo -v dados:/var/lib/postgresql/data postgres:17 $ docker run -d --name web --network loja -p 8080:80 -v ./site:/usr/share/nginx/html:ro nginx:1.29
Com o Compose, a mesma coisa vira um arquivo que descreve o estado desejado. Você diz o que quer, e o Compose descobre o que fazer.
Instalar
No Ubuntu, o Compose é um plugin do comando docker. Repare: é docker compose (com espaço). O antigo docker-compose (com hífen) é a versão 1, que não é mais mantida.
$ sudo apt install docker-compose-v2 $ docker compose version Docker Compose version 2.37.1+ds1-0ubuntu1 « número ilustrativo
Se você instalou o Docker pelo repositório oficial (docker-ce), o equivalente é o pacote docker-compose-plugin.
O arquivo compose.yaml
Vamos montar uma loja simples com três serviços: web (nginx, a porta de entrada), app (o programa) e db (PostgreSQL). Estrutura do projeto:
$ tree -a
.
├── .env
├── app
│ └── Dockerfile
├── compose.yaml
└── nginx
└── default.conf
2 directories, 4 files
# Cada item de "services" vira um container
services:
web: # nome do serviço (também é o nome na rede)
image: nginx:1.29 # imagem pronta, com versão fixa
ports:
- "8080:80" # porta do Ubuntu : porta do container
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- app # sobe o "app" antes do "web"
restart: unless-stopped # volta sozinho depois de um reboot
app:
build: ./app # constrói a imagem a partir de app/Dockerfile
environment:
DATABASE_URL: postgres://loja:${DB_SENHA}@db:5432/loja
depends_on:
db:
condition: service_healthy # espera o banco estar pronto de verdade
restart: unless-stopped
db:
image: postgres:17
environment:
POSTGRES_USER: loja
POSTGRES_PASSWORD: ${DB_SENHA} # vem do arquivo .env
POSTGRES_DB: loja
volumes:
- dados-db:/var/lib/postgresql/data # os dados sobrevivem ao "down"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U loja"]
interval: 5s
retries: 10
restart: unless-stopped
# Volumes nomeados usados acima
volumes:
dados-db:
| Chave | O que faz | Equivale no docker run |
|---|---|---|
image | Imagem a usar | o último argumento |
build | Constrói a imagem de um Dockerfile | docker build |
ports | Publica portas | -p |
volumes | Liga pastas ou volumes | -v |
environment | Variáveis de ambiente | -e |
depends_on | Ordem de subida | (não existe) |
healthcheck | Como saber se o serviço está saudável | --health-cmd |
restart | Política de reinício | --restart |
A hierarquia é feita por indentação com espaços (nunca tab). Um espaço a mais ou a menos muda o sentido ou quebra o arquivo. Use sempre 2 espaços por nível.
Os containers se encontram pelo nome
O Compose cria uma rede só para o projeto. Dentro dela, cada serviço é encontrado pelo nome. Por isso o app fala com o banco em db:5432, e não num IP. E por isso o banco não precisa de ports: ele só é acessível de dentro da rede, o que é mais seguro.
server {
listen 80;
location / {
proxy_pass http://app:3000; # "app" é o nome do serviço
}
}
Senhas fora do arquivo: .env
O ${DB_SENHA} é lido de um arquivo .env, na mesma pasta. Assim você pode publicar o compose.yaml no Git sem expor a senha.
DB_SENHA=troque-esta-senha
$ chmod 600 .env $ echo ".env" >> .gitignore « nunca envie o .env para o Git
Subir tudo: docker compose up
- docker compose
- O plugin Compose. Ele procura o
compose.yamlna pasta atual. - up
- Cria (ou atualiza) e inicia todos os serviços.
- -d
- Em segundo plano, devolvendo o terminal.
$ docker compose up -d [+] Building 12.4s (9/9) FINISHED « constrói a imagem do "app" [+] Running 5/5 ✔ Network loja_default Created « a rede do projeto ✔ Volume "loja_dados-db" Created ✔ Container loja-db-1 Healthy « esperou o healthcheck ✔ Container loja-app-1 Started ✔ Container loja-web-1 Started
Os nomes seguem o padrão projeto-serviço-número. O nome do projeto é o nome da pasta (loja).
Ver o estado: ps
$ docker compose ps NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS loja-app-1 loja-app "docker-entrypoint.s…" app 2 minutes ago Up 2 minutes 3000/tcp loja-db-1 postgres:17 "docker-entrypoint.s…" db 2 minutes ago Up 2 minutes (healthy) 5432/tcp loja-web-1 nginx:1.29 "/docker-entrypoint.…" web 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp
Só o web tem 0.0.0.0:8080->: é o único publicado no Ubuntu. 3000/tcp e 5432/tcp existem só dentro da rede do projeto.
Logs e comandos dentro dos serviços
$ docker compose logs --tail 3 app
app-1 | Servidor ouvindo na porta 3000
app-1 | Conectado ao banco "loja"
app-1 | GET / 200 12ms
$ docker compose exec db psql -U loja -c "select version();"
version
-----------------------------------------------------------
PostgreSQL 17.6 (Debian 17.6-1.pgdg13+1) on x86_64-pc-linux-gnu, …
(1 row)
Repare que no Compose você usa o nome do serviço (db), e não o do container (loja-db-1). docker compose logs -f acompanha os logs de todos ao vivo; saia com Ctrl+C.
Mudou o arquivo? Rode up de novo
$ docker compose up -d [+] Running 3/3 ✔ Container loja-db-1 Running « nada mudou: não mexe ✔ Container loja-app-1 Running ✔ Container loja-web-1 Recreated « só o que você alterou é recriado
Mudou o código do app? Use docker compose up -d --build para reconstruir a imagem.
Parar e apagar: stop x down
| Comando | Containers | Rede | Volumes (dados) |
|---|---|---|---|
docker compose stop | Parados (continuam existindo) | Fica | Ficam |
docker compose down | Apagados | Apagada | Ficam |
docker compose down -v | Apagados | Apagada | Apagados |
$ docker compose down [+] Running 4/4 ✔ Container loja-web-1 Removed ✔ Container loja-app-1 Removed ✔ Container loja-db-1 Removed ✔ Network loja_default Removed « o volume loja_dados-db continua
down -v apaga o banco de dados
O -v remove os volumes nomeados, e com eles todos os dados do PostgreSQL. Faça backup antes (veja Bancos de Dados).
Formas de rodar o mesmo projeto
| Forma | Comando | Melhor quando… |
|---|---|---|
| Segundo plano | docker compose up -d | Servidor ou uso normal. |
| Primeiro plano | docker compose up | Desenvolvimento: os logs aparecem na tela; Ctrl+C para tudo. |
| Um serviço só | docker compose up -d db | Você só precisa do banco agora. |
| Outro arquivo | docker compose -f producao.yaml up -d | Configurações diferentes para teste e produção. |
| Comando avulso | docker compose run --rm app npm test | Rodar uma tarefa uma vez, num container descartável. |
Quando não funciona
$ docker compose up -d no configuration file provided: not found
O que aconteceu: você não está na pasta do projeto. O Compose procura compose.yaml (ou docker-compose.yml) na pasta atual.
Como resolver: cd ~/loja, ou aponte o arquivo com -f.
$ docker compose up -d yaml: line 9: did not find expected key
O que aconteceu: indentação errada (ou um tab) perto da linha indicada.
Como confirmar: docker compose config valida e mostra o arquivo como o Compose entendeu.
$ docker compose up -d WARN[0000] The "DB_SENHA" variable is not set. Defaulting to a blank string.
O que aconteceu: o .env não está na pasta, ou a variável tem outro nome. O banco pode até subir, mas sem senha (ou recusar subir).
$ docker compose ps NAME … STATUS loja-app-1 … Restarting (1) 4 seconds ago $ docker compose logs --tail 5 app app-1 | Error: connect ECONNREFUSED 172.18.0.2:5432
O que aconteceu: o app tentou falar com o banco antes de ele estar pronto, ou com a senha errada. depends_on sozinho só espera o container iniciar; use condition: service_healthy com um healthcheck, como no exemplo.
Referência
| Comando | O que faz |
|---|---|
docker compose up -d | Cria/atualiza e sobe tudo |
docker compose up -d --build | Reconstrói as imagens antes |
docker compose ps | Estado dos serviços |
docker compose logs [-f] [SERVIÇO] | Logs |
docker compose exec SERVIÇO CMD | Roda comando num serviço rodando |
docker compose run --rm SERVIÇO CMD | Roda num container novo e descartável |
docker compose restart [SERVIÇO] | Reinicia |
docker compose stop | Para sem apagar |
docker compose down [-v] | Apaga containers e rede (-v: volumes também) |
docker compose pull | Baixa versões novas das imagens |
docker compose config | Valida e mostra o arquivo final |
Exercícios
1. Subir em segundo plano
2. Logs de um serviço
Complete para acompanhar ao vivo os logs só do serviço app:
3. Endereço do banco
No compose.yaml, o serviço do PostgreSQL se chama db. Complete a URL que o app usa:
4. Qual comando apaga os containers mas mantém os dados do banco?
5. Por que o serviço db do exemplo não tem ports?
Resumo
compose.yamldescreve todos os serviços;docker compose up -dfaz o resto.- Serviços se encontram pelo nome (
db,app) numa rede própria do projeto. - Publique só o necessário em
ports; guarde dados em volumes; senhas no.env. downapaga containers e mantém volumes;down -vapaga os dados também.docker compose configelogssão os primeiros passos quando algo falha.