Tutoriais

Nexus Repository

Tutorial completo de Sonatype Nexus 3 OSS — repositórios Maven, npm e Docker registry, cleanup policies, autenticação e integração com CI/CD.

Tutorial completo de referência para subir e operar o Sonatype Nexus Repository Manager 3 (OSS) em um servidor. O foco aqui é o uso como cache/proxy de dependências Maven e npm, registry Docker privado e repositório raw para artefatos diversos, atrás de um nginx com TLS Let’s Encrypt.


1. Visão geral

O Sonatype Nexus Repository é um gerenciador de artefatos universal. Ele cumpre três papéis:

  1. Proxy de registries públicos — em vez de bater no Maven Central, npm registry ou Docker Hub a cada build, o Nexus baixa uma vez e serve do cache local. Reduz banda, deixa o build mais rápido e funciona offline.
  2. Hosted (privado) — hospeda artefatos próprios (jars internos, pacotes npm privados, imagens Docker da empresa).
  3. Group (agrupamento) — combina vários repositórios do mesmo formato numa única URL pública. Cliente vê 1 endpoint, Nexus resolve internamente a ordem de busca.

Formatos suportados (OSS 3.x)

maven2, npm, docker, raw, pypi, helm, nuget, rubygems, gitlfs, r, apt, yum, conda, cocoapods, bower, go, composer.

Tipos de repositório

TipoFunçãoExemplo
proxyEspelha um remoto, cacheia downloadsmaven-central -> repo1.maven.org
hostedArmazena artefatos publicados por vocêmaven-releases, docker-internal
groupCombina proxy + hosted num só endpointmaven-public, npm-group

OSS vs Pro

RecursoOSSPro
Maven, npm, Docker, raw, pypi etc.simsim
Cleanup policiessimsim
LDAP / SAMLLDAP apenasLDAP + SAML + Crowd
HA / clusternaosim
Staging / RBAC granularnaosim
Blob store S3 / Azurenaosim
Repository Firewall integrationnaosim
Suporte comercialnaosim

Para uso em ambiente interno e cache de CI, OSS atende sobrando.


2. Arquitetura

flowchart LR
    subgraph Dev[Dev / CI]
        A[mvn / gradle]
        B[npm / pnpm]
        C[docker push]
    end

    subgraph Edge[Servidor - nginx-proxy]
        N1[nexus.example.com<br/>TLS LE]
        N2[registry.example.com<br/>TLS LE - docker hosted]
        N3[docker-proxy.example.com<br/>TLS LE - docker proxy]
    end

    subgraph Nexus[container sonatype/nexus3]
        UI[Web UI / REST<br/>:8081]
        DH[connector docker hosted<br/>:5001]
        DP[connector docker proxy<br/>:5002]
        BS[(blob store<br/>/nexus-data/blobs)]
        DB[(H2 / Postgres<br/>/nexus-data/db)]
    end

    A -->|HTTPS 443| N1 --> UI
    B -->|HTTPS 443| N1
    C -->|HTTPS 443| N2 --> DH
    C -.pull.-> N3 --> DP

    UI --> BS
    UI --> DB
    DH --> BS
    DP --> BS

Fluxo de um docker push do CI:

sequenceDiagram
    participant CI as CI Runner
    participant NG as nginx-proxy
    participant NX as Nexus (5001)
    participant FS as blob store

    CI->>NG: POST /v2/myimg/blobs/uploads/ (Bearer)
    NG->>NX: proxy (HTTP, proxy_request_buffering off)
    NX->>NX: valida token (Docker Bearer Realm)
    NX->>FS: grava layer
    NX-->>NG: 201 Created
    NG-->>CI: 201 Created
    CI->>NG: PUT /v2/myimg/manifests/tag
    NG->>NX: proxy
    NX->>FS: grava manifest
    NX-->>CI: 201 Created

