Se você está se perguntando como criar contêineres Docker reprodutíveis com o Docker Compose, você veio ao lugar certo. Neste tutorial passo a passo do Docker Compose, você vai aprender como criar contêineres simples, mapear portas com o Docker Compose até cenários multi-contêiner complexos.
Você está pronto? Vamos lá!
Pré-requisitos
Se você deseja acompanhar passo a passo neste tutorial, verifique se você tem o seguinte:
- A fresh install of Ubuntu Server LTS with SSH Enabled. This guide will use Ubuntu Server LTS 20.04.1 as the Docker host machine.
- A computer with VS Code installed (optional). This guide will use Visual Studio Code 1.52.1 to SSH to the Docker host and run commands.
- A extensão oficial SSH do VS Code instalada e conectada ao host Docker. (opcional)
O que é o Docker Compose?
Comandos únicos podem ficar longos, muito longos no Docker. Pegue o exemplo abaixo. Este exemplo cria um contêiner para um aplicativo de software chamado bookstack.
À medida que a complexidade de um ambiente Docker aumenta, também aumenta a quantidade de flags e condições necessárias para uma configuração de contêiner funcional. A linha de comando do Docker começa a se tornar complicada e difícil de solucionar problemas; especialmente quando configurações de vários contêineres começam a ser inseridas.
O Docker Compose é uma forma de criar contêineres Docker reproduzíveis usando um arquivo de configuração em vez de comandos Docker extremamente longos. Ao usar um arquivo de configuração estruturado, é mais fácil identificar erros e definir interações entre os contêineres.
O Docker Compose se torna rapidamente indispensável ao lidar com dependências de contêineres ou ambientes com vários contêineres.
O Docker Compose é uma maneira fantástica de entrar no Infrastructure as Code sem a complexidade de sistemas distribuídos como o Kubernetes.
O Docker Compose usa uma estrutura de arquivo de configuração chamada YAML. O YAML é semelhante ao JSON ou HTML, no sentido de que é uma linguagem estruturada e legível por máquina. O YAML se concentra especificamente em ser o mais legível possível para humanos, mantendo o poder estruturado.
O YAML possui uma desvantagem em que espaços em branco, como tabs, são significativos e devem ser formatados corretamente. O VS Code faz a maior parte desse trabalho difícil para você, e é por isso que você verá muitos exemplos sendo feitos no VS Code.
Instalando o Docker Compose
Agora vamos começar a colocar a mão na massa. Supondo que você esteja conectado ao seu host Docker, é hora de instalar o Docker Compose.
O Docker Compose é um pacote separado do tempo de execução do Docker. Mas ao instalar o Docker Compose, você também instalará o tempo de execução do Docker, matando dois coelhos com uma cajadada só!
Para instalar o Docker Compose e o tempo de execução do Docker, execute os dois comandos a seguir.

