Publicando Imagens no Docker Hub e GitHub Container Registry

Publicando Imagens no Docker Hub e GitHub Container Registry

Publicando imagens em registros de container: convenção de nomes no Docker Hub e no GitHub Container Registry, login por token, automação completa com GitHub Actions e metadata-action, builds multi-plataforma para ARM e AMD64, cache de camadas e estratégia de tags para rastreabilidade e rollback.
DevOps

• • 10 min de leitura

Uma imagem que existe apenas na máquina do desenvolvedor não serve a nenhum pipeline de CI/CD, a nenhum servidor de produção, a nenhum colega de equipe. Para que uma imagem seja utilizável em qualquer ambiente, ela precisa ser publicada em um registro de container — um serviço que armazena, versiona e distribui imagens.

O fluxo padrão em qualquer pipeline de entrega é construir a imagem, enviá-la para um registro e, em seguida, instruir o servidor de destino a baixá-la e executá-la. O registro é o elo entre a construção e a execução.

Docker Hub: O Registro Público Padrão

O Docker Hub é o registro padrão do Docker — quando se executa docker pull nginx, é do Docker Hub que a imagem vem. Qualquer pessoa pode criar uma conta gratuita e publicar imagens públicas sem limite.

Criando uma conta e fazendo login:

# Login interativo
docker login

# Login com credenciais diretas (útil em scripts)
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin

Convenção de nomenclatura no Docker Hub:

usuario/nome-da-imagem:tag

# Exemplos
ricardomatosdocker/minha-api:1.0.0
ricardomatosdocker/minha-api:latest

Publicando uma imagem:

# Constrói com a tag no formato correto
docker build -t ricardomatosdocker/minha-api:1.0.0 .

# Adiciona a tag latest apontando para a mesma imagem
docker tag ricardomatosdocker/minha-api:1.0.0 ricardomatosdocker/minha-api:latest

# Publica ambas as tags
docker push ricardomatosdocker/minha-api:1.0.0
docker push ricardomatosdocker/minha-api:latest

GitHub Container Registry: Imagens Integradas ao Repositório

O GitHub Container Registry (GHCR) armazena imagens diretamente associadas a um repositório GitHub. A integração com GitHub Actions é nativa e as permissões seguem as mesmas regras do repositório — quem tem acesso ao repositório tem acesso às imagens.

A URL do GHCR segue o formato:

ghcr.io/usuario-ou-org/nome-da-imagem:tag

# Exemplos
ghcr.io/ricardomatosdocker/minha-api:1.0.0
ghcr.io/minha-empresa/minha-api:main

Login no GHCR:

# Usando um Personal Access Token com permissão write:packages
echo "$GITHUB_TOKEN" | docker login ghcr.io -u "$GITHUB_USERNAME" --password-stdin

Automatizando a Publicação com GitHub Actions

O fluxo mais comum em projetos modernos: a cada push na branch main, o pipeline constrói a imagem e a publica automaticamente. A cada tag de release, publica com o número de versão.

# .github/workflows/docker-publish.yml
name: Build e Publicação de Imagem

on:
  push:
    branches: [ main ]
    tags: [ 'v*.*.*' ]
  pull_request:
    branches: [ main ]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Clona o repositório
        uses: actions/checkout@v4

      - name: Configura o Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login no GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extrai metadados da imagem
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix=sha-

      - name: Constrói e publica a imagem
        uses: docker/build-push-action@v5
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

O que esse workflow produz em cada cenário:

Um push na main gera a tag main e uma tag com o SHA do commit como sha-abc1234. Um Pull Request gera uma imagem de teste mas não a publica. A criação da tag v1.2.0 gera as tags 1.2.0, 1.2 e v1.2.0 — seguindo as convenções do SemVer.

Multi-platform Builds: Imagens para ARM e AMD64

Com a popularização de máquinas Apple Silicon e instâncias ARM na AWS (Graviton), imagens que rodam apenas em AMD64 se tornam um problema. O Docker Buildx permite construir imagens para múltiplas arquiteturas em um único comando:

