Dia 21: Docker — Containers, Images, Volumes e Networks

O Docker mudou a forma como se empacota e distribui software. Em vez de instalar dependências directamente no sistema, um container empacota a aplicação e tudo o que ela precisa — bibliotecas, runtime, configuração — numa unidade isolada e portável. Este guia cobre os quatro pilares do Docker: images (o template), containers (a instância a correr), volumes (persistência de dados) e networks (comunicação entre containers). Comandos testados em Ubuntu 24.04 e Rocky Linux 9.

Nota: Este artigo assume um servidor Linux com acesso root ou sudo, e pelo menos 2 GB de RAM disponível. Em VPS com 1 GB, o Docker funciona mas containers pesados (bases de dados, CI/CD) podem exaurir a memória.

Este artigo faz parte da série Linux em 30 Dias. Para seguir a ordem, ver o Dia 1: Terminal, Shell e Comandos Essenciais.

Introdução: Containers vs Máquinas Virtuais

Uma máquina virtual (VM) emula hardware completo: tem o seu próprio kernel, drivers e sistema operativo convidado. Cada VM consome gigabytes de disco e RAM, mesmo quando a aplicação lá dentro precisa de poucos megabytes. O overhead de virtualização é pago a cada arranque — uma VM típica demora 30 a 60 segundos a arrancar.

Um container partilha o kernel do hospedeiro. Em vez de virtualizar hardware, usa funcionalidades do kernel Linux — namespaces (isola processos, rede, mount points) e cgroups (limita CPU, memória, I/O) — para criar isolamento ao nível do processo. O resultado: um container arranca em milissegundos, pesa megabytes e vários containers partilham o mesmo kernel sem duplicar o SO (Docker — What is Docker?, namespaces(7), cgroups(7)).

Comparação Máquina Virtual Container
Kernel Próprio (convidado) Partilhado com o hospedeiro
Tamanho 1–20 GB 5–500 MB
Arranque 30–60 segundos Milissegundos a segundos
Isolamento Hardware completo Processo (namespaces + cgroups)
Overhead Alto (SO completo) Baixo (apenas a aplicação)

O Docker foi lançado em 2013 por Solomon Hykes e popularizou o conceito de container que já existia no Linux através de LXC, cgroups e namespaces. Hoje é o padrão de facto para empacotamento de aplicações, com um ecossistema que inclui o Docker Hub (registo público de images), Docker Compose (orquestração multi-container) e Docker Swarm / Kubernetes (orquestração em cluster). A documentação oficial está em docs.docker.com.

Instalar Docker

O Docker instala-se via repositório oficial. NÃO usar os pacotes docker.io ou docker-compose das distribuições — estão desactualizados. Seguir a documentação oficial de instalação.

Ubuntu/Debian — adicionar a chave GPG e o repositório oficial:

# Instalar dependências
apt update
apt install -y ca-certificates curl gnupg

# Adicionar chave GPG oficial
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

# Adicionar repositório
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
  > /etc/apt/sources.list.d/docker.list

apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Rocky/Alma/RHEL — o Docker não está nos repositórios padrão; usar o repositório oficial:

# Adicionar repositório oficial
dnf install -y dnf-plugins-core
dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo

dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Activar e arrancar o serviço
systemctl enable --now docker

Verificar a instalação:

docker --version
# Docker version 27.x, build xxx

docker run hello-world
# Hello from Docker!
# This message shows that your installation appears to be working correctly.

Para evitar escrever sudo antes de cada comando, adicionar o utilizador ao grupo docker:

usermod -aG docker $USER
# Reiniciar sessão ou: newgrp docker
Atenção: Adicionar um utilizador ao grupo docker dá-lhe acesso root sem password — um container pode montar o sistema de ficheiros do hospedeiro. Em servidores de produção, deixar o sudo docker e restringir o grupo a administradores de confiança.

Imagens e Containers

Uma image é um template só de leitura — um sistema de ficheiros em camadas com a aplicação e dependências. Um container é uma instância a correr dessa image, com uma camada de escrita por cima. A analogia: a image é o programa no disco; o container é o processo em execução (Docker — What is an image?, Docker — What is a container?).

