Docker

Fundamentos do Docker

Docker é uma plataforma de código aberto que simplifica o uso de containers Linux.

Um container é um processo isolado que empacota uma aplicação e suas bibliotecas, sem incluir um sistema operacional completo.

Containers vs Máquinas Virtuais

Máquinas Virtuais

Containers

Máquinas Virtuais simulam um hardware completo através do hypervisor e exige um sistema operacional dedicado para cada instância — é como ter vários computadores físicos rodando em um só.

Containers compartilham o kernel do host e isolam apenas o necessário (processos, rede, sistema de arquivos), consumindo uma fração dos recursos de uma VM.

Comparativo

CaracterísticaMáquina VirtualContainer
TamanhoGBsMBs
Tempo de inicializaçãoMinutosSegundos
IsolamentoCompleto (hardware virtual.)Nível de processo
Sistema OperacionalCompleto por VMCompartilhado
PerformanceOverhead do hypervisorPróximo ao nativo
PortabilidadeLimitadaAlta
DensidadeDezenas por hostCentenas/milhares por host

Containers e VMs não são excludentes! É comum executar containers dentro de VMs para combinar os benefícios de ambos.

Docker em Diferentes Sistemas Operacionais

Containers baseados em distribuições Linux dependem de funcionalidades nativas do Kernel Linux, por isso só rodam diretamente no Linux — em outros sistemas operacionais é necessária uma camada de virtualização:

macOS

Apesar de ser um sistema Unix, o macOS é baseado no BSD e usa o kernel Darwin. Como containers baseados em Linux só rodam sobre o Kernel Linux, o Docker Desktop para Mac cria uma VM em segundo plano para executá-los.

Windows

O Windows oferece duas opções:

  • WSL 2 (recomendado): O Windows Subsystem for Linux 2 executa um kernel Linux real usando virtualização leve. O Docker Engine pode rodar diretamente dentro do WSL 2 oferece uma performance próxima ao Linux nativo.
  • Hyper-V: Alternativa que cria uma VM Linux tradicional, similar ao macOS.

Se você usa Windows com WSL 2, é possível instalar o Docker Engine diretamente na distribuição Linux (Ubuntu, Debian, etc.) sem precisar do Docker Desktop.

O Docker no Windows também pode rodar "Windows Containers" nativos, porém sua adoção é limitada comparada aos containers Linux, que são o padrão da indústria e foco deste guia.

Funcionalidades do Kernel Linux

Containers dependem de três funcionalidades nativas do kernel Linux.

Isolamento por Processo

O processo principal do container (PID 1) é configurado com namespaces e cgroups específicos, e todos os processos filhos herdam essas configurações automaticamente.

Namespaces

Namespaces isolam recursos do sistema para que um processo tenha sua própria visão do ambiente. Cada container recebe seus próprios namespaces:

NamespaceO que isolaExemplo prático
PIDProcessosContainer vê apenas seus próprios processos (PID 1 é o processo principal)
NETRedeContainer tem sua própria interface de rede, IP e tabela de rotas
MNTSistema de arquivosContainer tem sua própria árvore de diretórios
UTSHostnameContainer pode ter seu próprio hostname
IPCComunicação entre processosFilas de mensagem e memória compartilhada isoladas
USERUsuáriosUID 0 (root) dentro do container pode ser mapeado para usuário não-root no host

O Linux possui mais dois namespaces: Cgroup namespace (kernel 4.6, 2016) isola a visão da hierarquia de cgroups — não confundir com cgroups em si, que são um mecanismo separado para limitar recursos. Time namespace (kernel 5.6, 2020) isola os relógios do sistema. Ambos são menos comuns no contexto de containers Docker.

Por padrão, cada container recebe seu próprio conjunto de namespaces. Porém, é possível compartilhar namespaces específicos entre containers. Por exemplo, dois containers podem entrar no mesmo namespace de rede e se comunicar via localhost, enquanto mantêm namespaces separados para PID, MNT, UTS, IPC e USER.

Control Groups (cgroups)

Enquanto namespaces isolam o que o processo pode ver, cgroups controlam o quanto de cada recurso ele pode usar:

  • CPU: Controla o consumo de processador
  • Memória: Define limites de RAM (o container é encerrado se exceder)
# Exemplo: executar container com limite de 512MB de RAM e 50% de CPU
docker run -m 512m --cpus=0.5 nginx

Union File System

O sistema de arquivos em camadas permite que containers compartilhem partes comuns e mantenham apenas suas diferenças. Funciona como uma pilha de transparências:

  1. Camada base (read-only): Sistema operacional mínimo
  2. Camadas intermediárias (read-only): Dependências e aplicação
  3. Camada do container (read-write): Alterações feitas durante execução

Isso explica por que imagens Docker são tão eficientes — se você tem 10 containers baseados na mesma imagem, as camadas base são compartilhadas em disco.

A camada read-write é efêmera: se o container for removido, os dados serão perdidos. Para fazer persistência de dados podemos montar diretórios do host no container (bind mounts) ou usar Volumes gerenciados pelo Docker (que veremos em módulos futuros).

Arquitetura do Docker

Arquitetura Docker

  • Cliente: A CLI (como docker run, docker build) envia comandos via REST API para o daemon.
  • Daemon (dockerd): Processo que roda em background e gerencia imagens, containers, networks e volumes. Delega a execução para o containerd via gRPC.
  • containerd: Runtime de alto nível que gerencia o ciclo de vida dos containers, baixa e armazena imagens. Comunica-se com os shims via ttrpc.
  • shim: Processo intermediário que permite ao containerd reiniciar sem afetar os containers em execução. Mantém os streams (stdin/stdout/stderr) abertos e reporta o status de saída.
  • runc: CLI que cria e executa containers usando namespaces, cgroups e union file system. Instalado junto com o Docker (via pacote containerd.io), é o runtime de referência da OCI. Após iniciar o container o runc encerra e o shim assume o controle.
  • container: Processo isolado executando a aplicação. Comunica-se com o shim via stdio (stdin/stdout/stderr) para I/O e gerenciamento de ciclo de vida.

