Imagens Docker: pull, build e push

1. Entendendo o ecossistema de imagens Docker

Imagens Docker são pacotes executáveis imutáveis que contêm tudo necessário para rodar um software: código, runtime, bibliotecas, variáveis de ambiente e arquivos de configuração. Elas são a base para a criação de containers — instâncias em execução dessas imagens.

O ecossistema gira em torno de registries (repositórios centralizados), como Docker Hub, Amazon ECR, Google Container Registry (GCR) e GitHub Container Registry. Cada imagem é identificada por um nome no formato registry/usuario/repo:tag. A tag representa uma versão específica, sendo latest a padrão (mas não recomendada para produção).

Imagens oficiais são mantidas pela Docker ou por comunidades confiáveis (ex: node:18-alpine), enquanto imagens personalizadas são criadas pela equipe de desenvolvimento para atender necessidades específicas do projeto.

2. Pull: baixando imagens do registry

O comando docker pull baixa uma imagem de um registry para o cache local. Sintaxe básica:

docker pull [OPÇÕES] NOME[:TAG|@DIGEST]

Exemplos práticos:

# Baixar imagem oficial do Ubuntu com tag 22.04
docker pull ubuntu:22.04

# Baixar por digest SHA256 (imutável)
docker pull ubuntu@sha256:abcdef1234567890

# Baixar de registry privado (ex: ECR)
docker pull 123456789012.dkr.ecr.us-east-1.amazonaws.com/minha-app:v1

Flags úteis:
- --platform linux/amd64 — especificar arquitetura
- --all-tags — baixar todas as tags de um repositório

Pull por digest é a abordagem mais segura para produção, pois garante que exatamente a mesma imagem será obtida, independente de atualizações na tag.

Para autenticação em registries privados:

# Docker Hub
docker login -u usuario

# Amazon ECR
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com

# Google GCR
gcloud auth configure-docker

3. Build: criando sua própria imagem do zero

O comando docker build constrói uma imagem a partir de um Dockerfile. O contexto de build é o diretório enviado ao daemon do Docker (geralmente o diretório atual).

Dockerfile essencial:

# Imagem base
FROM node:18-alpine

# Define diretório de trabalho
WORKDIR /app

# Copia arquivos de dependências
COPY package*.json ./

# Instala dependências
RUN npm install --production

# Copia código fonte
COPY . .

# Expõe porta
EXPOSE 3000

# Comando de inicialização
CMD ["node", "server.js"]

Comando de build:

docker build -t minha-app:v1 .

Estratégias de otimização:

Multi-stage builds reduzem o tamanho final da imagem:

# Estágio 1: build
FROM node:18 AS builder
WORKDIR /app
COPY . .
RUN npm install && npm run build

# Estágio 2: produção (imagem final)
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Aproveitamento de cache: o Docker reutiliza camadas em cache enquanto o Dockerfile não mudar. Para maximizar isso, coloque instruções que mudam com frequência (como COPY . .) no final do arquivo.

4. Push: publicando imagens para um registry

Após construir a imagem, use docker push para enviá-la a um registry. Antes, é necessário autenticar-se com docker login.

Nomeando corretamente com docker tag:

# Taggear imagem local para push
docker tag minha-app:v1 docker.io/meuusuario/minha-app:v1.2.3

# Enviar ao registry
docker push docker.io/meuusuario/minha-app:v1.2.3

A convenção de nomenclatura é: registry/usuario/repositorio:tag. Para Docker Hub, o registry pode ser omitido.

Políticas de versionamento semântico em produção:

  • v1.2.3 — versão completa (major.minor.patch)
  • v1.2 — versão menor (atualizada com patches)
  • v1 — versão major (atualizada com minor e patches)
  • latest — evitar em produção, usar apenas para desenvolvimento

Exemplo de pipeline de versionamento:

# Build com tag específica
docker build -t app:${CI_COMMIT_TAG} .

# Push com múltiplas tags
docker tag app:${CI_COMMIT_TAG} registry.example.com/app:${CI_COMMIT_TAG}
docker tag app:${CI_COMMIT_TAG} registry.example.com/app:latest
docker push registry.example.com/app:${CI_COMMIT_TAG}
docker push registry.example.com/app:latest

5. Gerenciamento local de imagens

Comandos essenciais para gerenciar imagens no cache local:

# Listar imagens
docker images

# Inspecionar detalhes (camadas, configurações)
docker inspect ubuntu:22.04

# Remover imagem específica
docker rmi ubuntu:22.04

# Remover todas as imagens não utilizadas
docker image prune -a

# Salvar imagem em arquivo tar (offline)
docker save -o minha-app.tar minha-app:v1

# Carregar imagem de arquivo tar
docker load -i minha-app.tar

O comando docker image prune é essencial para liberar espaço em disco, removendo imagens que não estão associadas a nenhum container.

6. Integração com Kubernetes: o ciclo de vida da imagem

No Kubernetes, o kubelet em cada nó é responsável por baixar as imagens dos containers. A política de pull é controlada pelo campo imagePullPolicy no Pod spec:

apiVersion: v1
kind: Pod
metadata:
  name: meu-pod
spec:
  containers:
  - name: app
    image: registry.example.com/app:v1.2.3
    imagePullPolicy: IfNotPresent

Políticas disponíveis:
- Always — sempre baixa a imagem, mesmo se já existir localmente
- IfNotPresent — baixa apenas se não estiver em cache (padrão para tags que não são latest)
- Never — nunca baixa, usa apenas imagens locais

Atualização de imagens em Deployments:

# Rolling update com nova imagem
kubectl set image deployment/minha-app app=registry.example.com/app:v1.3.0

# Ou editando o deployment diretamente
kubectl edit deployment minha-app

Autenticação em registries privados no Kubernetes usa imagePullSecrets:

# Criar secret
kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=usuario \
  --docker-password=senha \
  --docker-email=email@example.com

# Usar no Pod
apiVersion: v1
kind: Pod
spec:
  imagePullSecrets:
  - name: regcred
  containers:
  - name: app
    image: registry.example.com/app:v1

7. Boas práticas e segurança no ciclo pull-build-push

Assinatura de imagens com Docker Content Trust (Notary) garante que a imagem não foi adulterada:

# Ativar trust
export DOCKER_CONTENT_TRUST=1

# Push assinado
docker push meuusuario/minha-app:v1

Scan de vulnerabilidades deve ser parte do pipeline:

# Docker Scout (integrado)
docker scout quickview minha-app:v1

# Trivy (ferramenta externa)
trivy image minha-app:v1

# Snyk
snyk container test minha-app:v1 --file=Dockerfile

Automação com CI/CD — exemplo com GitHub Actions:

name: Build, Push e Deploy

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Login no Registry
        run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.example.com -u ${{ secrets.REGISTRY_USER }} --password-stdin

      - name: Build e Push
        run: |
          docker build -t app:${GITHUB_REF_NAME} .
          docker tag app:${GITHUB_REF_NAME} registry.example.com/app:${GITHUB_REF_NAME}
          docker push registry.example.com/app:${GITHUB_REF_NAME}

      - name: Deploy no Kubernetes
        run: |
          kubectl set image deployment/app app=registry.example.com/app:${GITHUB_REF_NAME}

Boas práticas adicionais:
- Nunca usar latest em produção
- Sempre especificar versão exata ou digest
- Usar imagens base mínimas (Alpine, distroless)
- Escanear vulnerabilidades antes do push
- Implementar políticas de retenção no registry

Referências