3. Pré-requisitos

  • Docker Engine 24+ e Docker Compose v2
  • 4 GB de RAM dedicados ao container (Nexus e uma JVM, ~2 GB heap + 2 GB direct memory + overhead). Em servidor pequeno, planeje 6 GB totais com folga
  • Volume rapido (SSD/NVMe) montado em /nexus-data. Storage de blobs cresce rapido se for cachear muito (Maven Central completo passa de 30 GB)
  • Dominio com DNS apontando pro servidor: nexus.example.com, registry.example.com
  • nginx-proxy ja rodando com Let’s Encrypt
  • Network Docker compartilhada (proxy) entre nginx e Nexus

Sanity check:

docker network ls | grep proxy
df -h /var/lib/docker
free -h

4. Instalacao com docker compose

A imagem oficial e sonatype/nexus3. O processo dentro do container roda como UID 200, entao o volume precisa ser gravavel por esse uid (use volume nomeado do Docker em vez de bind mount para evitar dor de cabeca com permissoes).

Edite /opt/docker/docker-compose.yaml (ou um override dedicado):

services:
  nexus:
    image: sonatype/nexus3:3.74.0
    container_name: nexus
    restart: unless-stopped
    ulimits:
      nofile:
        soft: 65536
        hard: 65536
    environment:
      # Heap fixo (Xms = Xmx evita resize) + direct memory para indices
      INSTALL4J_ADD_VM_PARAMS: >-
        -Xms2g -Xmx2g -XX:MaxDirectMemorySize=2g
        -Djava.util.prefs.userRoot=/nexus-data/javaprefs
    volumes:
      - nexus-data:/nexus-data
    networks:
      - proxy
    # Sem ports: expondo so' via nginx
    expose:
      - "8081"
      - "5001"
      - "5002"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8081/service/rest/v1/status"]
      interval: 30s
      timeout: 10s
      retries: 10
      start_period: 180s

volumes:
  nexus-data:
    driver: local

networks:
  proxy:
    external: true

Suba:

docker compose up -d nexus
docker compose logs -f nexus

O primeiro boot leva 2-3 minutos. Aguarde a linha Started Sonatype Nexus OSS X.Y.Z.

Pontos de atencao

  • Nao use latest em producao (rebuilds quebram). Pin na minor (3.74.0).
  • Se preferir bind mount, garanta chown -R 200:200 /caminho/no/host.
  • INSTALL4J_ADD_VM_PARAMS precisa estar numa unica linha logica (use >- do YAML).
  • A porta 8081 e o web/REST. As portas 5001 e 5002 so funcionam depois de criar os connectors Docker (passo 8).

5. Bootstrap inicial

A senha inicial do admin e gerada no primeiro boot e gravada num arquivo dentro do volume:

docker exec nexus cat /nexus-data/admin.password

Acesse https://nexus.example.com (depois que o nginx estiver configurado, secao 9; pode usar kubectl port-forward/SSH tunnel pro 8081 enquanto isso).

Setup wizard:

  1. Clique em “Sign In” -> usuario admin + senha do arquivo
  2. Defina uma nova senha forte e guarde no gerenciador
  3. Anonymous access: para ambiente interno onde voce confia na rede, marque “Enable anonymous access”. Para Internet exposto, mantenha disabled (forca login pra pull).
  4. Finalize

Apague o arquivo de senha:

docker exec nexus rm /nexus-data/admin.password

6. Repositorios Maven

Vamos criar a hierarquia padrao: 1 proxy + 2 hosted + 1 group.

6.1 maven-central (proxy)

Administration -> Repository -> Repositories -> Create repository -> maven2 (proxy)

  • Name: maven-central
  • Online: marcado
  • Remote storage: https://repo1.maven.org/maven2/
  • Version policy: Release
  • Layout policy: Strict
  • Blob store: default
  • Strict Content Type Validation: marcado

6.2 maven-releases (hosted)