Instalação do Docker (Ubuntu)

  1. Remova versões antigas (se existirem):
sudo apt remove $(dpkg --get-selections docker.io docker-compose \
  docker-compose-v2 docker-doc podman-docker containerd runc | cut -f1) || true
  1. Instale dependências:
sudo apt update
sudo apt install ca-certificates curl
  1. Adicione a chave GPG oficial do Docker:
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
  1. Configure o repositório:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF
  1. Instale o Docker Engine:
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Configuração Pós-Instalação

Após a instalação configure o Docker para executar sem sudo e iniciar automaticamente com o sistema.

1. Adicione seu usuário ao grupo docker

Por padrão o Docker requer privilégios de root, para executar comandos sem sudo, adicione seu usuário ao grupo docker.

sudo groupadd docker
sudo usermod -aG docker $USER

Adicionar um usuário ao grupo docker concede privilégios equivalentes a root. Só faça isso em máquinas de desenvolvimento ou onde você confia nos usuários.

2. Configure o Docker para iniciar no boot

sudo systemctl enable docker.service
sudo systemctl enable containerd.service

3. Reinicie o sistema

Reinicie o computador para que todas as configurações sejam aplicadas corretamente.

O reinício é necessário para que seu usuário seja reconhecido no grupo docker. Alternativas como newgrp docker ou logout/login funcionam, mas reiniciar garante que tudo esteja configurado corretamente, incluindo a inicialização automática do Docker.

4. Teste a instalação

Após o reinício, execute seu primeiro container:

docker run hello-world

Se a instalação estiver correta, você verá uma saída similar a esta:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
17eec7bbc9d7: Pull complete
Digest: sha256:d4aaab6242e0cace87e2ec17a2ed3d779d18fbfd03042ea58f2995626396a274
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash
...

Entendendo o Hello World

O que aconteceu?

  1. O cliente Docker enviou o comando para o daemon
  2. O daemon verificou se a imagem hello-world existia localmente
  3. Como não existia, baixou do Docker Hub
  4. Criou um container a partir da imagem
  5. Executou o programa dentro do container
  6. O programa imprimiu a mensagem e o container encerrou

Se você executar o mesmo comando novamente, a imagem não será baixada. O Docker usa o cache local, tornando a execução muito mais rápida.

Executando um container interativo

Vamos executar um container Ubuntu de forma interativa:

docker run -it ubuntu bash

Você agora está dentro de um container Ubuntu! Experimente:

cat /etc/os-release
ls /
whoami
exit

Comandos Básicos

Os comandos abaixo devem ser executados no terminal do seu sistema (host), não dentro de um container. Se você ainda estiver dentro do container Ubuntu, digite exit para sair.

Verificar versão do Docker

docker version

Mostra a versão do cliente e do servidor (daemon):

Client: Docker Engine - Community
 Version:           28.1.1
 API version:       1.49
 Go version:        go1.23.8
 Git commit:        4eba377
 Built:             Fri Apr 18 09:52:18 2025
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          28.1.1
  API version:      1.49 (minimum version 1.24)
  Go version:       go1.23.8
  Git commit:       01f442b
  Built:            Fri Apr 18 09:52:18 2025
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.7.27
  GitCommit:        05044ec0a9a75232cad458027ca83437aae3f4da
 runc:
  Version:          1.2.5
  GitCommit:        v1.2.5-0-g59923ef
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Informações do sistema

docker info

Mostra informações detalhadas sobre a instalação:

Client: Docker Engine - Community
 Version:    28.1.1
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.23.0
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.35.1
    Path:     /usr/libexec/docker/cli-plugins/docker-compose

Server:
 Containers: 7
  Running: 0
  Paused: 0
  Stopped: 7
 Images: 42
 Server Version: 28.1.1
 ...

Uso de disco

docker system df

Mostra quanto espaço está sendo usado:

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          42        7         19.45GB   17.99GB (92%)
Containers      7         0         3.805GB   3.805GB (100%)
Local Volumes   6         4         2.36GB    21.04kB (0%)
Build Cache     225       0         1.306kB   1.306kB

Obtendo Ajuda

O Docker possui documentação integrada excelente:

# Ajuda geral
docker --help
 
# Ajuda para um comando específico
docker run --help
docker image --help
docker container --help

Exercícios Práticos

  1. Instale o Docker em seu sistema seguindo as instruções deste módulo
  2. Execute docker version e anote as versões do cliente e servidor
  3. Execute docker info e identifique:
    • Quantos containers você tem?
    • Qual o Storage Driver sendo usado?
  1. Remova a imagem hello-world (caso já tenha em cache): docker rmi hello-world
  2. Execute docker run hello-world
  3. Execute novamente o mesmo comando
  4. Compare as saídas - o que mudou na segunda execução? Por quê?
  1. Execute um container Ubuntu interativo: docker run -it ubuntu bash
  2. Dentro do container, execute:
    • cat /etc/os-release - qual versão do Ubuntu está rodando?
    • ps aux - quantos processos estão rodando?
    • hostname - qual o hostname do container?
  3. Saia do container com exit
  1. Execute docker info e responda:
    • O Docker está usando cgroups v1 ou v2?
    • Qual o diretório root do Docker?
    • A API experimental está habilitada?

Próximo Módulo

No próximo módulo, vamos explorar as Imagens Docker em detalhes: como funcionam as camadas, como baixar, criar tags, e gerenciar imagens no seu sistema.