Uma vez instalado, agora você deve criar uma estrutura de pastas para armazenar os containers.
Criando uma Estrutura de Pastas para o Docker Compose
Antes de criar um container com o Docker Compose, você deve primeiro criar uma pasta para armazenar os containers. Não apenas crie uma estrutura de pastas para armazenar os containers, mas também perceberá que vários comandos do Docker são sensíveis à localização de vários arquivos de configuração; o Docker Compose não é diferente.
O componente mais importante do Docker Compose é o seu arquivo de configuração chamado docker-compose.yaml. Este arquivo de configuração, como explicado acima, determina como o tempo de execução do Docker deve construir um container.
Ao executar o Docker Compose, o comando procurará pelo arquivo de configuração na mesma pasta em que o comando é executado. Devido a esse requisito, é sempre melhor criar uma pasta separada ao executar o Docker Compose.
Pode haver apenas um arquivo de configuração do Docker Compose por pasta.
Para demonstrar a criação de um container Docker com o Docker Compose, primeiro crie uma estrutura de pastas para armazenar o futuro container e seu arquivo de configuração usando um pequeno servidor de arquivos chamado Caddy.
Caddy é um servidor de arquivos, similar ao apache httpd ou ao nginx, mas escrito na linguagem Go. Caddy foi especificamente projetado para facilidade de uso (e irá gerar ou servir automaticamente um arquivo index.html) sem configuração. Essa combinação faz do caddy uma boa escolha para iniciantes.
Assumindo que você está logado no seu host Docker, crie a estrutura de pastas da seguinte forma:
- No diretório home, crie uma pasta chamada containers. Essa pasta será um bom local para esse e outros containers.
- Dentro da pasta containers, crie uma subpasta chamada caddy. Essa pasta irá conter o arquivo de configuração do Docker Compose e o próprio container do Caddy.
- Por fim, dentro da pasta do container, caddy, crie um arquivo de texto em branco chamado docker-compose.yaml que se tornará o arquivo de configuração do Docker Compose.
Com a estrutura de pastas e o arquivo de configuração do Docker Compose criados, você agora pode começar a preencher esse arquivo com a configuração do Docker Compose.
Criando um Arquivo de Configuração do Docker Compose
Em sua forma mais básica, um arquivo docker-compose.yaml para o container caddy se parece com o seguinte. Em seu editor de texto favorito do Linux ou com o VS Code, copie e cole o código abaixo no arquivo de configuração do Docker Compose criado anteriormente.
Vamos passar por cada uma das opções mostradas:
version
especifica a versão do arquivo docker-compose. Cada nova definição do Docker Compose inclui mudanças que podem quebrar a especificação. Portanto, a versão é importante para que o Docker Compose saiba quais recursos deve usar. A versão 3.7 é a versão mais recente suportada pelo Ubuntu 20.04.1 LTS.
A especificação completa para o Docker Compose 3.x pode ser encontrada aqui. A documentação vinculada menciona todas as opções que podem ser usadas no Docker Compose
services
contém as especificações para os contêineres reais. Você pode definir vários contêineres nesta seção.caddy
é o nome do primeiro contêiner (apenas para referência).container_name
define o nome real dado ao contêiner pelo Docker e deve ser único.image
é o nome da imagem. Neste caso, é definido o caddy do Docker Hub. O nome ou número após a tag separados por dois pontos é a versão.
Mapeamento de Portas
Essa última opção em particular requer uma menção especial:
No Docker Compose, a diretiva ports
permite definir um ou mais mapeamentos do host para o contêiner. Por exemplo, acima você mapeou a porta 80
do host para a porta 80
do contêiner. No entanto, não é necessário que as portas correspondam. O exemplo abaixo mapeia a porta 8800
do host para a porta 80
do contêiner.
Também é possível definir várias portas, como no exemplo abaixo.
Ao fazer isso, as portas 80
e 443
serão mapeadas para o host (uma configuração comum para servidores web, para atender tanto HTTP quanto HTTPS).
O criador da imagem Docker define as portas disponíveis no momento da criação. Verifique a documentação da imagem com a qual você está trabalhando no Docker Hub ou no site do mantenedor para obter as portas mapeáveis. Não faz sentido mapear uma porta se ela não estiver em uso!
Com isso em mente, vamos ver como executar o contêiner.
Executando o Contêiner
Neste ponto, você deve ter o arquivo docker-compose.yaml dentro da pasta ~\containers\caddy. Agora é hora de criar e iniciar o contêiner do Caddy.
No seu terminal, execute o seguinte comando para iniciar os contêineres Docker definidos no arquivo docker-compose.yaml.
Você pode notar que não precisou especificar a localização do arquivo docker-compose.yaml ao executar
sudo docker-compose up -d
. O Docker Compose espera que você execute todos os comandos dentro da pasta que contém o arquivo docker-compose.yaml, pois muitos comandos são relativos a essa pasta.
Agora, verifique se o contêiner está funcionando navegando até http://<seu ip>. Este guia está usando http://homelab-docker como referência.
Você pode ver esse processamento acontecendo no VS Code enquanto está conectado ao host Docker na animação abaixo:

