Docker Engine: Arquitetura Fundamental
O Docker Engine é o coração de toda a plataforma Docker. Trata-se de uma aplicação cliente-servidor que funciona como um gerenciador de containers, permitindo que você crie, execute, distribua e gerencie aplicações em ambientes isolados. Diferentemente de máquinas virtuais tradicionais, containers compartilham o kernel do sistema operacional host, tornando-os leves e eficientes.
A arquitetura do Docker Engine é construída sobre três pilares fundamentais: o daemon Docker (dockerd), a CLI (Command Line Interface) e a API REST. O daemon é um processo de longa duração que executa no sistema operacional, gerenciando objetos Docker como images, containers, volumes e networks. A CLI é a interface pela qual você interage com o daemon, enquanto a API REST permite que programas terceirizados se comuniquem com o Docker programaticamente.
Componentes Principais da Arquitetura
O Docker Engine funciona através de uma arquitetura modular bem definida. O containerd é o runtime de container que atua como intermediário entre o daemon e o sistema operacional, gerenciando o ciclo de vida dos containers de forma isolada. O runc é o runtime de container de baixo nível que efetivamente executa os containers usando features nativas do Linux como namespaces e cgroups.
┌─────────────────────────────────────────────────┐
│ Aplicação do Usuário │
├─────────────────────────────────────────────────┤
│ Docker CLI (docker command) │
├─────────────────────────────────────────────────┤
│ Docker Daemon (dockerd) - API Server │
├─────────────────────────────────────────────────┤
│ containerd - Container Runtime Manager │
├─────────────────────────────────────────────────┤
│ runc - Container Runtime (executa) │
├─────────────────────────────────────────────────┤
│ Kernel Linux (namespaces, cgroups, etc) │
└─────────────────────────────────────────────────┘
Quando você executa um comando Docker, a CLI se conecta ao daemon através de um socket Unix (/var/run/docker.sock por padrão). O daemon processa a solicitação, interage com o containerd, que por sua vez utiliza o runc para executar os containers. Esse design em camadas permite que o Docker seja modular e que cada componente possa ser atualizado independentemente.
Docker Daemon e Sua Operação
O Docker Daemon (dockerd) é um serviço que roda em background no seu sistema operacional. Ele é responsável por escutar requisições da CLI ou da API, gerenciar o ciclo de vida completo dos containers e manter o estado de todos os objetos Docker. Em sistemas Linux, o daemon geralmente roda como um serviço systemd, enquanto em Windows e macOS roda através do Docker Desktop.
O daemon armazena suas configurações em /etc/docker/daemon.json (no Linux), permitindo customizações como limites de recursos, drivers de storage, logging e outras opções avançadas. Ele mantém um estado persistente que sobrevive a reinicializações, garantindo que containers e volumes configurados anteriormente sejam recuperados quando o daemon reinicia.
Iniciando e Gerenciando o Daemon
No Linux, você pode controlar o daemon usando systemctl. Para verificar se o daemon está rodando:
# Verificar status do daemon
sudo systemctl status docker
# Iniciar o daemon
sudo systemctl start docker
# Parar o daemon
sudo systemctl stop docker
# Reiniciar o daemon
sudo systemctl restart docker
# Habilitar para iniciar automaticamente no boot
sudo systemctl enable docker
Para visualizar informações detalhadas sobre o daemon e seu ambiente:
docker info
Esse comando retorna informações como versão do Docker, número de containers em execução, drivers de storage, logging, e configurações de rede. É uma ferramenta valiosa para diagnosticar problemas e entender o estado atual do seu ambiente Docker.
Configuração do Daemon
Um arquivo daemon.json típico pode parecer assim:
{
"debug": false,
"log-level": "info",
"storage-driver": "overlay2",
"storage-opts": [
"overlay2.override_kernel_check=true"
],
"insecure-registries": [
"registry.interno.local:5000"
],
"registry-mirrors": [
"https://mirror.docker.io"
],
"live-restore": true,
"max-concurrent-downloads": 5
}
Cada propriedade controla um aspecto diferente da operação do daemon. A opção live-restore é particularmente importante em ambientes de produção, pois permite que containers continuem rodando mesmo se o daemon parar e reiniciar. A configuração storage-driver define qual mecanismo de armazenamento o Docker usará para as camadas das imagens.
Docker CLI: Interagindo com Containers
A Docker CLI é a ferramenta de linha de comando que você utiliza diariamente para interagir com o Docker. Ela funciona como um cliente que se conecta ao daemon através de uma chamada de API REST. Cada comando que você executa é traduzido em uma requisição HTTP que o daemon processa e responde.
A CLI segue um padrão de comando bem estruturado: docker [COMANDO] [OPÇÕES] [ARGUMENTOS]. Os comandos principais são divididos em categorias que trabalham com diferentes objetos Docker: imagens (docker image), containers (docker container), volumes (docker volume), networks (docker network), e muitos outros.
Comandos Essenciais para Containers
Os comandos mais frequentemente utilizados são aqueles que lidam diretamente com o ciclo de vida dos containers. Vamos explorar os principais:
# Criar e executar um container
docker run --name meu-app -d -p 8080:80 nginx:latest
# Listar containers em execução
docker ps
# Listar todos os containers (incluindo parados)
docker ps -a
# Executar um comando dentro de um container rodando
docker exec -it meu-app /bin/bash
# Visualizar logs de um container
docker logs meu-app
# Ver logs em tempo real
docker logs -f meu-app
# Parar um container
docker stop meu-app
# Iniciar um container parado
docker start meu-app
# Remover um container
docker rm meu-app
# Inspecionar detalhes de um container
docker inspect meu-app
A flag -d (detached) inicia o container em background, retornando o controle do terminal imediatamente. A flag -it (interactive + tty) permite interação direta com o container, abrindo um shell quando combinada com um comando como /bin/bash. A flag -p mapeia portas entre o host e o container, no formato porta_host:porta_container.
Buildando Imagens com a CLI
Criar imagens Docker também é feito através da CLI. Um Dockerfile é um arquivo de texto que contém instruções para construir uma imagem. Aqui está um exemplo prático:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV FLASK_APP=app.py
ENV FLASK_ENV=production
EXPOSE 5000
CMD ["python", "-m", "flask", "run", "--host=0.0.0.0"]
Para construir a imagem e executar o container:
# Construir a imagem
docker build -t minha-app-flask:1.0 .
# Executar um container baseado na imagem
docker run -d -p 5000:5000 --name app-flask minha-app-flask:1.0
# Verificar se está rodando
docker ps
A flag -t especifica uma tag para a imagem no formato nome:versao. O ponto final (.) indica que o Dockerfile está no diretório atual. O Docker irá executar cada instrução do Dockerfile sequencialmente, criando camadas (layers) que são cacheadas para builds futuros.
Ciclo de Vida de Containers
O ciclo de vida de um container é uma sequência bem definida de estados pelos quais passa desde sua criação até sua remoção. Compreender esse ciclo é essencial para gerenciar containers efetivamente em produção. Um container passa por vários estados: Created, Running, Paused, Stopped e Deleted.
Quando você executa docker run, ocorre uma sequência de operações: primeiro a imagem é baixada (se não existir localmente), depois um container é criado a partir dessa imagem, e finalmente o container é iniciado executando o comando especificado. Se nenhum comando for fornecido, o container usará o comando padrão definido na imagem (ENTRYPOINT ou CMD).
Estados e Transições
Um container começa no estado Created quando é criado mas não iniciado. Quando você executa docker start, ele transita para Running, onde o processo principal está executando. Do estado Running, você pode pausar o container com docker pause, movendo-o para Paused. Um container pausado pode ser retomado com docker unpause.
# Demonstração do ciclo de vida
docker create --name meu-container nginx:latest
# Container agora está em Created
docker start meu-container
# Container agora está em Running
docker pause meu-container
# Container agora está em Paused
docker unpause meu-container
# Container volta para Running
docker stop meu-container
# Container agora está em Stopped
docker rm meu-container
# Container é removido (estado Deleted)
Quando você executa docker stop, o Docker envia um sinal SIGTERM para o processo principal do container, dando-lhe tempo para encerrar gracefully. Se o container não parar dentro do timeout padrão (10 segundos), o Docker envia SIGKILL para forçar o término. Você pode customizar esse timeout com a flag --time.
Gerenciamento do Ciclo de Vida em Aplicações Reais
Em aplicações reais, você frequentemente precisa garantir que containers reiniciem automaticamente em caso de falha. O Docker oferece políticas de restart para isso:
# Restart sempre que o container parar
docker run -d --restart always --name api-service nginx
# Restart apenas se saiu com código de erro (não-zero)
docker run -d --restart on-failure --name worker nginx
# Restart com máximo de tentativas
docker run -d --restart on-failure:5 --name worker nginx
# Restart com delay entre tentativas
docker run -d --restart on-failure:5 --restart-delay 5s --name worker nginx
A política always é útil para serviços que devem estar sempre disponíveis, enquanto on-failure é melhor para jobs que podem completar com sucesso e não devem ser reiniciados continuamente. Você também pode combinar essas opções com --health-cmd para realizar health checks e permitir que o Docker tome decisões mais inteligentes sobre quando reiniciar.
Exemplo Completo: Ciclo de Vida com Health Checks
Um padrão robusto em produção é combinar restart policies com health checks:
docker run -d \
--name banco-dados \
--restart on-failure:3 \
--health-cmd="curl -f http://localhost:8000/health || exit 1" \
--health-interval=30s \
--health-timeout=5s \
--health-retries=3 \
-p 8000:8000 \
minha-api:1.0
O health check executa o comando especificado a cada 30 segundos. Se o comando retornar sucesso (código 0), o container é considerado saudável. Se falhar 3 vezes consecutivas, o container é marcado como unhealthy. Combinado com a política restart, isso garante que containers problemáticos sejam automaticamente reiniciados.
Para verificar o status de saúde:
# Ver status do health check
docker inspect --format='{{json .State.Health}}' banco-dados | jq .
# Exemplo de saída:
# {
# "Status": "healthy",
# "FailingStreak": 0,
# "Log": [...]
# }
Conclusão
Dominar Docker Engine requer compreensão de três elementos interdependentes: a arquitetura em camadas que separa responsabilidades entre CLI, daemon, containerd e runc; o daemon como orquestrador central que gerencia estado e comunica com o sistema operacional; e finalmente o ciclo de vida de containers como mecanismo essencial para construir aplicações resilientes. Esses conceitos formam a base sólida necessária para avançar em tópicos como orquestração com Kubernetes, deploy em produção e otimização de performance. A prática constante com a CLI e a compreensão profunda de como o daemon funciona transformarão você de um usuário casual para um profissional capaz de diagnosticar problemas complexos e arquitetar soluções robustas.