Monte um registry Docker privado com o Distribution (registry:3), HTTPS via Caddy e senha, use no k3s e saiba quando escolher o Harbor.

Para que serve

Um registry Docker privado guarda as imagens das suas aplicações no seu próprio servidor, em vez de publicá-las no Docker Hub ou depender de registries externos. Vantagens: imagens com código proprietário ficam sob seu controle, os nós do cluster baixam tudo pela rede local (mais rápido) e você se protege dos limites de download do Docker Hub para quem não está autenticado. Este artigo usa o Distribution (a imagem oficial registry:3) com HTTPS automático pelo Caddy e autenticação por senha.

Pré-requisitos

  • Uma VM Debian 13 ou Ubuntu 24.04 com Docker e Compose (veja Como instalar Docker e Docker Compose no Debian e no Ubuntu) e espaço em disco para as imagens.
  • Um nome DNS apontando para um IP público da VM, por exemplo registry.seudominio.com.br.
  • Portas 80 e 443 liberadas para esse IP. Leia antes Docker e firewall: por que o Docker ignora o UFW: neste guia, o registry só escuta em 127.0.0.1 e quem fica exposto é o Caddy.

Distribution ou Harbor?

Distribution (registry:3)Harbor
RecursosPush e pull, autenticação simplesInterface web, projetos, usuários e RBAC, varredura com Trivy, replicação, assinatura
Recursos de máquinaMínimos (um container)Vários containers; recomenda-se 4 GB de RAM ou mais
Indicado paraUma equipe pequena, CI simplesVárias equipes ou clientes, auditoria, políticas

Se você já usa Gitea, Forgejo ou GitLab, eles também têm registry de containers embutido, integrado aos repositórios. Este guia segue com o Distribution.

Etapa 1: criar a estrutura e o usuário

mkdir -p /srv/registry/{data,auth,caddy}
cd /srv/registry
docker run --rm --entrypoint htpasswd httpd:2 -Bbn ci 'SenhaForteAqui' > auth/htpasswd
chmod 600 auth/htpasswd

O -B é obrigatório: o registry só aceita senhas em bcrypt. Para mais usuários, repita o comando trocando > por >>.

Etapa 2: subir registry e Caddy com Compose

Crie /srv/registry/compose.yaml:

services:
  registry:
    image: registry:3
    restart: unless-stopped
    environment:
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: Registry
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
      REGISTRY_STORAGE_DELETE_ENABLED: "true"
    volumes:
      - ./data:/var/lib/registry
      - ./auth:/auth:ro
    ports:
      - "127.0.0.1:5000:5000"

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
    depends_on: [registry]

volumes:
  caddy_data:

E o /srv/registry/caddy/Caddyfile:

registry.seudominio.com.br {
    request_body {
        max_size 10GB
    }
    reverse_proxy registry:5000
}

Suba com docker compose up -d. O Caddy obtém o certificado do Let's Encrypt automaticamente no primeiro acesso. O limite de tamanho evita que camadas grandes sejam recusadas.

Atenção: não publique a porta 5000 em 0.0.0.0. Um registry exposto sem TLS envia a senha em texto claro e, como o Docker ignora o UFW, a porta ficaria aberta para a internet mesmo com o firewall ativo.

Etapa 3: enviar a primeira imagem

Na sua máquina de build ou no CI:

docker login registry.seudominio.com.br -u ci
docker pull docker.io/library/nginx:1.28-alpine
docker tag nginx:1.28-alpine registry.seudominio.com.br/infra/nginx:1.28-alpine
docker push registry.seudominio.com.br/infra/nginx:1.28-alpine

Etapa 4: usar o registry no k3s

Em cada nó do cluster, crie /etc/rancher/k3s/registries.yaml:

configs:
  "registry.seudominio.com.br":
    auth:
      username: ci
      password: SenhaForteAqui

