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.

Você vai aprender
  • 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:
ChaveO que fazEquivale no docker run
imageImagem a usaro último argumento
buildConstrói a imagem de um Dockerfiledocker build
portsPublica portas-p
volumesLiga pastas ou volumes-v
environmentVariáveis de ambiente-e
depends_onOrdem de subida(não existe)
healthcheckComo saber se o serviço está saudável--health-cmd
restartPolítica de reinício--restart
Atenção: YAML é sensível a espaços

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.yaml na 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

ComandoContainersRedeVolumes (dados)
docker compose stopParados (continuam existindo)FicaFicam
docker compose downApagadosApagadaFicam
docker compose down -vApagadosApagadaApagados
$ 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
Atenção: 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

FormaComandoMelhor quando…
Segundo planodocker compose up -dServidor ou uso normal.
Primeiro planodocker compose upDesenvolvimento: os logs aparecem na tela; Ctrl+C para tudo.
Um serviço sódocker compose up -d dbVocê só precisa do banco agora.
Outro arquivodocker compose -f producao.yaml up -dConfigurações diferentes para teste e produção.
Comando avulsodocker compose run --rm app npm testRodar uma tarefa uma vez, num container descartável.

Quando não funciona

"no configuration file provided: not found"
$ 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.

Erro de YAML
$ 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.

Variável vazia
$ 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).

Um serviço fica reiniciando
$ 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

ComandoO que faz
docker compose up -dCria/atualiza e sobe tudo
docker compose up -d --buildReconstrói as imagens antes
docker compose psEstado dos serviços
docker compose logs [-f] [SERVIÇO]Logs
docker compose exec SERVIÇO CMDRoda comando num serviço rodando
docker compose run --rm SERVIÇO CMDRoda num container novo e descartável
docker compose restart [SERVIÇO]Reinicia
docker compose stopPara sem apagar
docker compose down [-v]Apaga containers e rede (-v: volumes também)
docker compose pullBaixa versões novas das imagens
docker compose configValida e mostra o arquivo final

Exercícios

1. Subir em segundo plano

docker compose

2. Logs de um serviço

Complete para acompanhar ao vivo os logs só do serviço app:

docker compose logs app

3. Endereço do banco

No compose.yaml, o serviço do PostgreSQL se chama db. Complete a URL que o app usa:

postgres://loja:senha@:5432/loja

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.yaml descreve todos os serviços; docker compose up -d faz 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.
  • down apaga containers e mantém volumes; down -v apaga os dados também.
  • docker compose config e logs são os primeiros passos quando algo falha.