Aller au contenu

Installation et configuration

Ce chapitre couvre le déploiement de Nexus Repository Manager OSS avec Podman et la configuration initiale des dépôts.

Prerequis

Composant Version minimum Vérification
Podman 4.0+ podman --version
Réseau Ports 8081-8083 ss -tlnp \| grep 808
Stockage 500 Go minimum df -h /opt/nexus
RAM 4 Go minimum free -h

Structure des fichiers

# Creer l'arborescence de donnees
mkdir -p /opt/nexus/{data,certs,backup}
/opt/nexus/
├── data/               # Volume persistant Nexus (blobs + config)
├── certs/              # Certificats TLS (pour le reverse proxy)
├── backup/             # Sauvegardes de la base de metadonnees
├── Caddyfile           # Configuration du reverse proxy
└── compose.yaml        # Definition des services (Podman compose)

Déploiement avec Podman Compose

Fichier compose.yaml

# /opt/nexus/compose.yaml
version: "3.8"

services:
  nexus:
    image: docker.io/sonatype/nexus3:3.75.1
    container_name: nexus
    restart: unless-stopped
    user: "200:200"
    environment:
      INSTALL4J_ADD_VM_PARAMS: >-
        -Xms2g -Xmx4g
        -XX:MaxDirectMemorySize=4g
        -Djava.util.prefs.userRoot=/nexus-data/javaprefs
    volumes:
      - /opt/nexus/data:/nexus-data:Z
    ports:
      - "127.0.0.1:8081:8081"   # UI et API REST
      - "127.0.0.1:8082:8082"   # Docker Registry
      - "127.0.0.1:8083:8083"   # Docker Registry (groupe)
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8081/service/rest/v1/status"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 120s

  caddy:
    image: docker.io/library/caddy:2-alpine
    container_name: caddy-nexus
    restart: unless-stopped
    volumes:
      - /opt/nexus/Caddyfile:/etc/caddy/Caddyfile:ro,Z
      - /opt/nexus/certs:/data/caddy:Z
    ports:
      - "443:443"
    depends_on:
      nexus:
        condition: service_healthy

Configuration Caddy (reverse proxy)

# /opt/nexus/Caddyfile

# Interface web et API REST
nexus.internal.company.io {
    reverse_proxy nexus:8081
    encode gzip
    log {
        output file /var/log/caddy/nexus.log
    }
}

# Docker Registry (proxy + hosted)
docker.internal.company.io {
    reverse_proxy nexus:8082
    log {
        output file /var/log/caddy/docker.log
    }
}

# Docker Registry (groupe)
docker-group.internal.company.io {
    reverse_proxy nexus:8083
    log {
        output file /var/log/caddy/docker-group.log
    }
}

Lancement

# Ajuster les permissions du volume
chown -R 200:200 /opt/nexus/data

# Demarrer les services
cd /opt/nexus
podman compose up -d

# Verifier le demarrage (peut prendre 1-2 minutes)
podman compose logs -f nexus

# Recuperer le mot de passe admin initial
cat /opt/nexus/data/admin.password

Configuration initiale

Première connexion

  1. Accéder a https://nexus.internal.company.io
  2. Se connecter avec admin et le mot de passe du fichier admin.password
  3. Suivre l'assistant de configuration initiale :
    • Changer le mot de passe admin
    • Désactiver l'accès anonyme (important pour la sécurité)

Créer les blob stores

Avant de créer les dépôts, définir les blob stores pour isoler les données par famille :

# Via l'API REST — creer un blob store fichier
curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/blobstores/file' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "bs-docker",
    "path": "/nexus-data/blobs/bs-docker",
    "softQuota": {
      "type": "spaceRemainingQuota",
      "limit": 10737418240
    }
  }'

Répéter pour chaque blob store :

Blob store Path Quota (soft)
bs-docker /nexus-data/blobs/bs-docker 100 Go
bs-npm /nexus-data/blobs/bs-npm 20 Go
bs-pypi /nexus-data/blobs/bs-pypi 10 Go
bs-maven /nexus-data/blobs/bs-maven 30 Go
bs-system /nexus-data/blobs/bs-system 50 Go
bs-hosted /nexus-data/blobs/bs-hosted 50 Go

Configuration des dépôts

Dépôts Docker

Docker Hub (proxy)

curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/docker/proxy' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "docker-hub-proxy",
    "online": true,
    "storage": {
      "blobStoreName": "bs-docker",
      "strictContentTypeValidation": true
    },
    "proxy": {
      "remoteUrl": "https://registry-1.docker.io",
      "contentMaxAge": 1440,
      "metadataMaxAge": 1440
    },
    "negativeCache": {
      "enabled": true,
      "timeToLive": 15
    },
    "httpClient": {
      "autoBlock": true,
      "blocked": false
    },
    "docker": {
      "v1Enabled": false,
      "forceBasicAuth": true
    },
    "dockerProxy": {
      "indexType": "HUB",
      "indexUrl": "https://index.docker.io/"
    }
  }'

Docker hosted (images internes)

curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/docker/hosted' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "docker-hosted",
    "online": true,
    "storage": {
      "blobStoreName": "bs-hosted",
      "strictContentTypeValidation": true,
      "writePolicy": "ALLOW"
    },
    "docker": {
      "v1Enabled": false,
      "forceBasicAuth": true,
      "httpPort": 8082
    }
  }'

Docker group

curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/docker/group' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "docker-group",
    "online": true,
    "storage": {
      "blobStoreName": "bs-docker",
      "strictContentTypeValidation": true
    },
    "group": {
      "memberNames": ["docker-hosted", "docker-hub-proxy"]
    },
    "docker": {
      "v1Enabled": false,
      "forceBasicAuth": true,
      "httpPort": 8083
    }
  }'

Dépôt npm

# npm proxy (registry.npmjs.org)
curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/npm/proxy' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "npm-proxy",
    "online": true,
    "storage": {
      "blobStoreName": "bs-npm",
      "strictContentTypeValidation": true
    },
    "proxy": {
      "remoteUrl": "https://registry.npmjs.org",
      "contentMaxAge": 1440,
      "metadataMaxAge": 1440
    },
    "negativeCache": {
      "enabled": true,
      "timeToLive": 15
    },
    "httpClient": {
      "autoBlock": true
    }
  }'

# npm hosted (paquets internes)
curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/npm/hosted' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "npm-hosted",
    "online": true,
    "storage": {
      "blobStoreName": "bs-hosted",
      "strictContentTypeValidation": true,
      "writePolicy": "ALLOW"
    }
  }'

# npm group
curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/npm/group' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "npm-group",
    "online": true,
    "storage": {
      "blobStoreName": "bs-npm",
      "strictContentTypeValidation": true
    },
    "group": {
      "memberNames": ["npm-hosted", "npm-proxy"]
    }
  }'

Dépôt PyPI

# PyPI proxy
curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/pypi/proxy' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "pypi-proxy",
    "online": true,
    "storage": {
      "blobStoreName": "bs-pypi",
      "strictContentTypeValidation": true
    },
    "proxy": {
      "remoteUrl": "https://pypi.org",
      "contentMaxAge": 1440,
      "metadataMaxAge": 1440
    },
    "negativeCache": {
      "enabled": true,
      "timeToLive": 15
    },
    "httpClient": {
      "autoBlock": true
    }
  }'

# PyPI hosted
curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/pypi/hosted' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "pypi-hosted",
    "online": true,
    "storage": {
      "blobStoreName": "bs-hosted",
      "strictContentTypeValidation": true,
      "writePolicy": "ALLOW"
    }
  }'

# PyPI group
curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/pypi/group' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "pypi-group",
    "online": true,
    "storage": {
      "blobStoreName": "bs-pypi",
      "strictContentTypeValidation": true
    },
    "group": {
      "memberNames": ["pypi-hosted", "pypi-proxy"]
    }
  }'

Dépôt Maven

# Maven Central proxy
curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/maven/proxy' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "maven-central-proxy",
    "online": true,
    "storage": {
      "blobStoreName": "bs-maven",
      "strictContentTypeValidation": true
    },
    "proxy": {
      "remoteUrl": "https://repo.maven.apache.org/maven2/",
      "contentMaxAge": 1440,
      "metadataMaxAge": 1440
    },
    "negativeCache": {
      "enabled": true,
      "timeToLive": 15
    },
    "httpClient": {
      "autoBlock": true
    },
    "maven": {
      "versionPolicy": "RELEASE",
      "layoutPolicy": "STRICT"
    }
  }'