Proteja o arquivo (chmod 600) e reinicie: systemctl restart k3s nos servidores e systemctl restart k3s-agent nos agentes. Depois, basta usar image: registry.seudominio.com.br/infra/nginx:1.28-alpine nos manifestos. Se preferir não deixar a senha nos nós, use um imagePullSecret por namespace:

kubectl -n site create secret docker-registry regcred \
  --docker-server=registry.seudominio.com.br \
  --docker-username=ci --docker-password='SenhaForteAqui'

e referencie imagePullSecrets: [{name: regcred}] no pod. Crie usuários diferentes para push (CI) e para pull (cluster), assim um vazamento no cluster não permite enviar imagens.

Etapa 5 (opcional): cache do Docker Hub

Um segundo container do registry pode funcionar como cache (pull-through) do Docker Hub: a primeira vez baixa de lá, as próximas saem do seu servidor. Configure-o com REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io (e, de preferência, usuário e token de uma conta Docker Hub em REGISTRY_PROXY_USERNAME e REGISTRY_PROXY_PASSWORD), em outro nome DNS. No k3s, aponte o espelho em registries.yaml:

mirrors:
  docker.io:
    endpoint:
      - "https://cache.seudominio.com.br"

Um cache aceita apenas download, não push, e espelha um único registry de origem.

Como saber se funcionou

curl -s -u ci:'SenhaForteAqui' https://registry.seudominio.com.br/v2/_catalog
curl -s -u ci:'SenhaForteAqui' https://registry.seudominio.com.br/v2/infra/nginx/tags/list
kubectl run teste --rm -it --restart=Never \
  --image=registry.seudominio.com.br/infra/nginx:1.28-alpine -- nginx -v

O catálogo deve listar infra/nginx, e o pod de teste deve baixar a imagem e mostrar a versão do nginx. Sem credencial, curl https://registry.seudominio.com.br/v2/ deve responder 401.

Etapa de manutenção: liberar espaço

Apagar uma tag não libera o disco; é preciso rodar a coleta de lixo com o registry parado ou em modo somente leitura, para não corromper envios em andamento:

cd /srv/registry
docker compose stop registry
docker compose run --rm registry garbage-collect --delete-untagged /etc/distribution/config.yml
docker compose start registry

Teste antes com --dry-run. Agende essa manutenção fora do horário de deploys e inclua /srv/registry/data no seu backup.

Problemas comuns

"unauthorized" no push mesmo com login feito

O arquivo htpasswd foi gerado sem -B ou com aspas erradas na senha. Gere de novo e reinicie o container do registry.

"413 Request Entity Too Large" ou push interrompido

Algum proxy no caminho limita o tamanho do corpo. Ajuste o max_size no Caddy (ou client_max_body_size 0; se usar Nginx).

Pods em ImagePullBackOff

Veja kubectl describe pod. Normalmente falta o registries.yaml em algum nó ou o k3s não foi reiniciado depois de criá-lo.

Erro de garbage-collect: arquivo de configuração não encontrado

Imagens antigas (registry:2) usam /etc/docker/registry/config.yml. Na versão 3, o caminho é /etc/distribution/config.yml.

Perguntas frequentes

Posso usar HTTP sem certificado na rede interna?

Funciona configurando o registry como inseguro em cada cliente, mas a senha trafega em texto claro. Prefira HTTPS, mesmo internamente.

Como limitar quem acessa o registry?

Além da senha, restrinja o acesso por IP no firewall (escritório, VPN e IPs do cluster) ou publique o registry só pela VPN.

O registry escaneia vulnerabilidades?

O Distribution não. O Harbor integra o Trivy; com o Distribution, escaneie no pipeline (veja Segurança em containers).

Leitura complementar

Precisa de ajuda?

Se o certificado não é emitido e você suspeita de bloqueio nas portas 80/443, abra um ticket informando o IP e o domínio.

Esta resposta lhe foi útil? 0 Usuários acharam útil (0 Votos)

Leia também