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 | |
|---|---|---|
| Recursos | Push e pull, autenticação simples | Interface web, projetos, usuários e RBAC, varredura com Trivy, replicação, assinatura |
| Recursos de máquina | Mínimos (um container) | Vários containers; recomenda-se 4 GB de RAM ou mais |
| Indicado para | Uma equipe pequena, CI simples | Vá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.
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.