Sucesso! Agora você usou com sucesso o Docker Compose para iniciar um contêiner a partir de um arquivo de configuração. Com esse primeiro passo importante concluído, vamos ver como você gerencia o estado do seu contêiner.
Comandos para Gerenciar Contêineres em Execução em Segundo Plano
Na seção anterior, você iniciou o contêiner caddy usando a opção -d
. Fazendo isso, o contêiner foi executado em um estado desanexado. Quando um contêiner está em um estado desanexado, ele continuará sendo executado em segundo plano. No entanto, isso apresenta um problema: como você gerencia esse contêiner se não tiver mais controle direto sobre ele?
Para resolver esse problema, o Docker Compose possui uma série de comandos que gerenciarão os contêineres iniciados com um arquivo docker-compose.yaml:
docker-compose restart
é usado para reiniciar um contêiner que está em execução. Fazê-lo é diferente de realmente executar novamentedocker-compose up -d
. O comando de reinicialização simplesmente reiniciará um contêiner existente, executará novamente o comandodocker-compose up -d
e recriará o contêiner do zero (se o arquivo de configuração tiver sido alterado).docker-compose stop
irá parar um contêiner em execução sem destruir o contêiner. Da mesma forma,docker-compose start
iniciará o contêiner novamente.docker-compose down
irá parar os contêineres em execução e também destruí-los. É aqui que entram em jogo as ligações de volumes (leia mais abaixo).docker-compose pull
irá buscar a versão atual da imagem (ou imagens) do docker no repositório. Se estiver usando a marcalatest
, você pode seguir comdocker-compose down && sudo docker-compose up -d
para substituir o contêiner pela versão mais recente. Usardocker-compose pull
é uma maneira conveniente de atualizar os contêineres rapidamente com um tempo de inatividade mínimo.docker-compose logs
mostrará os logs do contêiner em execução (ou parado). Você também pode especificar contêineres individuais (se houver vários contêineres definidos no arquivo de composição) comdocker-compose logs <nome do contêiner>
.
A full list of docker-compose commands can be seen by running
docker-compose
with no additional arguments or referenced here in the documentation.
Agora que você tem um contêiner em execução, vamos ver como usar conteúdo salvo localmente em sua máquina.
Criando Ligações de Volumes no Docker Compose
Bind Mounts são como o Docker mapeia dados importantes do usuário para armazenamento local no seu servidor. Para começar, gere algum conteúdo para o contêiner hospedar:
- No host do Docker, dentro da pasta ~/containers/caddy, crie uma nova pasta chamada files.
2. Crie um novo arquivo chamado index.html dentro da pasta ~/containers/caddy, que se parece com o exemplo abaixo. Esta será a página principal que o servidor web Caddy irá servir.
3. Modifique o seu arquivo de configuração do Docker Compose para ficar como o exemplo abaixo. O arquivo de exemplo abaixo está adicionando a seção volumes
e apontando um bind mount para a pasta files recém-criada para torná-la disponível para o contêiner.
4. Execute novamente o comando docker-compose up -d
. O Docker Compose agora reconhecerá que o arquivo foi alterado e recriará o seu contêiner.
5. Acesse a página do contêiner com um navegador e você deverá ver que ela está servindo a página “Hello World!”.
Você pode ver o seguinte na animação abaixo:

Agora você está hospedando conteúdo armazenado localmente em sua máquina! No entanto, e se o seu conteúdo estiver em uma fonte externa, como uma rede compartilhada?
Usando o Docker Compose com Volumes do Docker
Uma vez que você cria um contêiner simples com o Docker Compose, provavelmente precisará que esse contêiner acesse arquivos em outro lugar, talvez em uma rede compartilhada. Se for o caso, você pode configurar o contêiner para usar volumes do Docker diretamente em seu arquivo de configuração do Docker Compose.
Para fins de demonstração, este guia irá criar um servidor de Compartilhamento de Arquivos de Rede (NFS) no host do Docker. Servir conteúdo local como um ponto de montagem NFS não tem nenhum propósito prático fora da demonstração. Se você for montar um volume NFS, geralmente será de uma fonte externa, como um NAS ou servidor remoto.
Configure um Compartilhamento NFS
Se você ainda não tem um compartilhamento NFS configurado, crie um agora no host do Docker para este tutorial. Para fazer isso:
- Instale o pacote do servidor NFS executando
apt install nfs-kernel-server -y
.
2. Adicione o contêiner como uma exportação NFS (similar a um compartilhamento CIFS do Windows) executando o seguinte.
3. Agora verifique se o host expõe o compartilhamento NFS executando o comando showmount -e localhost
. Este comando mostrará quaisquer compartilhamentos NFS atualmente expostos e quem tem acesso.
Na captura de tela abaixo, você pode ver que /home/homelab/containers está exposto, mas apenas para o computador localhost (que é o mesmo servidor que executa o host do Docker).

