Docker¶
Criado em 2013, o Docker introduziu o que se tornou o padrão da indústria para contêineres. Ele foi desenvolvido por Solomon Hykes e sua equipe na dotCloud, uma plataforma de PaaS (Platform as a Service). Desde então, o Docker evoluiu para se tornar uma das ferramentas mais populares para desenvolvimento e implantação de aplicativos em contêineres.
Docker é uma plataforma Open Source escrita em Go que ajuda a criação e a administração de ambientes isolados. Ele permite criar, testar e implantar aplicativos rapidamente. O Docker empacota o software em unidades padronizadas chamadas contêineres, que incluem tudo o que o software precisa para funcionar, como bibliotecas, dependências e arquivos de configuração. Isso garante que o aplicativo funcione de maneira consistente em qualquer ambiente.
O Docker trabalha com uma virtualização a nível do sistema operacional, onde o mesmo utiliza de recursos como o kernel do sistema hospedeiro para executar seus contêineres. Diferente do modelo tradicional de Máquinas Virtuais, o Docker não necessita da instalação de um sistema operacional por completo, e sim apenas dos arquivos necessários para a aplicação ser executada.
Em resumo, o Docker simplifica o processo de desenvolvimento, teste, empacotamento, homologação e implantação de aplicativos em contêineres portáteis e leves. O Docker é amplamente utilizado em ambientes de desenvolvimento e produção, permitindo que os desenvolvedores criem aplicativos de forma mais rápida e eficiente, minimizando impactos no processo de desenvolvimento e entrega de software.
Como já discutido anteriormente, existem diversos runtimes de contêineres e até é possível utilizar contêineres sem Docker. Contudo, atualmente o Docker é o runtime de container mais utilizado no mercado, sendo uma tecnologia fundamental no mundo do DevOps.
No capítulo anterior, estudamos os fundamentos de isolamento (namespaces) e controle de recursos (cgroups) que tornam os contêineres possíveis. O Docker abstrai toda essa complexidade, oferecendo uma interface de linha de comando e um conjunto de ferramentas para criar, distribuir e executar contêineres de forma prática.
Instalação do Docker¶
Em geral, uma ferramenta muito comum para administrar os contêineres Docker é o Docker Desktop. O Docker Desktop é uma aplicação que fornece uma interface gráfica para gerenciar contêineres Docker, imagens e volumes. Ele é fácil de instalar e configurar, e é uma ótima opção para desenvolvedores que desejam trabalhar com Docker em suas máquinas locais. Ele está disponível para Windows, macOS e Linux.
Por outro lado, existe uma confusão comum entre "Docker Desktop" e "Docker Engine". O Docker Engine refere-se especificamente a um subconjunto dos componentes do Docker Desktop que são gratuitos e de código aberto e podem ser instalados apenas no Linux. O Docker Engine pode criar imagens de contêiner, executar contêineres a partir delas e, em geral, fazer a maioria das coisas que o Docker Desktop pode, mas é apenas para Linux e não fornece todo o polimento da experiência do desenvolvedor que o Docker Desktop fornece.
Para instalar o Docker Desktop em sua máquina, acesse o site do Docker Desktop e siga as instruções de instalação para o seu sistema operacional. O Docker Desktop é gratuito para uso pessoal e educacional, mas pode ter custos associados para uso comercial em algumas circunstâncias. Consulte a documentação do Docker para obter mais informações sobre preços e licenciamento.
Em sistemas operacionais baseados em Linux, como Ubuntu, você pode instalar o Docker Engine diretamente usando o gerenciador de pacotes. Aqui estão os passos básicos para instalar o Docker Engine no Ubuntu:
sudo apt-get update && sudo apt upgrade
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
software-properties-common \
gnupg \
lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl start docker
sudo systemctl enable docker
sudo apt-get update && sudo apt upgrade
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl start docker
sudo systemctl enable docker
Também, pode ser útil adicionar o usuário atual ao grupo docker para evitar a necessidade de usar sudo ao executar comandos do Docker. Você pode fazer isso com o seguinte comando:
Depois de adicionar o usuário ao grupo docker, você precisará sair e entrar novamente na sessão para que as alterações tenham efeito. Você pode verificar se o usuário foi adicionado corretamente ao grupo docker executando o seguinte comando:
Após a instalação, você pode verificar se o Docker está funcionando corretamente executando o seguinte comando:
Isso deve exibir a versão do Docker instalada em seu sistema.
Você também pode executar o Hello World do docker, com o seguinte comando:
Isso deve baixar uma imagem de teste do Docker Hub e executar um contêiner a partir dela. Se tudo estiver funcionando corretamente, você verá uma mensagem de boas-vindas do Docker.
Os componentes do Docker¶
Existem três componentes principais no ecossistema Docker:
- Dockerfile: Um arquivo de texto contendo instruções (comandos) para construir uma imagem Docker.
- Imagem Docker: Um instantâneo de um contêiner, criado a partir de um Dockerfile. As imagens são armazenadas em um registro, como o Docker Hub, e podem ser puxadas ou enviadas para o registro.
- Contêiner Docker: Uma instância em execução de uma imagem Docker.
Estrutura do Dockerfile¶
Um Dockerfile é um arquivo de texto que contém uma série de instruções para criar uma imagem Docker. Cada instrução no Dockerfile cria uma camada na imagem, e essas camadas são empilhadas para formar a imagem final. Aqui está um exemplo básico de um Dockerfile:
FROM ubuntu:20.04
RUN apt-get update && \
apt-get install -y nodejs npm
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["npm", "start"]
Vamos analisar cada instrução utilizada nesse exemplo:
FROM ubuntu:20.04: Especifica a imagem base a ser usada. Neste caso, estamos usando a imagem oficial do Ubuntu 20.04.RUN apt-get update && apt-get install -y nodejs npm: Executa comandos no contêiner durante a construção da imagem. Aqui, estamos atualizando o sistema e instalando o Node.js e o npm.WORKDIR /app: Define o diretório de trabalho dentro do contêiner. Todos os comandos subsequentes serão executados a partir desse diretório.COPY . .: Copia os arquivos do diretório atual (no host) para o diretório de trabalho do contêiner.RUN npm install: Instala as dependências do projeto Node.js.EXPOSE 3000: Informa ao Docker que o contêiner escutará na porta 3000 em tempo de execução. Isso não publica a porta, mas é uma boa prática documentar quais portas o contêiner usará.CMD ["npm", "start"]: Especifica o comando a ser executado quando o contêiner é iniciado.
O quadro abaixo resume os principais comandos do Dockerfile:
| Comando | Descrição |
|---|---|
FROM |
Especifica a imagem base a ser usada. |
RUN |
Executa comandos no contêiner durante a construção da imagem. |
WORKDIR |
Define o diretório de trabalho dentro do contêiner. |
COPY |
Copia arquivos do diretório atual (no host) para o contêiner. |
EXPOSE |
Informa ao Docker que o contêiner escutará na porta especificada. |
CMD |
Especifica o comando a ser executado quando o contêiner é iniciado. |
ENTRYPOINT |
Define o ponto de entrada do contêiner, permitindo que o contêiner seja executado como um executável. |
ENV |
Define variáveis de ambiente dentro do contêiner. |
VOLUME |
Cria um ponto de montagem para persistência de dados. |
ARG |
Define variáveis de construção que podem ser passadas durante a construção da imagem. |
LABEL |
Adiciona metadados à imagem, como autor, versão, etc. |
ADD |
Copia arquivos e diretórios do host para o contêiner, com suporte a URLs e extração de arquivos tar. |
USER |
Define o usuário a ser usado ao executar o contêiner. |
HEALTHCHECK |
Define um comando para verificar a saúde do contêiner. |
ONBUILD |
Adiciona instruções que serão executadas quando a imagem for usada como base para outra imagem. |
A complexidade de um Dockerfile pode variar dependendo do aplicativo que você está criando. À medida que você se familiariza com o Docker, você aprenderá a usar esses comandos de forma mais eficaz.
A ordem dos comandos no Dockerfile é importante, pois cada comando cria uma nova camada na imagem. O Docker tenta otimizar o processo de construção, armazenando em cache as camadas que não mudaram. Portanto, é uma boa prática colocar os comandos que mudam com menos frequência (como a instalação de dependências) antes dos que mudam com mais frequência (como a cópia do código-fonte).
Note que, ao gerar uma imagem, o Docker executa cada comando do Dockerfile em uma nova camada. Isso significa que, se você modificar um comando no Dockerfile, todas as camadas subsequentes também serão reconstruídas, o que pode aumentar o tempo de construção da imagem. Para otimizar isso, é recomendável agrupar comandos relacionados e minimizar o número de camadas criadas.
Exercício: construindo sua primeira imagem¶
Vamos criar um projeto mínimo para praticar a construção de uma imagem Docker. Neste exercício, criaremos uma aplicação Node.js simples e a empacotaremos em um contêiner.
Passo 1: Crie um diretório para o projeto e inicialize um projeto Node.js:
Passo 2: Crie o arquivo index.js com uma aplicação web mínima:
const express = require('express');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.send('Hello DevOps! Esta aplicação está rodando em um contêiner Docker.');
});
app.listen(PORT, () => {
console.log(`Servidor rodando na porta ${PORT}`);
});
Passo 3: Crie o Dockerfile na raiz do projeto:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]
Note que, seguindo a boa prática mencionada anteriormente, copiamos primeiro os arquivos package*.json e instalamos as dependências antes de copiar o restante do código-fonte. Dessa forma, se apenas o código-fonte mudar, a camada de instalação de dependências será reutilizada do cache.
Passo 4: Construa a imagem e execute o contêiner:
Passo 5: Acesse http://localhost:3000 no navegador. Você verá a mensagem "Hello DevOps!".
Para encerrar o contêiner após o teste:
A imagem Docker¶
Uma imagem Docker é um arquivo leve, independente e executável que contém tudo o que é necessário para executar um aplicativo, incluindo o código-fonte, bibliotecas, dependências e arquivos de configuração. As imagens são criadas a partir de um Dockerfile e podem ser armazenadas em um registro, como o Docker Hub.
As imagens Docker são compostas por várias camadas, cada uma representando uma instrução no Dockerfile. Essas camadas são empilhadas para formar a imagem final. Quando você executa um contêiner a partir de uma imagem, o Docker cria uma camada de leitura e gravação em cima da imagem, permitindo que você faça alterações no contêiner sem afetar a imagem original.
Por isso, as imagens Docker são imutáveis, o que significa que, uma vez criadas, não podem ser alteradas. Se você precisar fazer alterações em uma imagem, precisará criar uma nova imagem a partir do Dockerfile. As alterações que são feitas em um contêiner em execução não afetam a imagem original, e são armazenadas numa camada sobreposta à imagem original.
As imagens Docker são identificadas por um nome e uma tag, que geralmente seguem o formato nome:tag. Por exemplo, meuapp:1.0 refere-se à imagem chamada meuapp com a tag 1.0. Se você não especificar uma tag, o Docker usará a tag latest por padrão.
Você pode verificar a listagem de imagens disponíveis em sua máquina local usando o seguinte comando:
Vamos supor que você não tenha ainda nenhuma imagem baixada. Você pode baixar uma imagem do Docker Hub usando o comando docker pull. Por exemplo, para baixar a imagem oficial do Nginx, você pode usar o seguinte comando:
Isso fará o download da imagem do Nginx e a armazenará localmente. Você pode verificar se a imagem foi baixada com sucesso executando novamente o comando docker image ls. Você verá uma saída semelhante a esta:
| REPOSITORY | TAG | IMAGE ID | CREATED | SIZE |
|---|---|---|---|---|
| nginx | latest | 4cad75abc83d | 2 weeks ago | 192MB |
A imagem nginx foi baixada e está disponível localmente. Você pode usar essa imagem para criar contêineres Nginx em sua máquina.
O contêiner Docker¶
Um contêiner Docker é uma instância em execução de uma imagem Docker. Como já estudamos, os contêineres são isolados uns dos outros e do host, o que significa que eles têm seu próprio sistema de arquivos, rede e processos. Isso permite que você execute vários contêineres em um único host sem conflitos.
Os contêineres são criados a partir de imagens Docker e podem ser iniciados, parados e removidos conforme necessário. Quando você executa um contêiner, o Docker cria uma camada de leitura e gravação em cima da imagem, permitindo que você faça alterações no contêiner sem afetar a imagem original.
Os contêineres são efêmeros por natureza, o que significa que eles podem ser iniciados e parados rapidamente. Quando um contêiner é parado, ele pode ser removido ou reiniciado a partir da imagem original. Isso torna os contêineres ideais para ambientes de desenvolvimento e produção, onde você pode precisar criar e destruir instâncias rapidamente.
Os contêineres podem ser executados em segundo plano (modo "detached") ou em primeiro plano (modo "foreground"). Quando um contêiner é executado em segundo plano, ele continua em execução mesmo depois que o terminal é fechado. Para executar um contêiner em segundo plano, você pode usar a opção -d com o comando docker run. Por exemplo:
Isso executará um contêiner Nginx em segundo plano, mapeando a porta 80 do contêiner para a porta 8001 do host. Você pode acessar o Nginx no seu navegador em http://localhost:8001.
Se você quiser ver uma lista dos contêineres em execução, pode usar o seguinte comando:
Isso exibirá uma tabela com informações sobre os contêineres em execução, incluindo o ID do contêiner, o nome da imagem, o status e as portas mapeadas.
Se você quiser ver todos os contêineres, incluindo os parados, pode usar o seguinte comando:
Isso exibirá uma tabela com informações sobre todos os contêineres, incluindo os que estão parados.
Para parar um contêiner em execução, você pode usar o seguinte comando:
Substitua <container_id> pelo ID do contêiner que você deseja parar. Você pode encontrar o ID do contêiner na saída do comando docker container ls.
Comandos do Docker¶
Abaixo estão alguns comandos essenciais do Docker que você usará com frequência:
docker pull <image>: Baixar uma imagem de um registro, como o Docker Hub.docker build -t <image_name> <path>: Criar uma imagem a partir de um Dockerfile, onde<path>é o diretório que contém o Dockerfile.docker image ls: Listar todas as imagens disponíveis em sua máquina local.docker run -d -p <host_port>:<container_port> --name <container_name> <image>: Executar um contêiner a partir de uma imagem, mapeando portas do host para portas do contêiner.docker container ls: Listar todos os contêineres em execução.docker container stop <container>: Parar um contêiner em execução.docker container rm <container>: Remover um contêiner parado.docker image rm <image>: Remover uma imagem de sua máquina local.
Docker Hub e registros de imagens¶
Até agora, usamos imagens como nginx, postgres e node sem nos preocupar de onde elas vieram. Essas imagens são baixadas de um registro de imagens (container registry) — um repositório centralizado onde imagens Docker são armazenadas e distribuídas.
O registro mais conhecido é o Docker Hub, que funciona como o "GitHub das imagens Docker". Nele, você encontra:
- Imagens oficiais: mantidas pela Docker ou pelos mantenedores do projeto (ex:
nginx,postgres,node,python). São identificadas pelo selo "Docker Official Image". - Imagens da comunidade: publicadas por desenvolvedores e organizações, no formato
usuario/imagem(ex:dpage/pgadmin4). - Imagens privadas: repositórios com acesso restrito, disponíveis em planos pagos.
Quando você executa docker pull nginx, o Docker busca a imagem no Docker Hub por padrão. Você também pode especificar outro registro:
# Puxar do Docker Hub (padrão)
docker pull nginx
# Puxar de um registro alternativo (ex: GitHub Container Registry)
docker pull ghcr.io/usuario/minha-imagem:1.0
Para enviar suas próprias imagens ao Docker Hub, você precisa:
- Criar uma conta em hub.docker.com
- Fazer login via terminal:
docker login - Nomear a imagem com seu usuário:
docker tag meu-app:1.0 seuusuario/meu-app:1.0 - Enviar:
docker push seuusuario/meu-app:1.0
Existem outros registros populares além do Docker Hub, como o GitHub Container Registry (ghcr.io), o Amazon ECR, o Google Artifact Registry e o Azure Container Registry. Em ambientes corporativos, é comum usar registros privados para armazenar imagens internas da organização.
Boas práticas para Dockerfile¶
À medida que você cria mais imagens Docker, alguns padrões se destacam como boas práticas que melhoram a segurança, o tamanho e o desempenho das suas imagens:
Use imagens base mínimas¶
Prefira imagens baseadas em Alpine ou variantes -slim em vez de imagens completas. Elas são significativamente menores:
Copie dependências antes do código¶
Já vimos essa prática no exercício anterior, mas vale reforçar: copie os arquivos de dependência (package.json, requirements.txt, etc.) e instale-as antes de copiar o código-fonte. Isso maximiza o uso do cache de camadas:
COPY package*.json ./
RUN npm install
# Só depois copie o código — se ele mudar, as dependências não serão reinstaladas
COPY . .
Combine comandos RUN¶
Cada instrução RUN cria uma nova camada na imagem. Combine comandos relacionados para reduzir o número de camadas e o tamanho final:
# Ruim: 3 camadas
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# Bom: 1 camada
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
Use .dockerignore¶
Assim como o .gitignore para o Git, o arquivo .dockerignore impede que arquivos desnecessários sejam copiados para a imagem durante o COPY . .:
Não execute como root¶
Por padrão, processos dentro do contêiner rodam como root. Para maior segurança, crie um usuário não-privilegiado:
Interagindo com contêineres: docker exec¶
Muitas vezes, é necessário executar comandos dentro de um contêiner que já está em execução — seja para depurar problemas, inspecionar arquivos ou executar tarefas administrativas. O comando docker exec permite exatamente isso.
A sintaxe básica é:
As opções mais comuns são -i (interativo) e -t (aloca um terminal), geralmente combinadas como -it para obter um terminal interativo:
# Abrir um shell dentro de um contêiner em execução
docker exec -it meu-container /bin/bash
# Em contêineres Alpine (que não têm bash):
docker exec -it meu-container /bin/sh
# Executar um comando único (sem modo interativo):
docker exec meu-container ls /app
# Ver variáveis de ambiente do contêiner:
docker exec meu-container env
Exercício: explorando um contêiner por dentro¶
Vamos praticar o uso do docker exec para entender como é o ambiente interno de um contêiner.
Passo 1: Inicie um contêiner Nginx:
Passo 2: Abra um shell interativo dentro do contêiner:
Passo 3: Dentro do contêiner, explore o ambiente:
# Veja o sistema operacional base
cat /etc/os-release
# Liste os processos em execução
ps aux
# Veja os arquivos de configuração do Nginx
ls /etc/nginx/
# Veja o conteúdo servido pelo Nginx
cat /usr/share/nginx/html/index.html
# Saia do contêiner
exit
Note que o contêiner tem seu próprio sistema de arquivos, seus próprios processos e sua própria configuração — exatamente o isolamento que estudamos no capítulo de Contêineres.
Passo 4: Limpe o contêiner de teste:
Nos próximos capítulos, veremos como o docker exec é usado em conjunto com o Docker Compose (docker compose exec) para interagir com serviços específicos em ambientes multi-contêiner.