Obter e gerir images

# Descarregar image do Docker Hub
docker pull nginx:latest
docker pull ubuntu:24.04
docker pull alpine:3.20

# Listar images locais
docker images
# REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
# nginx        latest    605c77e624dd   2 weeks ago   142MB
# alpine       3.20      3243e171c5db   3 weeks ago   7.8MB

# Apagar image (só se não houver containers a usá-la)
docker rmi nginx:latest

# Remover images não usadas (liberta disco)
docker image prune -a

As imagens usam um sistema de camadas (layers). Cada instrução num Dockerfile cria uma camada só de leitura. Várias images que partilham a mesma imagem base reutilizam as camadas — daí o docker pull de uma segunda image ser muito mais rápido que a primeira.

Gerir containers

# Lançar container interativo
docker run -it --name teste ubuntu:24.04 bash
# -i  stdin aberto
# -t  aloca pseudo-terminal
# --name  dá nome ao container

# Lançar em background (detached)
docker run -d --name web -p 8080:80 nginx:latest
# -d  detached (corre em background)
# -p  mapeia porta hospedeiro:container

# Listar containers a correr
docker ps
# CONTAINER ID   IMAGE     STATUS         PORTS                  NAMES
# a1b2c3d4e5f6   nginx     Up 2 minutes   0.0.0.0:8080->80/tcp   web

# Listar todos (incluindo parados)
docker ps -a

# Ver logs de um container
docker logs web
docker logs -f web          # seguir em tempo real
docker logs --tail 50 web    # últimas 50 linhas

# Entrar num container a correr
docker exec -it web bash

# Parar e arrancar
docker stop web
docker start web

# Reiniciar
docker restart web

# Apagar container (tem de estar parado)
docker rm web
docker rm -f web             # força mesmo a correr

# Apagar todos os containers parados
docker container prune

Dockerfile — construir images próprias

O Dockerfile é um ficheiro de texto com instruções para construir uma image. Exemplo — uma aplicação Node.js simples:

FROM node:20-slim

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --omit=dev

COPY . .

EXPOSE 3000
CMD ["node", "server.js"]

Construir a image:

docker build -t minha-app:1.0 .
# -t  tag (nome:versão)
# .   contexto (directório actual)

docker run -d --name app -p 3000:3000 minha-app:1.0

Instruções principais do Dockerfile (referência oficial):

Instrução Função
FROM Define a image base (obrigatório, primeira instrução)
WORKDIR Define o directório de trabalho dentro do container
COPY Copia ficheiros do hospedeiro para o container
RUN Executa um comando durante o build (cria camada)
EXPOSE Documenta a porta que o container escuta (não publica)
CMD Comando a executar ao arrancar o container (um só)
ENTRYPOINT Comando fixo; CMD pode passar argumentos

Volumes e Bind Mounts — Persistência de Dados

Quando um container é apagado com docker rm, a sua camada de escrita desaparece — os dados perdem-se. Para persistir dados entre reinícios e entre containers, o Docker oferece três mecanismos: volumes, bind mounts e tmpfs mounts (Docker — Volumes).

Volumes

Volumes são a forma recomendada para persistência. O Docker gere o armazenamento — fica em /var/lib/docker/volumes/ — e sobrevive à remoção do container. São independentes do sistema de ficheiros do hospedeiro e funcionam em Linux, Mac e Windows.

# Criar volume
docker volume create dados_pg

# Usar volume num container
docker run -d --name postgres \
  -e POSTGRES_PASSWORD=segredo \
  -v dados_pg:/var/lib/postgresql/data \
  postgres:16

# Listar volumes
docker volume ls

# Ver detalhes
docker volume inspect dados_pg

# Apagar volume (se não estiver em uso)
docker volume rm dados_pg

# Remover volumes não usados
docker volume prune

Bind Mounts

Bind mounts montam um directório específico do hospedeiro dentro do container. Úteis para desenvolvimento — o código no hospedeiro aparece dentro do container em tempo real — mas menos portáveis porque dependem do layout do sistema de ficheiros do hospedeiro.

