Olá! Bem-vindo ao nosso primeiro passo no mundo das aplicações multi-contêiner.
Introdução
Na lição anterior, você justificou com sucesso por que os volumes são a escolha profissional para persistir os dados de um banco de dados. Até agora, gerenciamos cada contêiner — seja uma API ou um banco de dados — como uma entidade isolada, usando comandos como docker run e docker volume create.
No entanto, aplicações do mundo real raramente são um único serviço. Elas são ecossistemas: uma API .NET que se comunica com um banco de dados PostgreSQL, que por sua vez pode interagir com um cache Redis. Gerenciar o ciclo de vida, a rede e a configuração de cada um desses contêineres individualmente com docker run torna-se rapidamente complexo e propenso a erros.
É aqui que entra o Docker Compose. Nesta lição, você aprenderá a usar essa ferramenta poderosa para definir e executar uma aplicação básica com múltiplos serviços a partir de um único arquivo de configuração. Este é o seu primeiro passo para orquestrar aplicações completas, uma habilidade fundamental para o desenvolvimento moderno.
Por que precisamos do Docker Compose?
Imagine uma aplicação .NET composta por uma API, um banco de dados e um frontend. Para executá-la com o Docker, você precisaria:
- Criar uma rede customizada para que os contêineres se comuniquem.
- Iniciar o contêiner do banco de dados, conectando-o à rede.
- Iniciar o contêiner da API, conectando-o à mesma rede e passando as credenciais do banco de dados como variáveis de ambiente.
- Iniciar o contêiner do frontend, também na mesma rede.
Cada passo envolveria um comando docker run longo e complexo. O Docker Compose simplifica tudo isso, permitindo que você descreva toda a sua aplicação em um arquivo declarativo.
O vídeo a seguir introduz esse problema e a solução que o Docker Compose oferece de uma forma bem didática.
What is Docker Compose? Multiple Containers & Docker Networking
Para entender o "porquê" por trás do Docker Compose, assista à introdução deste vídeo de Monis Yousuf, "What is Docker Compose?". Ele ilustra perfeitamente a complexidade de gerenciar múltiplos serviços e apresenta o Docker Compose como a solução.
Assista do início até 03:10. Preste atenção no exemplo da aplicação de três camadas (frontend, backend, database) e como a necessidade de orquestração surge naturalmente.
A Anatomia de um Arquivo docker-compose.yml
O coração do Docker Compose é um arquivo de configuração chamado docker-compose.yml. Ele usa a sintaxe YAML para definir os serviços que compõem sua aplicação. Vamos construir um exemplo para uma API .NET e um banco de dados PostgreSQL.
Um arquivo docker-compose.yml tem uma estrutura básica:
version: '3.8' # Especifica a versão da sintaxe do Compose
services:
# Aqui definiremos nossos serviços (contêineres)
A seção services é onde a mágica acontece. Cada chave sob services é um serviço individual.
Definindo o Serviço da API
Vamos definir nosso primeiro serviço: a API .NET. Dado que você tem um Dockerfile para sua API (como aprendeu no Módulo 2), a definição pode ser assim:
services:
webapi: # O nome do nosso serviço. Pode ser o que você quiser.
build:
context: . # O diretório onde o Dockerfile está localizado (o 'build context')
dockerfile: MyApi/Dockerfile # O nome do Dockerfile (se não for 'Dockerfile')
ports:
- "8080:80" # Mapeia a porta 8080 do host para a porta 80 do contêiner
build: Diz ao Compose para construir uma imagem a partir de umDockerfile. Você especifica ocontext(o diretório do projeto) e opcionalmente odockerfilese ele não estiver na raiz do contexto.ports: Funciona exatamente como o flag-pnodocker run. Ele expõe a porta do contêiner para a sua máquina host.
Definindo o Serviço do Banco de Dados
Agora, vamos adicionar um banco de dados PostgreSQL. Para isso, não precisamos construir uma imagem, pois podemos usar a imagem oficial do Docker Hub.
services:
webapi:
# ... (definição da API acima)
database: # O nome do nosso serviço de banco de dados
image: postgres:16-alpine # Usa a imagem 'postgres' versão 16, variante 'alpine'
environment:
- POSTGRES_USER=myuser
- POSTGRES_PASSWORD=mypassword
- POSTGRES_DB=mydatabase
ports:
- "5432:5432" # Mapeia a porta padrão do Postgres para o host
image: Diz ao Compose para puxar a imagem especificada de um registro (Docker Hub, por padrão).environment: Permite definir variáveis de ambiente dentro do contêiner, exatamente como o flag--envou-enodocker run. Isso é essencial para configurar o banco de dados.
O vídeo a seguir, focado em .NET, demonstra de forma prática como adicionar esses serviços (API, PostgreSQL e até um Redis) em um arquivo docker-compose.yml.
Docker Compose with .NET 8, PostgreSQL, and Redis (step by step)
Este vídeo de Milan Jovanović, "Docker Compose with .NET 8, PostgreSQL, and Redis", é extremamente relevante para você. Ele mostra passo a passo como construir um arquivo docker-compose.yml para um ambiente de desenvolvimento .NET.
Assista aos seguintes segmentos: a explicação inicial do serviço da API (02:16 a 03:04), como adicionar o serviço PostgreSQL (03:04 a 05:17) e como adicionar o serviço Redis (08:43 a 09:04). Observe como as chaves build, image, ports e environment são usadas.