Se você ver a pasta /home/<username>/containers na saída, o compartilhamento NFS está configurado.
Definindo um Volume Nomeado do Docker
Depois de criar o compartilhamento NFS, agora você precisa informar ao Docker como acessar esse compartilhamento. Usando o Docker Compose, você pode fazer isso definindo um volume nomeado no arquivo de configuração do Docker Compose.
A named volume is a way for Docker to abstract network-based file shares. Network file sharing comes in all sorts of shapes and sizes these days: CIFS (windows) shares, NFS (Linux) shares, AWS S3 Buckets, and more. By creating a Named Volume, Docker does the hard part of figuring out how to talk to the network share and lets the container just treat the share as if it is local storage.
Para criar um volume nomeado:
- Abra o arquivo de configuração do Docker Compose (docker-compose.yaml). Se você estiver seguindo junto, o arquivo deve estar localizado na pasta ~/containers/caddy.
2. Dentro do arquivo de configuração do Docker Compose, adicione uma seção volumes
após a seção services
. Seu arquivo de configuração deve ficar como abaixo. A seção volumes
cria um volume nomeado chamado MeuWebsite. Dentro desse volume nomeado, os parâmetros necessários (como IP, configurações NFS e caminho) são especificados. O parâmetro volumes
dentro da seção services
também é modificado para apontar para o volume nomeado em vez de uma pasta local.
3. Depois de definir o volume nomeado apontando para o compartilhamento NFS no arquivo de configuração do Docker Compose, execute docker-compose up -d
para criar e iniciar o contêiner. Se tudo correr bem, o contêiner e o site devem ser iniciados novamente.

4. Navegue novamente para a página do contêiner. O conteúdo do index.html deve aparecer como se o arquivo estivesse sendo montado localmente. No entanto, esse arquivo está sendo montado através do servidor NFS configurado na rede.

Agora que você pode montar volumes externos do Docker no Docker Compose, agora pode trazer todos os tipos de armazenamento de rede para seus contêineres. No entanto, o Docker Compose pode fazer mais do que apenas definir contêineres ou volumes individuais. Vamos mergulhar em cenários mais complexos com vários contêineres.
Este tutorial não usará mais o contêiner caddy, portanto, você pode remover o contêiner usando
docker-compose down
.
Definindo Múltiplos Contêineres no Docker Compose
A maioria dos contêineres do Docker não funciona isoladamente. Os contêineres do Docker geralmente têm dependências de serviços, como bancos de dados ou serviços da web separados que se comunicam através de uma API.
Usando o Docker Compose, você pode agrupar contêineres definidos em um único arquivo. Ao definir vários contêineres em um único arquivo, os contêineres podem se comunicar entre serviços dependentes e simplificar a organização de layouts de contêineres complexos.
Para demonstrar um cenário como esse, vamos configurar um aplicativo wiki popular chamado BookStack.
O BookStack é um software wiki popular conhecido por sua facilidade de uso e layout hierárquico (ao contrário de um layout plano, como o mediawiki).
O BookStack, assim como muitos aplicativos da web, requer um banco de dados separado para funcionar corretamente, bem como as informações necessárias para se comunicar com o banco de dados. Configurar essa situação é onde o Docker Compose se destaca.
Crie o arquivo de configuração do Docker Compose
O BookStack não possui uma imagem Docker mantida internamente, no entanto, o linuxserver.io mantém uma imagem confiável do BookStack em nome deles no Docker Hub. Embora a documentação no site do Docker Hub tenha um arquivo de configuração recomendado do Docker Compose, este tutorial criará um novo arquivo de configuração enquanto explica os conceitos.
No host Docker:
- Primeiro, crie uma pasta para o BookStack. Se você seguiu os tutoriais da seção anterior, você deve ter uma pasta ~/containers. Crie uma pasta chamada bookstack dentro dela.
2. Em seguida, crie um arquivo de configuração em branco do Docker Compose chamado docker-compose.yaml dentro da pasta bookstack.