- name: Configura QEMU para emulação de ARM
  uses: docker/setup-qemu-action@v3

- name: Configura Docker Buildx
  uses: docker/setup-buildx-action@v3

- name: Constrói para múltiplas plataformas
  uses: docker/build-push-action@v5
  with:
    context: .
    platforms: linux/amd64,linux/arm64
    push: true
    tags: ${{ steps.meta.outputs.tags }}

O registro armazena um manifesto multi-plataforma — quando docker pull é executado, o Docker automaticamente baixa a variante correta para a arquitetura do host.

Gerenciando Tags de Forma Consistente

Uma estratégia de tagging bem definida evita confusão sobre qual versão está rodando em cada ambiente:

# SHA do commit — imutável, rastreável, ideal para rollbacks
ghcr.io/empresa/api:sha-abc1234

# Branch — sempre aponta para o último build daquela branch
ghcr.io/empresa/api:main
ghcr.io/empresa/api:develop

# Versão semântica — para releases estáveis
ghcr.io/empresa/api:1.2.0
ghcr.io/empresa/api:1.2      # última patch da minor 1.2
ghcr.io/empresa/api:1        # última minor do major 1

# Ambiente — prático mas menos rastreável
ghcr.io/empresa/api:staging
ghcr.io/empresa/api:production

# latest — conveniente mas perigoso em produção
ghcr.io/empresa/api:latest

A recomendação para ambientes de produção é usar sempre o SHA do commit ou a versão semântica exata — nunca latest ou tags de ambiente mutáveis. Isso garante que o deploy é determinístico e que um rollback pode ser feito apontando para um SHA específico.

Inspecionando Imagens Publicadas

# Inspeciona uma imagem remota sem baixá-la
docker manifest inspect ghcr.io/empresa/api:1.2.0

# Verifica as plataformas suportadas
docker manifest inspect ghcr.io/empresa/api:1.2.0 | \
  grep -A2 '"platform"'

# Histórico de camadas de uma imagem publicada
docker history ghcr.io/empresa/api:1.2.0

O Módulo 3 percorreu o container do conceito à distribuição: o que ele é por baixo, como construí-lo com um Dockerfile que aproveita cache, como dar a ele dados que persistem e rede por onde conversar, como descrever um ambiente inteiro em Compose e como publicar o resultado num registro. A imagem publicada é o ponto em que o trabalho local deixa de ser local — a partir dela, qualquer máquina executa exatamente o mesmo software.

Fontes e leituras recomendadas

Documentação oficial

GitHub Actions

Leitura complementar

Exercícios

Exercício 1

Por que o step de publicação usa push: ${{ github.event_name != 'pull_request' }}? O que se perderia ao publicar também a partir de PRs?

Ver resposta

✓ Resposta: Porque em um Pull Request o objetivo é validar que a imagem constrói, não distribuí-la. A expressão mantém o build acontecendo — que é justamente o valor do check — e desliga apenas a publicação. Publicar a partir de PRs traria dois problemas. O primeiro é poluição: o registro acumularia tags de branches que talvez nunca sejam mergeadas. O segundo é de segurança: qualquer pessoa capaz de abrir um PR passaria a gravar no registro da organização, o que abre caminho para publicar conteúdo arbitrário sob um nome confiável — e imagens são baixadas e executadas justamente por confiança na origem.

Exercício 2

Por que o workflow declara permissions: contents: read e packages: write? O que acontece se packages: write for esquecido?

Ver resposta

✓ Resposta: Porque o GITHUB_TOKEN é gerado automaticamente a cada execução e suas permissões são escopadas. Declará-las explicitamente aplica o mínimo privilégio ao workflow: ele lê o repositório e escreve pacotes, e nada além disso — se o token vazar em um log, o alcance é limitado a essas duas capacidades. Esquecer o packages: write produz uma falha particularmente confusa: o docker login no GHCR conclui com sucesso, dando a impressão de que a autenticação está correta, e só o push falha por permissão. É um dos erros mais comuns na primeira configuração, justamente porque o login não denuncia o problema.