Create repository -> maven2 (hosted)

  • Name: maven-releases
  • Version policy: Release
  • Deployment policy: Disable redeploy (impede sobrescrever release; bloqueia bug comum)

6.3 maven-snapshots (hosted)

Create repository -> maven2 (hosted)

  • Name: maven-snapshots
  • Version policy: Snapshot
  • Deployment policy: Allow redeploy

6.4 maven-public (group)

Create repository -> maven2 (group)

  • Name: maven-public
  • Members (nesta ordem): maven-releases, maven-snapshots, maven-central

A URL publica vira: https://nexus.example.com/repository/maven-public/

6.5 Configuracao do cliente

~/.m2/settings.xml:

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0">
  <mirrors>
    <mirror>
      <id>nexus</id>
      <name>Nexus Public</name>
      <url>https://nexus.example.com/repository/maven-public/</url>
      <mirrorOf>*</mirrorOf>
    </mirror>
  </mirrors>

  <servers>
    <server>
      <id>nexus-releases</id>
      <username>${env.NEXUS_USER}</username>
      <password>${env.NEXUS_PASS}</password>
    </server>
    <server>
      <id>nexus-snapshots</id>
      <username>${env.NEXUS_USER}</username>
      <password>${env.NEXUS_PASS}</password>
    </server>
  </servers>

  <profiles>
    <profile>
      <id>nexus</id>
      <repositories>
        <repository>
          <id>central</id>
          <url>https://nexus.example.com/repository/maven-public/</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled></snapshots>
        </repository>
      </repositories>
    </profile>
  </profiles>

  <activeProfiles>
    <activeProfile>nexus</activeProfile>
  </activeProfiles>
</settings>

Gradle (build.gradle de uma aplicacao Spring Boot, por exemplo):

repositories {
    maven {
        url 'https://nexus.example.com/repository/maven-public/'
    }
}

publishing {
    repositories {
        maven {
            def releasesRepoUrl  = 'https://nexus.example.com/repository/maven-releases/'
            def snapshotsRepoUrl = 'https://nexus.example.com/repository/maven-snapshots/'
            url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
            credentials {
                username = System.getenv('NEXUS_USER')
                password = System.getenv('NEXUS_PASS')
            }
        }
    }
}

7. Repositorios npm