3. Agora abra o arquivo de configuração do Docker Compose e defina dois containers: o container bookstack
e o container bookstack_db
(mariadb).
Até agora, este arquivo docker-compose.yaml está usando principalmente conceitos já introduzidos: Você tem dois serviços (bookstack
e bookstack_db
), ambos com imagens e bind mounts. O contêiner bookstack tem um mapeamento de porta da porta do host 8080 para a porta interna 80.
Dado o baixo overhead extremamente baixo dos contêineres Docker, é prática comum definir um contêiner de banco de dados separado para cada aplicativo da web. Fazê-lo permite uma maior separação de responsabilidades. Isso é completamente diferente das configurações de banco de dados tradicionais, onde uma única instalação do banco de dados pode servir centenas de aplicativos da web.
Uma nova opção que você pode ver no arquivo acima é o comando depends_on
. Esse comando informa ao Docker a ordem em que os contêineres devem ser iniciados. Definir o comando depends_on
informa ao Docker que o contêiner bookstack_db
deve ser iniciado primeiro.
Configurando a comunicação entre contêineres com variáveis de ambiente
Este arquivo de configuração construído na última seção ainda não está completo. Embora você tenha definido dois serviços (contêineres), eles não estão se comunicando! O contêiner bookstack
não sabe como se comunicar com o contêiner bookstack_db
. Vamos resolver isso usando variáveis de ambiente.
As variáveis de ambiente são a maneira mais comum de fornecer variáveis para os contêineres Docker. Essas variáveis são fornecidas em tempo de execução (ou definidas no arquivo de configuração docker-compose.yaml) para fornecer informações sobre o que o contêiner precisa fazer.
As variáveis de ambiente são definidas pela pessoa que cria a imagem do Docker. Elas serão diferentes dependendo da imagem do Docker que você está usando e você deve consultar a documentação do criador para saber quais variáveis de ambiente usar.
Há dois métodos para definir variáveis de ambiente: diretamente no arquivo docker-compose.yaml ou em um arquivo separado.
A separate file is, typically, the recommended method, especially if variables contain sensitive data such as passwords. A docker-compose.yaml file is designed to be shared or even uploaded to a public-facing GitHub repo. Having a separate file for sensitive data reduces the chance of an accidental security breach.
No host do Docker, crie duas variáveis de ambiente: uma para o container bookstack e outra para o container bookstack_db.
- Crie um novo arquivo na pasta ~/containers/bookstack chamado bookstack.env com o seguinte conteúdo:
2. Crie um novo arquivo na pasta ~/containers/bookstack chamado bookstack_db.env e inclua o seguinte conteúdo:
3. Como uma prática recomendada, agora garanta que ambos os arquivos env não sejam legíveis por outros usuários.
Você deve alterar o acesso de leitura porque os arquivos bookstack.env e bookstack_db.env contêm dados sensíveis.
4. Atualize o arquivo ~/containers/bookstack/docker-compose.yaml do Docker Compose para fazer referência a esses dois arquivos de ambiente mostrados abaixo.
5. Agora inicie os containers bookstack e bookstack_db usando o Docker Compose.
Você pode ver cada um dos passos mencionados nesta seção sendo realizados no VS Code abaixo.

Monitorando os Logs do Docker Compose
O mecanismo Docker funciona com o Docker Compose para executar várias tarefas diferentes em segundo plano. Ser capaz de monitorar o que está acontecendo, especialmente ao trabalhar com vários containers ao mesmo tempo, é útil.
Para monitorar o container do bookstack, por exemplo, use o comando logs
. Neste tutorial, assim que você ver os logs mostrarem [services.d] done
, você pode acessar a URL do bookstack.


Nesta etapa, você deve ter um wiki totalmente funcional em execução em seu próprio container, com seu próprio banco de dados, completamente dentro do Docker!
Desde que você tenha as pastas bookstack e bookstack_db, você pode recriar seu ambiente bookstack do zero.
Docker Compose e Networking
Até este ponto, você não aprendeu muito sobre a comunicação e a aspecto de rede de como os containers trabalham juntos. Vamos mudar isso.
Quando você cria vários containers dentro de um único arquivo docker-compose.yaml, como fez nas seções anteriores, todos eles são atribuídos à mesma rede (geralmente chamada nome-da-pasta-pai_default).
Você pode ver a rede criada para os containers quando executa o comando docker-compose up -d
como mostrado abaixo.