Executando e Conectando os Serviços
Com o arquivo docker-compose.yml pronto no diretório raiz do seu projeto, você pode iniciar toda a aplicação com um único comando:
docker-compose up
ou, para a versão mais recente da CLI do Docker:
docker compose up
Este comando irá:
- Ler o arquivo
docker-compose.yml. - Criar uma rede padrão para os serviços se comunicarem.
- Construir a imagem da
webapi(se ainda não existir ou se você usar o flag--build). - Puxar a imagem do
postgres. - Iniciar os contêineres para ambos os serviços.
Como a API se conecta ao Banco de Dados?
Este é um ponto crucial e onde a mágica do Compose brilha, algo que seu background em redes vai apreciar.
Quando o docker compose up é executado, o Docker cria uma rede bridge customizada e anexa todos os contêineres definidos no arquivo a essa rede. Dentro dessa rede, o Docker fornece um servidor DNS interno. Esse DNS resolve o nome de um serviço (por exemplo, database) para o endereço IP interno do contêiner correspondente.
Isso significa que, de dentro do contêiner webapi, você pode se conectar ao banco de dados usando database como o nome do host.
Portanto, sua string de conexão no appsettings.json da sua API .NET não seria localhost, mas sim o nome do serviço:
{
"ConnectionStrings": {
"DefaultConnection": "Host=database;Port=5432;Database=mydatabase;Username=myuser;Password=mypassword;"
}
}
O vídeo a seguir explica esse mecanismo de rede e descoberta de serviços de forma excelente.
What is Docker Compose? Multiple Containers & Docker Networking
Para entender "como funciona" a comunicação entre contêineres, assista a este segmento do vídeo de Monis Yousuf. Ele explica o conceito da rede interna e da resolução de nomes por DNS que o Docker Compose configura automaticamente.
Assista da marca de 09:06 a 12:11. Foque na explicação sobre como o nome do serviço se torna o endereço (hostname) dentro da rede do Docker, eliminando a necessidade de expor todas as portas para o host.
Test your understanding!
Você está montando um docker-compose.yml para uma nova aplicação. Ela consiste em dois serviços:
- Um serviço de API .NET chamado
products-api. - Um serviço de cache Redis chamado
products-cache, que usa a imagemredis:lateste escuta na porta6379dentro do contêiner.
Sua API precisa se conectar ao Redis. Qual seria o valor do host na string de conexão do Redis dentro da configuração da sua API?
Show answer
O valor do host seria products-cache.
Graças à rede e ao DNS internos do Docker Compose, o contêiner products-api pode encontrar o contêiner products-cache simplesmente usando o nome do serviço como hostname. A string de conexão completa seria algo como products-cache:6379.
Exemplo Completo e Leitura Recomendada
Para consolidar tudo o que vimos, aqui está um exemplo completo de um arquivo docker-compose.yml para uma API .NET e um banco de dados SQL Server, extraído de um artigo muito útil.
version: '3.8'
services:
# Serviço da aplicação ASP.NET Core
todo-app:
build: . # Constrói a imagem a partir do Dockerfile no diretório atual
ports:
- "8090:80" # Mapeia a porta 8090 do host para a 80 do contêiner
environment:
- ASPNETCORE_ENVIRONMENT=Development
# A string de conexão aponta para o serviço 'mssql-server'
- ConnectionStrings__DefaultConnection=Server=mssql-server,1433;Initial Catalog=TodoListDb;User ID=SA;Password=YourStrong(!)Password;TrustServerCertificate=true
# Serviço do banco de dados SQL Server
mssql-server:
image: mcr.microsoft.com/mssql/server:2022-latest # Usa a imagem oficial do SQL Server
environment:
- ACCEPT_EULA=Y
- MSSQL_PID=Express
- SA_PASSWORD=YourStrong(!)Password
ports:
- "1433:1433" # Expõe a porta do SQL Server para o host (útil para ferramentas de DB)
Para uma análise detalhada deste arquivo e mais contexto, recomendo fortemente a leitura do artigo a seguir.
Deploying ASP.NET Core Applications with Docker—Part 2
Este artigo da Telerik, 'Deploying ASP.NET Core Applications with Docker—Part 2', oferece um exemplo completo e comentado de um arquivo docker-compose.yml para uma aplicação ASP.NET Core e um banco de dados SQL Server. É uma excelente referência textual.
Leia as seções 'Creating the Docker Compose File' e a análise detalhada do código que se segue. Foque em como cada serviço (mssql-server e todo-app) é definido e como as chaves image, build, environment e ports são utilizadas.
Conclusão
Parabéns! Você aprendeu os fundamentos do Docker Compose e como definir uma aplicação multi-serviços. Esta é uma mudança de paradigma em relação ao gerenciamento de contêineres individuais e a base para a maioria dos fluxos de trabalho de desenvolvimento com Docker.
Principais Pontos:
- Docker Compose orquestra aplicações multi-contêiner a partir de um único arquivo
docker-compose.yml. - Cada contêiner é definido como um serviço, podendo ser construído a partir de um
Dockerfile(build) ou puxado de um registro (image). - Configurações como mapeamento de portas (
ports) e variáveis de ambiente (environment) são definidas de forma declarativa para cada serviço. - O comando
docker compose upinicia toda a stack, criando uma rede interna que permite a comunicação entre os serviços usando seus nomes como hostnames.
Na próxima lição, vamos aprimorar nosso docker-compose.yml. Aprenderemos a integrar formalmente as redes customizadas e os volumes nomeados que você estudou no módulo anterior, tornando nossa aplicação ainda mais robusta e organizada. Também exploraremos outros comandos do ciclo de vida, como docker-compose down.