7.1 Criar

  • npm-proxy (proxy de https://registry.npmjs.org/)
  • npm-hosted (hosted, para pacotes internos; deployment Allow redeploy off)
  • npm-group (group, ordem: npm-hosted, npm-proxy)

URL publica: https://nexus.example.com/repository/npm-group/

7.2 Habilitar realm npm

Security -> Realms -> mova npm Bearer Token Realm para “Active”. Isso permite npm login direto contra o Nexus.

7.3 Configurar .npmrc

Dois caminhos: login interativo (gera authToken) ou auth basico em base64.

Opcao A — npm login (recomendado):

npm login --registry=https://nexus.example.com/repository/npm-group/
# usuario / senha / email do Nexus

Resultado no ~/.npmrc:

registry=https://nexus.example.com/repository/npm-group/
//nexus.example.com/repository/npm-group/:_authToken=NpmToken.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
always-auth=true

Opcao B — auth basico (CI/CD):

echo -n 'ci-user:s3nh4-forte' | base64
# Y2ktdXNlcjpzM25oNC1mb3J0ZQ==

.npmrc:

registry=https://nexus.example.com/repository/npm-group/
_auth=Y2ktdXNlcjpzM25oNC1mb3J0ZQ==
email=ci@example.com
always-auth=true

7.4 Publicar pacote interno

package.json:

{
  "name": "@empresa/ui-components",
  "version": "1.0.0",
  "publishConfig": {
    "registry": "https://nexus.example.com/repository/npm-hosted/"
  }
}
npm publish

7.5 Migrar um frontend Vue

Em um frontend Vue (ou monorepo equivalente), basta criar um .npmrc na raiz do projeto com registry=... apontando para npm-group. Commitar sem _authToken; o token vai como secret no CI.


8. Docker Registry

Docker e o caso mais sensivel — clientes Docker tem restricoes que dificultam path-based routing.

8.1 Criar repositorios

  • docker-hub (proxy de https://registry-1.docker.io, indice Use Docker Hub)
  • docker-hosted (hosted; deployment Allow redeploy para latest/tags moveis, ou Disable se voce versiona estritamente)
  • docker-group (group: docker-hosted + docker-hub)

8.2 Conectores HTTP

No editor do repositorio, secao Repository Connectors:

RepositorioHTTP connectorHTTPS connector
docker-hosted5001(deixar vazio, TLS no nginx)
docker-group5002(idem)

Marque “Force basic authentication” desativado (vamos usar Bearer). Marque “Allow anonymous docker pull” se quiser pull sem login pelo group.

Para que os connectors funcionem, exponha as portas no compose (passo 4 ja tem expose: 5001 5002).

8.3 Habilitar Docker Bearer Token Realm

Security -> Realms -> mova Docker Bearer Token Realm para “Active”. Sem isso, docker login falha com erros bizarros.

8.4 Por que subdominios > subpath

O Docker client envia requests para /v2/... na raiz do host. Se voce expoe Nexus em https://nexus.example.com/repository/docker-hosted/, o client manda /v2/ e o nginx tem que reescrever — funciona para pull, quebra para push (uploads de blobs grandes, auth challenge, etc).

A pratica padrao e:

  • registry.example.com -> connector 5001 (docker-hosted, push/pull internos)
  • docker.example.com (ou outro) -> connector 5002 (docker-group, pull cacheado)

Cada subdominio respondendo /v2/ na raiz. O Nexus mapeia o subdominio para o repo via porta.

8.5 Login e uso

docker login registry.example.com
# username / password

docker tag api-backend:1.2.3 \
  registry.example.com/api-backend:1.2.3
docker push registry.example.com/api-backend:1.2.3

# Pull cacheado de Docker Hub via group
docker pull docker.example.com/library/postgres:16-alpine

9. Nginx reverse proxy

Tres server blocks: web UI, docker-hosted, docker-group.

/opt/docker/nginx/conf.d/nexus.conf:

# Web UI + REST API
server {
    listen 443 ssl http2;
    server_name nexus.example.com;

    ssl_certificate     /etc/letsencrypt/live/nexus.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/nexus.example.com/privkey.pem;

    client_max_body_size 1G;

    location / {
        proxy_pass http://nexus:8081;
        proxy_set_header Host              $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 600s;
    }
}

/opt/docker/nginx/conf.d/register.conf (docker hosted):

server {
    listen 443 ssl http2;
    server_name registry.example.com;

    ssl_certificate     /etc/letsencrypt/live/registry.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/registry.example.com/privkey.pem;

    # Layers Docker podem passar de 1GB
    client_max_body_size 5G;
    chunked_transfer_encoding on;
    proxy_request_buffering   off;
    proxy_buffering           off;

    # Sinaliza ao cliente que e API v2
    add_header Docker-Distribution-Api-Version registry/2.0 always;

    location / {
        proxy_pass http://nexus:5001;
        proxy_set_header Host              $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  900s;
        proxy_send_timeout  900s;
    }
}

/opt/docker/nginx/conf.d/docker-proxy.conf (docker group, opcional):

server {
    listen 443 ssl http2;
    server_name docker.example.com;

    ssl_certificate     /etc/letsencrypt/live/docker.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/docker.example.com/privkey.pem;

    client_max_body_size 5G;
    chunked_transfer_encoding on;
    proxy_request_buffering   off;
    proxy_buffering           off;

    add_header Docker-Distribution-Api-Version registry/2.0 always;

    location / {
        proxy_pass http://nexus:5002;
        proxy_set_header Host              $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  900s;
    }
}

Recarrega:

docker exec nginx-proxy nginx -t && docker exec nginx-proxy nginx -s reload

Pontos criticos para Docker:

  • proxy_request_buffering off — sem isso, layers grandes estouram disco do nginx ou time out
  • client_max_body_size generoso
  • chunked_transfer_encoding on — Docker manda blobs em chunked
  • Docker-Distribution-Api-Version — alguns clients chamam GET /v2/ so’ pra checar essa header

10. Realms e autenticacao

Security -> Realms. Ative em ordem:

  1. Local Authenticating Realm (sempre)
  2. Local Authorizing Realm (sempre)
  3. Docker Bearer Token Realm (se usar Docker)
  4. npm Bearer Token Realm (se usar npm login)
  5. LDAP Realm (opcional)

Usuarios e roles

Security -> Users -> Create local user:

  • ci-bot para CI/CD
  • dev para acesso humano

Security -> Roles -> Create role:

RolePrivilegios
developersnx-repository-view-*-*-read, nx-repository-view-*-*-browse
maven-publishersnx-repository-view-maven2-maven-releases-*, idem snapshots
docker-publishersnx-repository-view-docker-docker-hosted-add, -edit, -read
ci-botunion das tres anteriores

Atribua roles aos usuarios em Users -> <user> -> Roles.

LDAP (opcional)

Security -> LDAP -> Create connection. Util se voce ja tem Keycloak/AD; para ambiente single-user nao compensa.


11. Cleanup policies

Sem limpeza, o blob store cresce indefinidamente. Crie politicas e agende a task de cleanup.

Administration -> Repository -> Cleanup Policies -> Create Cleanup Policy:

Policy: snapshots-old

  • Format: maven2
  • Component age: 30 dias
  • Component usage: deixar vazio
  • (Pro apenas) Retain N versions

Policy: docker-untagged

  • Format: docker
  • Asset regex: deixar vazio
  • Components: Only untagged components

Aplique no repo: Repositories -> <repo> -> Cleanup -> Apply.

Agendar tasks

Administration -> System -> Tasks -> Create task:

  1. Admin - Cleanup repositories using their associated policies — diaria 03:00
  2. Admin - Compact blob store — semanal 04:00 (libera espaco efetivamente)
  3. Maven - Delete unused SNAPSHOT — semanal (mantém ultimo N)
  4. Docker - Delete unused manifests and images — semanal

As tasks fazem soft delete (marcam como deletado). O compact blob store e quem efetivamente libera disco.


12. Backup

Nexus 3 modernos (3.71+) usam H2 (ou Postgres no Pro). Em versoes antigas era OrientDB.

Backup logico (recomendado)

Administration -> System -> Tasks -> Create task -> Admin - Export databases for backup:

  • Output path: /nexus-data/backup
  • Schedule: diaria 02:00

Isso exporta um dump consistente sem precisar parar o Nexus.

Backup do blob store + configs

Os arquivos binarios ficam em /nexus-data/blobs/. Strategy mais seguro:

# 1. Roda a task de export pra ter db consistente
# 2. Tar dos blobs + db dump
docker exec nexus tar czf /tmp/nexus-backup.tar.gz \
    /nexus-data/backup \
    /nexus-data/blobs \
    /nexus-data/etc

docker cp nexus:/tmp/nexus-backup.tar.gz /root/backups/nexus-$(date +%F).tar.gz

Para backup completo offline (mais lento, mais seguro):

docker compose stop nexus
tar czf /root/backups/nexus-full-$(date +%F).tar.gz \
    -C /var/lib/docker/volumes/infra_nexus-data/_data .
docker compose start nexus

Restore

  1. Parar Nexus
  2. Apagar conteudo do volume
  3. Extrair tar
  4. Subir Nexus
  5. Se backup logico: Administration -> System -> Tasks -> Admin - Restore databases from backup no proximo boot (Nexus detecta o dump em /nexus-data/backup e oferece restore)

Teste o restore numa VM separada antes de precisar.


13. HA e storage

OSS nao tem HA — instancia unica, ponto. Se cair, cai. Mitigacoes:

  • Healthcheck no compose + restart: unless-stopped
  • Monitor com Uptime Kuma ou similar
  • Backup automatizado (secao 12)
  • Volume em disco com snapshots (LVM, ZFS, BTRFS)

Blob store: OSS aceita filesystem local. S3/Azure blob store sao recursos Pro. Para crescer:

Administration -> Repository -> Blob Stores -> Create -> File
Name: blobs-extra
Path: /nexus-data/blobs-extra

Repos novos podem usar o blob novo. Repos existentes nao migram facilmente — planeje antes.


14. Integracao com CI (GitHub Actions / Gitea Actions)

Secrets

No GitHub Actions (ou Gitea Actions), configure:

  • NEXUS_USER = ci-bot
  • NEXUS_PASS = senha forte do ci-bot

Push de imagem Docker

.github/workflows/build.yml:

name: build-and-push
on:
  push:
    tags: ['v*']

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Log in to Nexus registry
        run: |
          echo "${{ secrets.NEXUS_PASS }}" \
            | docker login registry.example.com \
                -u "${{ secrets.NEXUS_USER }}" --password-stdin

      - name: Build
        run: |
          docker build -t registry.example.com/api-backend:${GITHUB_REF_NAME} .

      - name: Push
        run: |
          docker push registry.example.com/api-backend:${GITHUB_REF_NAME}

Maven deploy (Gradle)

- name: Publish to Nexus
  env:
    NEXUS_USER: ${{ secrets.NEXUS_USER }}
    NEXUS_PASS: ${{ secrets.NEXUS_PASS }}
  run: ./gradlew publish

npm publish

- name: Setup .npmrc
  run: |
    cat > ~/.npmrc <<EOF
    registry=https://nexus.example.com/repository/npm-group/
    //nexus.example.com/repository/npm-hosted/:_authToken=${{ secrets.NEXUS_NPM_TOKEN }}
    always-auth=true
    EOF
- run: npm publish --registry=https://nexus.example.com/repository/npm-hosted/

15. Troubleshooting

”503 Service Unavailable” no UI

Quase sempre OOM da JVM. Cheque:

docker stats nexus
docker exec nexus tail -100 /nexus-data/log/nexus.log | grep -i "OutOfMemory\|GC"

Suba -Xmx no INSTALL4J_ADD_VM_PARAMS (mas garanta RAM no host).

”Repository does not allow updating assets”

Voce tentou re-publicar um release com a mesma versao. Comportamento correto da policy Disable redeploy. Solucoes:

  • Bumpa a versao
  • (Apenas em dev) temporariamente troca a policy para Allow redeploy

docker push: “blob upload invalid” ou trava em 50%

Problema de buffering no nginx. Garanta:

proxy_request_buffering off;
client_max_body_size 5G;

E que a porta interna do connector docker bate com o proxy_pass.

docker login retorna unauthorized: authentication required mesmo com senha certa

Faltou ativar o Docker Bearer Token Realm em Security -> Realms.

Disk full

du -sh /var/lib/docker/volumes/infra_nexus-data/_data/blobs/*

Rodar manualmente:

  • Admin - Cleanup repositories using their associated policies
  • Admin - Compact blob store

Se urgente, considere apagar repos proxy inteiros (eles re-cacheiam sob demanda).

”Index out of date” em busca

Administration -> System -> Tasks -> Create task -> Repair - Rebuild repository search index. Roda uma vez.

Permissoes ao migrar para bind mount

docker compose stop nexus
chown -R 200:200 /caminho/do/bind
docker compose start nexus

16. Referencias