Registry Docker self-hosted na VPS

    Um registry Docker self-hosted armazena suas imagens de container localmente — sem depender do Docker Hub ou GHCR, sem limites de pull, com latência mínima para a VPS que vai fazer o pull. Ideal para ambientes offline, compliance com soberania de dados ou pipelines de alta frequência.

    Registry oficial Docker self-hosted

    Instalar o registry Docker da distribuição oficial:

    yaml
    # docker-compose.yml — Registry Docker com autenticação básica:
    services:
      registry:
        image: registry:2
        restart: always
        ports:
          - "127.0.0.1:5000:5000"
        environment:
          REGISTRY_AUTH: htpasswd
          REGISTRY_AUTH_HTPASSWD_REALM: "Docker Registry"
          REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
          REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
          # Ativar interface de gerenciamento:
          REGISTRY_HTTP_HEADERS_Access-Control-Allow-Origin: '["*"]'
        volumes:
          - registry_data:/data
          - ./auth:/auth
    
    volumes:
      registry_data:
    
    # Criar arquivo de autenticação htpasswd:
    # (instalar: apt install apache2-utils)
    mkdir -p auth
    htpasswd -Bbn meu-usuario minha-senha > auth/htpasswd
    
    # Expor via Nginx com HTTPS (obrigatório para registry remoto):
    # O Docker client exige HTTPS para registry remotos (exceto localhost)

    Nginx como proxy com HTTPS

    Expor o registry com HTTPS e certificado Let's Encrypt:

    nginx
    # /etc/nginx/conf.d/registry.conf:
    server {
        listen 443 ssl http2;
        server_name registry.seudominio.com.br;
    
        ssl_certificate /etc/letsencrypt/live/registry.seudominio.com.br/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/registry.seudominio.com.br/privkey.pem;
    
        # Aumentar limite para push de imagens grandes:
        client_max_body_size 2G;
    
        location / {
            proxy_pass http://127.0.0.1:5000;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_read_timeout 900;
        }
    }
    
    # Login no registry:
    docker login registry.seudominio.com.br
    # (informar usuário e senha do htpasswd)
    
    # Build e push para o registry self-hosted:
    docker build -t registry.seudominio.com.br/minha-org/minha-api:latest .
    docker push registry.seudominio.com.br/minha-org/minha-api:latest
    
    # Pull na VPS:
    docker pull registry.seudominio.com.br/minha-org/minha-api:latest

    Gitea com Container Registry integrado

    Gitea fornece git hosting + container registry em um único serviço:

    yaml
    # docker-compose.yml — Gitea com Container Registry:
    services:
      gitea:
        image: gitea/gitea:latest
        restart: always
        environment:
          USER_UID: 1000
          USER_GID: 1000
          GITEA__packages__ENABLED: "true"  # ativar Container Registry
        ports:
          - "127.0.0.1:3000:3000"
          - "22:22"    # SSH para git push
        volumes:
          - gitea_data:/data
          - /etc/timezone:/etc/timezone:ro
          - /etc/localtime:/etc/localtime:ro
    
    volumes:
      gitea_data:
    
    # Após criar conta e organização no Gitea:
    # Login no Container Registry do Gitea:
    docker login gitea.seudominio.com.br
    # Usuário: seu usuário do Gitea
    # Senha: Personal Access Token (Settings → Applications → Generate Token)
    
    # Push de imagem:
    docker tag minha-api gitea.seudominio.com.br/minha-org/minha-api:latest
    docker push gitea.seudominio.com.br/minha-org/minha-api:latest
    
    # O package aparece em: Gitea → Packages → Container

    Integrar registry self-hosted com GitHub Actions

    Push para o registry próprio no pipeline de CI/CD:

    yaml
    # .github/workflows/build.yml:
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          - name: Login no registry self-hosted
            uses: docker/login-action@v3
            with:
              registry: registry.seudominio.com.br
              username: ${{ secrets.REGISTRY_USER }}
              password: ${{ secrets.REGISTRY_PASSWORD }}
    
          - name: Build e push
            uses: docker/build-push-action@v5
            with:
              context: .
              push: true
              tags: |
                registry.seudominio.com.br/minha-org/minha-api:latest
                registry.seudominio.com.br/minha-org/minha-api:${{ github.sha }}
    
      deploy:
        needs: build
        runs-on: ubuntu-latest
        steps:
          - name: Deploy via SSH
            uses: appleboy/ssh-action@v1
            with:
              host: ${{ secrets.SSH_HOST }}
              username: ${{ secrets.SSH_USER }}
              key: ${{ secrets.SSH_PRIVATE_KEY }}
              script: |
                # Na VPS — já autenticada no registry local:
                docker pull registry.seudominio.com.br/minha-org/minha-api:${{ github.sha }}
                cd /opt/minha-api
                sed -i 's|:.*|:${{ github.sha }}|' docker-compose.yml
                docker compose up -d api

    Limpeza automática de imagens antigas

    Evitar que o disco encha com imagens não utilizadas:

    bash
    #!/bin/bash
    # /opt/scripts/cleanup-registry.sh
    
    # Deletar manifests de mais de 30 dias via Registry API:
    REGISTRY="http://localhost:5000"
    AUTH="meu-usuario:minha-senha"
    
    # Listar repositórios:
    REPOS=$(curl -s -u "$AUTH" "$REGISTRY/v2/_catalog" | jq -r '.repositories[]')
    
    for REPO in $REPOS; do
        # Listar todas as tags:
        TAGS=$(curl -s -u "$AUTH" "$REGISTRY/v2/$REPO/tags/list" | jq -r '.tags[]?')
    
        for TAG in $TAGS; do
            # Verificar data de criação pelo digest:
            DIGEST=$(curl -s -u "$AUTH" -I \
              -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
              "$REGISTRY/v2/$REPO/manifests/$TAG" | grep Docker-Content-Digest | awk '{print $2}' | tr -d '\r')
    
            # (lógica de idade da imagem via label ou timestamp)
            echo "  $REPO:$TAG → $DIGEST"
        done
    done
    
    # Limpeza mais simples — remover imagens não usadas na VPS:
    docker image prune -a --filter "until=720h" -f   # imagens com mais de 30 dias
    docker volume prune -f                             # volumes órfãos
    docker system df                                   # ver uso de disco do Docker
    
    # Crontab — executar toda semana:
    # 0 2 * * 0 root /opt/scripts/cleanup-registry.sh

    $ runstack deploy --plan starter

    Não quer configurar manualmente?

    Não quer configurar manualmente? Implante o VPS para Docker em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.

    Perguntas frequentes