# Bind mount: directório actual → /app no container
docker run -d --name dev \
  -v $(pwd):/app \
  -w /app \
  node:20-slim npm start

# Sintaxe longa (preferida para legibilidade)
docker run -d --name dev \
  --mount type=bind,source=$(pwd),target=/app \
  node:20-slim npm start
Característica Volume Bind Mount
Localização Gerida pelo Docker Directório do hospedeiro
Portabilidade Alta (independente do SO) Baixa (depende do caminho)
Backup docker volume commands Ferramentas normais do SO
Uso típico Bases de dados, dados de produção Desenvolvimento, config files
Atenção: Bind mounts dão ao container acesso de escrita ao directório do hospedeiro. Um container comprometido pode modificar ou apagar ficheiros do hospedeiro. Em produção, preferir volumes. Em desenvolvimento, montar apenas o directório da aplicação, nunca / ou /etc.

Networks — Comunicação entre Containers

Cada container tem a sua própria stack de rede. Por defeito, o Docker cria uma rede bridge chamada bridge e liga todos os containers a ela. Mas containers na bridge predefinida não se conseguem resolver por nome — para isso, criar uma rede personalizada (Docker — Networking).

Tipos de rede

Driver Descrição Uso
bridge Rede virtual isolada no hospedeiro. NAT para acesso externo. Predefinido, standalone
host Container partilha a stack de rede do hospedeiro (sem isolamento). Performance máxima
overlay Rede distribuída entre múltiplos hosts (Swarm). Clusters Docker Swarm
none Sem rede. Container totalmente isolado. Batch processing isolado

Gerir redes

# Listar redes
docker network ls
# NETWORK ID     NAME      DRIVER    SCOPE
# a1b2c3d4e5f6   bridge    bridge    local
# b2c3d4e5f6a1   host      host      local
# c3d4e5f6a1b2   none      null      local

# Criar rede personalizada
docker network create app-net

# Ligar containers à rede (por nome, DNS automático)
docker run -d --name db --network app-net \
  -e POSTGRES_PASSWORD=segredo postgres:16

docker run -d --name web --network app-net \
  -p 8080:3000 minha-app:1.0

# O container "web" consegue aceder a "db" por nome:
# psql -h db -U postgres

# Ver detalhes da rede
docker network inspect app-net

# Desligar container de uma rede
docker network disconnect app-net web

# Apagar rede (se não houver containers ligados)
docker network rm app-net

# Remover redes não usadas
docker network prune

A vantagem de uma rede personalizada é a resolução de nomes por DNS. O container web liga-se ao db usando o nome — não precisa de saber o IP. Se o db for reiniciado e mudar de IP, o DNS actualiza automaticamente.

Erros Comuns

Erro Causa Solução
permission denied ao executar docker Utilizador não está no grupo docker sudo usermod -aG docker $USER e reiniciar sessão
Cannot connect to the Docker daemon Serviço docker parado sudo systemctl start docker
port is already allocated Outro processo usa a mesma porta Mudar o mapeamento -p ou parar o processo conflituante
No space left on device Disco cheio com images/volumes antigos docker system prune -a para limpar tudo não usado
Dados desaparecem ao reiniciar container Sem volume; camada de escrita é efémera Montar volume com -v para dados persistentes
Containers não se encontram por nome Estão na bridge predefinida (sem DNS) Criar rede personalizada e ligar os containers com --network
image not found no build Typo no nome da image base ou sem tag Verificar FROM no Dockerfile; confirmar tag em hub.docker.com
Container sai imediatamente (Exited (0)) CMD/ENTRYPOINT termina sem processo daemon Verificar docker logs; usar -it para debug interativo
bind source path does not exist Directório do hospedeiro não existe Criar o directório antes de docker run ou usar caminho absoluto
Dica: O comando docker system df mostra o espaço usado por images, containers e volumes. Executar antes de prune para avaliar o que vai ser limpo.

Artigos Relacionados