Exercício 3

O que o docker/metadata-action gera em cada cenário — push na main, Pull Request e criação da tag v1.2.0? Por que produzir 1.2 além de 1.2.0?

Ver resposta

✓ Resposta: Um push na main gera a tag main e uma tag com o SHA do commit, como sha-abc1234. Um Pull Request gera a tag correspondente ao PR, mas sem publicação. A tag v1.2.0 gera 1.2.0, 1.2 e v1.2.0. A tag 1.2 existe para quem deseja receber correções de patch sem alterar a referência: quem aponta para ela passa a rodar a 1.2.1 e a 1.2.2 assim que saírem, mas nunca salta sozinho para a 1.3. É a mesma lógica das faixas de versão dos gerenciadores de pacote, aplicada a imagens — segurança de atualização automática apenas onde a compatibilidade está garantida.

Exercício 4

Por que a tag sha-abc1234 é a referência ideal para rollback, e por que apontar produção para latest é problemático?

Ver resposta

✓ Resposta: Porque o SHA é imutável e rastreável: aquela tag corresponde a exatamente um commit e a exatamente uma imagem, para sempre. O rollback se resume a apontar para o SHA anterior, com certeza absoluta do que está subindo e de qual código o produziu. O latest é o oposto — um ponteiro móvel. Duas máquinas que fizeram pull em momentos diferentes podem estar executando imagens distintas sob o mesmo nome, sem nenhum sinal disso. E o custo se manifesta no pior momento: durante um incidente, a pergunta "qual versão está no ar?" deixa de ter resposta confiável, e o rollback não tem alvo definido.

Exercício 5

Por que builds multi-plataforma se tornaram necessários? O que o registro efetivamente armazena quando se usa platforms: linux/amd64,linux/arm64?

Ver resposta

✓ Resposta: Porque a arquitetura deixou de ser homogênea: máquinas Apple Silicon no desenvolvimento e instâncias ARM em produção — como as Graviton da AWS — passaram a conviver com o AMD64 tradicional. Uma imagem construída apenas para AMD64 falha ao subir nessas máquinas ou roda sob emulação, com perda expressiva de desempenho. Com o build multi-plataforma, o registro armazena um manifesto multi-plataforma: uma lista que aponta para a imagem específica de cada arquitetura. O docker pull consulta esse manifesto e baixa automaticamente a variante correta para o host, sem que quem executa precise saber que existem duas.

Exercício 6

Que problema cache-from: type=gha e cache-to: type=gha,mode=max resolvem? E o que o mode=max acrescenta?

Ver resposta

✓ Resposta: Resolvem o fato de o runner ser descartado ao fim de cada execução, levando junto todo o cache de camadas do Docker. Sem um cache externo, cada build recomeça do zero — reinstala dependências, recompila tudo — mesmo quando apenas uma linha mudou, o que anula na prática toda a estratégia de ordenação de camadas do Dockerfile. O type=gha grava essas camadas no cache do GitHub Actions e as recupera na execução seguinte. O mode=max acrescenta a exportação das camadas intermediárias, incluindo as dos estágios de build de um multi-stage — que, por não fazerem parte da imagem final, não seriam preservadas de outro modo, apesar de serem justamente as mais caras de reconstruir.

Comentários

Mais em DevOps

Trabalhando em Equipe com Git Flow
Trabalhando em Equipe com Git Flow

Git Flow como modelo de organização de branches: os papéis de main e develop…

Grafana: Dashboards e Alertas que Fazem Sentido
Grafana: Dashboards e Alertas que Fazem Sentido

Dashboards que respondem perguntas em vez de acumular gráficos…

Estudar e realmente Aprender DevOps & Cloud
Estudar e realmente Aprender DevOps & Cloud

Um panorama de DevOps e Cloud: o problema real que deu origem à prática, por…