# Maven hosted (releases)
curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/maven/hosted' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "maven-hosted-releases",
    "online": true,
    "storage": {
      "blobStoreName": "bs-hosted",
      "strictContentTypeValidation": true,
      "writePolicy": "ALLOW_ONCE"
    },
    "maven": {
      "versionPolicy": "RELEASE",
      "layoutPolicy": "STRICT"
    }
  }'

# Maven hosted (snapshots)
curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/maven/hosted' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "maven-hosted-snapshots",
    "online": true,
    "storage": {
      "blobStoreName": "bs-hosted",
      "strictContentTypeValidation": true,
      "writePolicy": "ALLOW"
    },
    "maven": {
      "versionPolicy": "SNAPSHOT",
      "layoutPolicy": "STRICT"
    }
  }'

# Maven group
curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/maven/group' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "maven-group",
    "online": true,
    "storage": {
      "blobStoreName": "bs-maven",
      "strictContentTypeValidation": true
    },
    "group": {
      "memberNames": [
        "maven-hosted-releases",
        "maven-hosted-snapshots",
        "maven-central-proxy"
      ]
    },
    "maven": {
      "versionPolicy": "MIXED",
      "layoutPolicy": "STRICT"
    }
  }'

Dépôts système (apt et yum)

# apt proxy (Ubuntu)
curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/apt/proxy' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "apt-ubuntu-proxy",
    "online": true,
    "storage": {
      "blobStoreName": "bs-system",
      "strictContentTypeValidation": true
    },
    "proxy": {
      "remoteUrl": "http://archive.ubuntu.com/ubuntu/",
      "contentMaxAge": 1440,
      "metadataMaxAge": 1440
    },
    "negativeCache": {
      "enabled": true,
      "timeToLive": 15
    },
    "httpClient": {
      "autoBlock": true
    },
    "apt": {
      "distribution": "noble",
      "flat": false
    }
  }'

# yum proxy (Rocky Linux)
curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/repositories/yum/proxy' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "yum-rocky-proxy",
    "online": true,
    "storage": {
      "blobStoreName": "bs-system",
      "strictContentTypeValidation": true
    },
    "proxy": {
      "remoteUrl": "https://dl.rockylinux.org/pub/rocky/9/BaseOS/x86_64/os/",
      "contentMaxAge": 1440,
      "metadataMaxAge": 1440
    },
    "negativeCache": {
      "enabled": true,
      "timeToLive": 15
    },
    "httpClient": {
      "autoBlock": true
    }
  }'

Cleanup policies

Configurer les regles de nettoyage pour eviter la croissance indefinie du stockage :

# Creer une cleanup policy (composants non telecharges depuis 90 jours)
curl -u admin:PASSWORD -X POST \
  'https://nexus.internal.company.io/service/rest/v1/cleanup-policies' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "purge-90-days-unused",
    "format": "ALL_FORMATS",
    "notes": "Supprime les composants non telecharges depuis 90 jours",
    "criteria": {
      "lastDownloaded": 90
    }
  }'

Appliquer les cleanup policies aux dépôts proxy uniquement

Ne pas appliquer de cleanup aggressive aux dépôts hosted contenant des artefacts de release. Les releases doivent etre conservees explicitement. Appliquer les policies principalement aux dépôts proxy et aux hosted de snapshots.


Vérification

Apres la configuration, vérifier que chaque dépôt fonctionne :

# Verifier la liste des depots
curl -u admin:PASSWORD \
  'https://nexus.internal.company.io/service/rest/v1/repositories' \
  | python3 -m json.tool | grep '"name"'

# Tester le proxy Docker
docker pull docker.internal.company.io/library/alpine:latest

# Tester le proxy npm
npm --registry=https://nexus.internal.company.io/repository/npm-group/ \
  info lodash version

# Tester le proxy PyPI
pip install --index-url https://nexus.internal.company.io/repository/pypi-group/simple/ \
  --trusted-host nexus.internal.company.io \
  requests --dry-run

# Tester le proxy Maven
mvn dependency:get \
  -DremoteRepositories=https://nexus.internal.company.io/repository/maven-group/ \
  -Dartifact=org.apache.commons:commons-lang3:3.14.0