Quando todos os containers são atribuídos à mesma rede, o Docker cria entradas DNS para eles internamente. É por isso que, no exemplo anterior, você se referiu ao banco de dados como bookstack_db
nas variáveis de ambiente. Esse nome bookstack_db
é na verdade uma entrada DNS que aponta para o endereço IP do container do banco de dados.
Você também não precisa depender do Docker Compose para gerar automaticamente redes para você. Você pode definir manualmente redes internas ou externas. Definir manualmente redes é ótimo quando você tem um contêiner que precisa se comunicar com outro contêiner em um arquivo docker-compose.yaml separado. Você pode expor as portas ou criar uma rede na qual ambos podem se juntar!
Observe que quando você começa a definir redes explicitamente, também precisa definir explicitamente a rede padrão. O Docker Compose deixará de criar automaticamente essa rede assim que você começar a definir as redes
Agora modifique o docker-compose.yaml do bookstack para incluir uma rede criada externamente.
- Crie a rede externa com
docker network create my_external_network
.
2. Defina a rede externa no docker-compose.yaml:
3. Execute docker-compose up -d
para recriar os contêineres. Agora seus dois contêineres estão conectados a duas redes, conforme mostrado abaixo.

O contêiner bookstack agora também está conectado a uma rede definida externamente. Isso permite que você crie outro contêiner que converta o tráfego HTTP do bookstack em HTTPS antes de sair do Docker (referido como um proxy reverso).
Definindo um usuário específico para executar um contêiner.
Por padrão, todos os contêineres Docker são executados como um usuário root em sandbox. Isso é equivalente a executar uma máquina virtual conectada como o usuário Administrador padrão. Embora isso geralmente não seja um problema, existem preocupações de segurança se a sandbox for comprometida.
O outro problema de executar como root são as permissões de arquivo. Você pode notar que, se tentar excluir a pasta db dentro da pasta bookstack, na verdade não pode; o conteúdo pertence ao root.
Embora a maioria das imagens não aprecie ser executada como um usuário não-root, as imagens linuxserver.io em particular oferecem uma variável de ambiente para definir o usuário que é executado dentro do contêiner. Você pode fazer isso adicionando UID=1000
e GID=1000
na configuração bookstack.env.
1000:1000 é o ID de usuário e grupo padrão para o primeiro usuário no ubuntu (que pode não ser você). Você pode ler mais sobre IDs de usuário e IDs de grupo em Relacionado: Um cara do Windows em um mundo Linux: usuários e permissões de arquivo)
Você também pode forçar um UID e GID usando o parâmetro
user
no docker-compose, mas isso não é recomendado, pois a maioria dos contêineres não se comporta bem quando forçados a um usuário diferente
Definindo a Política de Reinicialização
Se você deseja que os contêineres construídos com o Docker Compose reiniciem em caso de falha, use a política de reinício adicionando o parâmetro restart: <opção>
nas configurações do contêiner no arquivo docker-compose.yaml.
Ao adicionar esse parâmetro, os contêineres serão reiniciados automaticamente em caso de falha, ajudando a manter a disponibilidade em caso de problemas de energia inesperados.
Configurando manualmente as entradas de DNS para contêineres
Assim como no Windows e Linux, o Docker também possui um “arquivo de hosts”. Usando o parâmetro extra_hosts em um arquivo de configuração, você pode forçar um host a ser resolvido para um IP específico. Isso pode ser útil quando há restrições de DNS, como DNS dividido ou um servidor de teste com o qual você deseja interagir temporariamente.
Executando comandos
Depois que o contêiner for iniciado, você poderá executar comandos dentro do contêiner usando o docker-compose run
. Por exemplo, talvez você queira iniciar um terminal Bash dentro do seu contêiner bookstack. Para fazer isso, execute o seguinte comando.
Conclusão
Nesta etapa, você deve ter informações suficientes para acompanhar a maioria dos tutoriais de docker-compose disponíveis na web. Ter esse conhecimento pode expandir muito sua capacidade de entrar no mundo do Docker e na construção de aplicativos da web como Infraestrutura como Código.
Source:
https://adamtheautomator.com/docker-compose-tutorial/