Skip to content
Документация
Sonatype Nexus

Sonatype Nexus Repository Manager

Введение

Представьте: ваша команда каждый день при сборке проекта заново скачивает сотни пакетов из Maven Central, NuGet Gallery или Docker Hub. Каждая сборка отправляет запросы в интернет, тратит время, нагружает сеть. А если интернет работает медленно или внешний репозиторий временно недоступен — весь CI/CD пайплайн встаёт.

Именно эту проблему решает Sonatype Nexus Repository Manager.

Nexus — это централизованный менеджер артефактов, который выполняет следующие задачи:

  • Кеширование внешних пакетов (Proxy) — сохраняет на локальном сервере пакеты, загруженные из внешних репозиториев: Maven Central, NuGet, PyPI, Docker Hub и других. При повторном запросе того же пакета он берётся из Nexus, а не из интернета.
  • Хранение внутренних пакетов (Hosted) — позволяет безопасно хранить собственные библиотеки и артефакты компании.
  • Объединение нескольких репозиториев (Group) — объединяет Proxy и Hosted репозитории под одним URL. Вашим проектам достаточно знать только один адрес.

Nexus поддерживает Java (Maven, Gradle), .NET (NuGet), Python (PyPI), Docker, Node.js (NPM), Go, Helm, Cargo и множество других технологий.

Nexus доступен в двух версиях: Nexus OSS (open source, бесплатный) и Nexus Pro (платный, с дополнительными возможностями). Данное руководство ориентировано на работу с Nexus OSS — в большинстве случаев его функциональности вполне достаточно.

Краткая история: Nexus был создан в 2007 году компанией Sonatype Inc. Изначально он был рассчитан только на Maven, однако сегодня превратился в универсальное решение, поддерживающее практически все популярные менеджеры пакетов.

В этом руководстве мы установим Nexus с помощью Docker, привяжем к нему домен, разберёмся с типами репозиториев и в завершение интегрируем проекты на Maven и NuGet с Nexus в рамках CI/CD пайплайнов.

Установка Nexus

Nexus будем запускать через Docker. Рассмотрим два варианта установки: ручной и автоматизированный с помощью Ansible.

Минимальные требования к серверу

ОСRAMCPUДискСтатический IP
Ubuntu 20.04+ или Rocky Linux 8+8 ГБ4 ядра80 ГБДа

Nexus активно работает с метаданными, поэтому объём RAM и дискового пространства критически важен. На 4 ГБ RAM он запустится, но в реальной эксплуатации будет заметно тормозить.

Если Docker ещё не установлен, сначала установите его — Руководство по установке Docker (opens in a new tab)

Ручная установка

1-> Nexus хранит свои данные (репозитории, конфигурации, кешированные пакеты) в каталоге /nexus-data. Если Docker-контейнер будет удалён или перезапущен, данные внутри него будут потеряны. Поэтому мы монтируем этот каталог на хост-сервер:

sudo mkdir -p /mnt/nexus/nexus-data
sudo chown -R 200 /mnt/nexus/nexus-data

Почему chown 200? Внутри контейнера Nexus работает под пользователем nexus с UID 200. Если владелец каталога не соответствует этому UID, Nexus не сможет запуститься из-за отсутствия прав на запись.

2-> Запускаем Nexus через Docker:

docker run -d \
  -p 8081:8081 \
  --name nexus \
  --restart=always \
  -v /mnt/nexus/nexus-data:/nexus-data \
  sonatype/nexus3

Здесь:

  • -p 8081:8081 — порт для веб-интерфейса Nexus
  • --restart=always — автоматический перезапуск Nexus при рестарте сервера или Docker
  • -v /mnt/nexus/nexus-data:/nexus-data — данные сохраняются на хост-сервере

3-> При первом запуске Nexus автоматически генерирует пароль для admin. Чтобы его получить:

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

Этот пароль предназначен только для первого входа! Nexus потребует его сменить. Сохраните пароль в надёжном месте.

Первый вход

После установки Nexus откройте в браузере http://server_ip:8081.

Нажмите кнопку Sign in в правом верхнем углу:

Войдите с логином admin и паролем, полученным на предыдущем шаге:

Nexus потребует сменить пароль по умолчанию — задайте новый надёжный пароль:

На следующем шаге настройте анонимный доступ. Если доступ должен быть только для аутентифицированных пользователей, выберите «Disable anonymous access»:

Поздравляем! Nexus готов к работе:

Привязка домена к Nexus

В production-среде Nexus следует использовать не по IP:порт, а через домен с HTTPS. Это важно как для безопасности, так и для удобства — особенно чтобы credentials в CI/CD пайплайнах не передавались в открытом виде.

Мы настроим NGINX в качестве reverse proxy и получим бесплатный SSL-сертификат от Let's Encrypt.

1-> Устанавливаем NGINX и Certbot:

sudo apt update
sudo apt install nginx certbot python3-certbot-nginx -y

2-> Создайте A-запись у вашего DNS-провайдера — направьте домен на IP-адрес сервера. Например, привязка субдомена nexus.helm.uz к серверу:

3-> Создаём конфигурационный файл NGINX:

sudo nano /etc/nginx/sites-available/nexus.helm.uz

Запишите следующую конфигурацию:

/etc/nginx/sites-available/nexus.helm.uz
server {
    listen 80;
    client_max_body_size 100M;
    server_name nexus.helm.uz;
    location / {
        proxy_pass http://localhost: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;
    }
}

client_max_body_size 100M — задаёт максимальный размер файлов, загружаемых через NGINX. Для загрузки крупных артефактов в Nexus это значение может потребоваться увеличить. Если возникает ошибка 413 Request Entity Too Large, увеличьте этот параметр.

4-> Для активации конфигурации создаём символическую ссылку в sites-enabled:

sudo ln -s /etc/nginx/sites-available/nexus.helm.uz /etc/nginx/sites-enabled/

5-> Перезапускаем NGINX:

sudo systemctl restart nginx

6-> Получаем SSL-сертификат от Let's Encrypt:

sudo certbot

Certbot запросит ваш email и предложит принять Terms of Service — нажмите y:

Затем выберите нужный домен из списка:

После получения сертификата перезапустите NGINX:

sudo systemctl restart nginx

Теперь, открыв в браузере https://nexus.helm.uz (opens in a new tab), вы увидите, что Nexus работает по HTTPS:

Знакомство с Nexus

Давайте разберёмся с основными концепциями и интерфейсом Nexus.

Поддерживаемые технологии

Nexus поддерживает практически все популярные менеджеры пакетов:

APT, Cargo, Bower, CocoaPods, Composer, Conan, Conda, Docker, Git LFS, Go, Helm, Hugging Face, Maven, npm, NuGet, p2, PyPI, R, Raw, RubyGems, Yum (RPM)

Помимо Nexus, компания Sonatype также предлагает продукты: Repository Firewall (блокировка вредоносных пакетов), Lifecycle (контроль безопасности open source) и SBOM (отслеживание состава программного обеспечения).

Знакомство с интерфейсом

При входе в Nexus открывается главная страница:

Server Administration and Configuration — здесь находятся все настройки Nexus:

Blob Stores — физическое хранилище файлов Nexus. По умолчанию существует один blob store с именем default. В крупных организациях рекомендуется создавать отдельные blob store для каждого типа репозитория:

Repositories — здесь можно просматривать все репозитории. По умолчанию Nexus поставляется с готовыми репозиториями для Maven и NuGet:

Настройка прокси

Если в вашей компании интернет работает через прокси, необходимо настроить прокси и для Nexus. Перейдите в Settings -> System -> HTTP:

Для HTTP Proxy отметьте Enable HTTP Proxy и укажите адрес и порт прокси-сервера:

Для HTTPS Proxy — аналогично. Если прокси требует аутентификацию, отметьте Enable HTTPS Proxy Authentication и введите логин с паролем:

No Proxy Hosts — здесь указываются локальные адреса, для которых проксирование не требуется (например, внутренние сервисы):

Работа с репозиториями

Ключевое понятие в Nexus — типы репозиториев. Их правильное понимание критически важно для эффективного использования Nexus.

Proxy Repository — "Умный кеш"

Proxy-репозиторий кеширует внешние (remote) репозитории на локальном сервере.

Аналогия из жизни: Представьте, что вы каждый день ходите в магазин за хлебом. Однажды у подъезда открывается мини-маркет — и теперь вам не нужно идти далеко, вы покупаете хлеб прямо рядом с домом. Proxy-репозиторий работает точно так же:

  1. Ваш проект запрашивает пакет spring-boot-starter
  2. Nexus сначала ищет его в своём кеше
  3. Если не находит — скачивает из Maven Central и сохраняет в кеш
  4. При следующем запросе того же пакета — отдаёт из кеша, не обращаясь в интернет

Результат: Сборка ускоряется, интернет-трафик снижается, а при недоступности внешнего репозитория проект всё равно собирается.

В Nexus по умолчанию есть proxy-репозиторий maven-central — он получает пакеты с https://repo1.maven.org/maven2/ (opens in a new tab):

Hosted Repository — "Внутреннее хранилище"

Hosted-репозиторий предназначен для хранения собственных пакетов компании. Он не связан с внешним миром и работает исключительно внутри вашей организации.

Когда он нужен?

  • В компании разработана общая библиотека (shared library), и все команды должны её использовать
  • Необходимо централизованно хранить SNAPSHOT или RELEASE версии проекта
  • Для загрузки артефактов командами mvn deploy или dotnet nuget push

По умолчанию в Nexus уже созданы hosted-репозитории maven-releases и maven-snapshots.

Group Repository — "Единая точка входа"

Group-репозиторий объединяет несколько репозиториев под одним URL.

Аналогия из жизни: В торговом центре 50 магазинов, но вы заходите через одну дверь. Group-репозиторий устроен так же — ваш проект знает только один URL, а за ним работает сразу несколько репозиториев.

Например, group-репозиторий maven-public включает в себя:

  • maven-central (proxy) — внешние пакеты
  • maven-releases (hosted) — релизные пакеты компании
  • maven-snapshots (hosted) — snapshot-пакеты компании

Вашему проекту достаточно указать URL maven-public — всё остальное Nexus разрешит самостоятельно.

Репозитории внутри maven-public:

Аналогично nuget-group объединяет репозитории nuget-hosted и nuget.org-proxy:

nuget.org-proxy кеширует NuGet-пакеты с внешнего адреса https://api.nuget.org/v3/index.json (opens in a new tab):

Итого: По умолчанию в Nexus есть репозитории только для Maven и NuGet. Для Docker, PyPI, Go, Cargo, Helm и других технологий репозитории необходимо создавать самостоятельно — через Settings -> Repositories -> Create Repository.

Интеграция с Nexus

Переходим к самой интересной части — подключению Nexus к реальным проектам и CI/CD пайплайнам. Рассмотрим интеграцию для Java (Maven) и .NET (NuGet).

Что даёт интеграция?

  • Ускорение сборки — пакеты берутся из локального Nexus, нет ожидания интернета
  • Стабильность — сборка работает даже при недоступности внешнего репозитория
  • Безопасность — все пакеты проходят через Nexus, что упрощает контроль
  • Экономия трафика — каждый пакет скачивается из интернета только один раз

Java Maven

На нашей платформе в практикуме Развёртывание Java Spring Boot: GitLab CI и GitHub Actions (opens in a new tab) мы рассматривали запуск Spring Boot Maven-проекта через CI/CD. Теперь добавим к этому проекту интеграцию с Nexus.

Проекты, используемые в этом разделе:

Шаг 1: Добавление репозитория в pom.xml

Добавьте в pom.xml вашего проекта следующую конфигурацию:

pom.xml
<repositories>
    <repository>
        <id>nexus</id>
        <url>https://nexus.helm.uz/repository/maven-public/</url>
        <releases>
            <enabled>true</enabled>
        </releases>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>
 
<pluginRepositories>
    <pluginRepository>
        <id>nexus</id>
        <url>https://nexus.helm.uz/repository/maven-public/</url>
        <releases>
            <enabled>true</enabled>
        </releases>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </pluginRepository>
</pluginRepositories>

Здесь два раздела:

  • <repositories> — указывает Maven, откуда загружать зависимости. Через group-репозиторий maven-public доступны как внешние пакеты (Maven Central), так и внутренние.
  • <pluginRepositories> — обеспечивает загрузку Maven-плагинов также через Nexus. Это важно, поскольку плагины Maven (compiler, surefire, jar) тоже скачиваются из внешних репозиториев.

Значение <id> (nexus) должно совпадать с ID сервера в settings.xml из следующего шага — Maven сопоставляет аутентификацию по ID.

Шаг 2: Создание settings.xml

settings.xml — глобальный конфигурационный файл Maven. Здесь задаются аутентификация, зеркала и настройки профилей:

settings.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
 
    <servers>
        <server>
            <id>nexus</id>
            <username>${env.NEXUS_USER}</username>
            <password>${env.NEXUS_PASSWORD}</password>
        </server>
    </servers>
 
    <mirrors>
        <mirror>
            <id>nexus</id>
            <mirrorOf>*</mirrorOf>
            <url>https://nexus.helm.uz/repository/maven-public/</url>
            <layout>default</layout>
        </mirror>
    </mirrors>
 
    <profiles>
        <profile>
            <id>nexus</id>
            <repositories>
                <repository>
                    <id>nexus</id>
                    <url>https://nexus.helm.uz/repository/maven-public/</url>
                    <releases>
                        <enabled>true</enabled>
                    </releases>
                    <snapshots>
                        <enabled>true</enabled>
                    </snapshots>
                </repository>
            </repositories>
        </profile>
    </profiles>
 
    <activeProfiles>
        <activeProfile>nexus</activeProfile>
    </activeProfiles>
</settings>

Разберём каждый раздел:

<servers> — учётные данные для доступа к Nexus. ${env.NEXUS_USER} и ${env.NEXUS_PASSWORD} берутся из переменных окружения — это безопасный подход для CI/CD, при котором пароли не хранятся в коде.

<mirrors><mirrorOf>*</mirrorOf> означает «использовать Nexus как зеркало для всех репозиториев». То есть вместо прямых обращений к Maven Central или другим внешним репозиториям все запросы идут через Nexus.

<profiles> и <activeProfiles> — создаётся и активируется профиль nexus. Это гарантирует, что Maven будет использовать репозиторий Nexus при выполнении любой команды mvn.

Шаг 3: Настройка CI/CD переменных окружения

Добавьте NEXUS_USER и NEXUS_PASSWORD в вашу CI/CD платформу:

GitlabSettings -> CI/CD -> Variables:

NEXUS_USER — имя пользователя Nexus:

NEXUS_PASSWORD — пароль Nexus:

GithubSettings -> Secrets -> New repository secret:

NEXUS_USER:

NEXUS_PASSWORD:

Вопрос безопасности: Рекомендуется создать в Nexus отдельного пользователя с правами «только чтение». Не используйте пароль admin в CI/CD — если credentials утекут, злоумышленник получит полный контроль над Nexus.

Шаг 4: Обновление Dockerfile

Чтобы Maven в процессе Docker-сборки знал о settings.xml, добавьте в Dockerfile следующую строку:

COPY settings.xml /root/.m2/settings.xml

Полный Dockerfile:

Dockerfile
FROM maven:3.9.5-eclipse-temurin-17-alpine AS builder
WORKDIR /app
COPY settings.xml /root/.m2/settings.xml
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
 
FROM eclipse-temurin:17-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD wget --spider --quiet http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]

mvn dependency:go-offline -B — эта команда предварительно скачивает все зависимости. Благодаря кешированию Docker-слоёв, если pom.xml не изменился, этот шаг будет пропущен при последующих сборках, что значительно ускоряет процесс.

Шаг 5: Обновление CI/CD пайплайна

В CI/CD пайплайне необходимо заменить placeholder-ы в settings.xml на реальные значения. Для этого используем команду sed.

Gitlab CI — добавьте в секцию before_script:

.gitlab-ci.yml
  before_script:
  - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  - sed -i "s|\${env.NEXUS_USER}|${NEXUS_USER}|g" settings.xml
  - sed -i "s|\${env.NEXUS_PASSWORD}|${NEXUS_PASSWORD}|g" settings.xml

Полный .gitlab-ci.yml:

.gitlab-ci.yml
stages:
  - build_and_push
  - deploy
 
variables:
  IMAGE_NAME: waifulist
  CONTAINER_NAME: waifulist
  PORT: "8080:8080"
  REPO_NAME: $CI_PROJECT_PATH
  REGISTRY: "registry.gitlab.com"
  SSH_HOST: $SERVER_IP
  SSH_USER: $SERVER_USERNAME
  SSH_KEY: $SSH_PRIVATE_KEY
  SPRING_PROFILES_ACTIVE: dev
  DEV_DATABASE_URL: $DEV_DATABASE_URL
  DEV_DATABASE_USERNAME: $DEV_DATABASE_USERNAME
  DEV_DATABASE_PASSWORD: $DEV_DATABASE_PASSWORD
 
build_and_push:
  stage: build_and_push
  image: docker:stable
 
  services:
    - docker:dind
 
  before_script:
  - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  - sed -i "s|\${env.NEXUS_USER}|${NEXUS_USER}|g" settings.xml
  - sed -i "s|\${env.NEXUS_PASSWORD}|${NEXUS_PASSWORD}|g" settings.xml
 
  script:
    - docker build -t "$REGISTRY/$REPO_NAME/$IMAGE_NAME:$CI_COMMIT_SHA" .
    - docker push "$REGISTRY/$REPO_NAME/$IMAGE_NAME:$CI_COMMIT_SHA"
 
deploy:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --update --no-cache openssh-client
 
  script:
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-keyscan -H $SSH_HOST >> ~/.ssh/known_hosts
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker pull $REGISTRY/$REPO_NAME/$IMAGE_NAME:$CI_COMMIT_SHA"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker stop $CONTAINER_NAME || true"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker rm $CONTAINER_NAME || true"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no "$SSH_USER@$SSH_HOST" "docker run -d --name $CONTAINER_NAME --restart=always -p $PORT -e SPRING_PROFILES_ACTIVE=$SPRING_PROFILES_ACTIVE -e DEV_DATABASE_URL=$DEV_DATABASE_URL -e DEV_DATABASE_USERNAME=$DEV_DATABASE_USERNAME -e DEV_DATABASE_PASSWORD=$DEV_DATABASE_PASSWORD $REGISTRY/$REPO_NAME/$IMAGE_NAME:$CI_COMMIT_SHA"

Github Actions — добавьте новый step:

.github/workflows/main.yml
- name: Replace Nexus Credentials in settings.xml
  run: |
    sed -i "s|\${env.NEXUS_USER}|${{ secrets.NEXUS_USER }}|g" settings.xml
    sed -i "s|\${env.NEXUS_PASSWORD}|${{ secrets.NEXUS_PASSWORD }}|g" settings.xml

Полный .github/workflows/main.yml:

.github/workflows/main.yml
name: Github Actions CI/CD
 
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
 
env:
  REPO_NAME: ${{ github.repository }}
  CONTAINER_NAME: waifulist
  REGISTRY: ghcr.io
  SSH_HOST: ${{ secrets.SERVER_IP }}
  SSH_USER: ${{ secrets.SERVER_USERNAME }}
  SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
  PORT: 8080:8080
  SPRING_PROFILES_ACTIVE: dev
 
jobs:
  build_and_push:
    runs-on: ubuntu-latest
    permissions:
        contents: read
        packages: write
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
 
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
 
      - name: Replace Nexus Credentials in settings.xml
        run: |
          sed -i "s|\${env.NEXUS_USER}|${{ secrets.NEXUS_USER }}|g" settings.xml
          sed -i "s|\${env.NEXUS_PASSWORD}|${{ secrets.NEXUS_PASSWORD }}|g" settings.xml
 
      - name: Log in to the Container registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: "${{ env.REGISTRY }}/${{ env.REPO_NAME }}:${{ github.sha }}"
          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.REPO_NAME }}:buildcache
          cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.REPO_NAME }}:buildcache,mode=max
 
  deploy:
    needs: build_and_push
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Executing remote SSH commands to deploy
        uses: appleboy/ssh-action@master
        with:
          host: "${{ env.SSH_HOST }}"
          username: "${{ env.SSH_USER }}"
          key: "${{ env.SSH_KEY }}"
          script: |
            echo ${{ secrets.GITHUB_TOKEN }} | docker login -u ${{ github.actor }} --password-stdin ${{ env.REGISTRY }}
            docker pull "${{ env.REGISTRY }}/${{ env.REPO_NAME }}:${{ github.sha }}"
            docker stop "${{ env.CONTAINER_NAME }}" || true
            docker rm "${{ env.CONTAINER_NAME }}" || true
            docker run -d --name ${{ env.CONTAINER_NAME }} --restart=always -p ${{ env.PORT }} \
            -e SPRING_PROFILES_ACTIVE=${{ env.SPRING_PROFILES_ACTIVE }} \
            -e DEV_DATABASE_URL=${{ secrets.DEV_DATABASE_URL }} \
            -e DEV_DATABASE_USERNAME=${{ secrets.DEV_DATABASE_USERNAME }} \
            -e DEV_DATABASE_PASSWORD=${{ secrets.DEV_DATABASE_PASSWORD }} \
            ${{ env.REGISTRY }}/${{ env.REPO_NAME }}:${{ github.sha }}

Шаг 6: Проверка результата

После запуска CI/CD пайплайна в логах вы увидите, что все пакеты загружаются через nexus.helm.uz:

Результат Gitlab CI:

Результат Github Actions:

Кешированные пакеты можно увидеть и в веб-интерфейсе Nexus:

Локальные пакеты, сохранённые в репозитории maven-central:

Обратите внимание: Первая сборка может занять больше времени, чем обычно, — поскольку Nexus при первом обращении скачивает все пакеты из интернета и сохраняет их в кеш. Все последующие сборки будут значительно быстрее, так как всё берётся из локального кеша.

.NET NuGet

В проектах на .NET используется менеджер пакетов NuGet. Логика интеграции с Nexus аналогична Maven — отличия только в конфигурационных файлах.

В этом разделе мы возьмём .NET Core-приложение из практикума GitHub Actions CI/CD (opens in a new tab) и добавим к нему интеграцию с Nexus.

Шаг 1: Создание NuGet.Config

Создайте файл NuGet.Config в корневом каталоге проекта:

NuGet.Config
<configuration>
  <packageSources>
    <clear />
    <add key="Nexus" value="https://nexus.helm.uz/repository/nuget-group/index.json" />
  </packageSources>
  <packageSourceCredentials>
    <Nexus>
      <add key="Username" value="__NEXUS_USER__" />
      <add key="ClearTextPassword" value="__NEXUS_PASSWORD__" />
    </Nexus>
  </packageSourceCredentials>
</configuration>

Что здесь происходит:

  • <clear /> — отключает стандартные источники NuGet (nuget.org). Теперь пакеты загружаются исключительно через Nexus.
  • nuget-group — аналогично maven-public в Maven, это group-репозиторий, объединяющий nuget-hosted и nuget.org-proxy.
  • __NEXUS_USER__ и __NEXUS_PASSWORD__ — это placeholder-ы. В CI/CD пайплайне они будут заменены реальными значениями.

Шаг 2: Настройка CI/CD переменных окружения

Аналогично шагу 3 из раздела Maven — добавьте NEXUS_USER и NEXUS_PASSWORD в вашу CI/CD платформу:

GitlabSettings -> CI/CD -> Variables:

NEXUS_USER:

NEXUS_PASSWORD:

GithubSettings -> Secrets -> New repository secret:

NEXUS_USER:

NEXUS_PASSWORD:

Шаг 3: Обновление Dockerfile

Чтобы .NET SDK в процессе сборки использовал NuGet.Config, добавьте в Dockerfile:

COPY NuGet.Config /root/.nuget/NuGet/NuGet.Config

Полный Dockerfile:

Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env
WORKDIR /app
 
COPY NuGet.Config /root/.nuget/NuGet/NuGet.Config
COPY . .
WORKDIR "/app/GitHub.Actions.API"
RUN dotnet publish "GitHub.Actions.API.csproj" -o /app/build -c Release
 
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /app
ENV ASPNETCORE_ENVIRONMENT=Development
ENV TZ="Asia/Tashkent"
COPY --from=build-env /app/build .
ENTRYPOINT ["dotnet", "GitHub.Actions.API.dll", "--urls=http://0.0.0.0:4001"]

Шаг 4: Обновление CI/CD пайплайна

Gitlab CI — добавьте замену placeholder-ов в секцию before_script:

.gitlab-ci.yml
before_script:
  - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  - sed -i 's|__NEXUS_USER__|'"$NEXUS_USER"'|g' NuGet.Config
  - sed -i 's|__NEXUS_PASSWORD__|'"$NEXUS_PASSWORD"'|g' NuGet.Config

Полный .gitlab-ci.yml:

.gitlab-ci.yml
stages:
  - build_and_push
  - deploy
 
variables:
  API_IMAGE_NAME: github-api
  API_CONTAINER_NAME: github-api
  UI_IMAGE_NAME: github-ui
  UI_CONTAINER_NAME: github-ui
  UI_PORT: "4000:4000"
  API_PORT: "4001:4001"
  REPO_NAME: $CI_PROJECT_PATH
  REGISTRY: "registry.gitlab.com"
  SSH_HOST: $SERVER_IP
  SSH_USER: $SERVER_USERNAME
  SSH_KEY: $SSH_PRIVATE_KEY
 
build_and_push:
  stage: build_and_push
  image: docker:stable
 
  services:
    - docker:dind
 
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - sed -i 's|__NEXUS_USER__|'"$NEXUS_USER"'|g' NuGet.Config
    - sed -i 's|__NEXUS_PASSWORD__|'"$NEXUS_PASSWORD"'|g' NuGet.Config
 
  script:
    - docker build -t $REGISTRY/$REPO_NAME/$API_IMAGE_NAME:$CI_COMMIT_SHA -f ./API.Dockerfile .
    - docker push $REGISTRY/$REPO_NAME/$API_IMAGE_NAME:$CI_COMMIT_SHA
    - docker build -t $REGISTRY/$REPO_NAME/$UI_IMAGE_NAME:$CI_COMMIT_SHA -f ./UI.Dockerfile .
    - docker push $REGISTRY/$REPO_NAME/$UI_IMAGE_NAME:$CI_COMMIT_SHA
 
deploy:
  stage: deploy
  image: alpine:latest
 
  before_script:
    - apk add --update --no-cache openssh-client
 
  script:
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-keyscan -H $SSH_HOST >> ~/.ssh/known_hosts
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker pull $REGISTRY/$REPO_NAME/$API_IMAGE_NAME:$CI_COMMIT_SHA"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker pull $REGISTRY/$REPO_NAME/$UI_IMAGE_NAME:$CI_COMMIT_SHA"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker stop $UI_CONTAINER_NAME || true"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker stop $API_CONTAINER_NAME || true"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker rm $UI_CONTAINER_NAME || true"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker rm $API_CONTAINER_NAME || true"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker run -d --name $API_CONTAINER_NAME --restart=always -p $API_PORT $REGISTRY/$REPO_NAME/$API_IMAGE_NAME:$CI_COMMIT_SHA"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker run -d --name $UI_CONTAINER_NAME --restart=always -p $UI_PORT $REGISTRY/$REPO_NAME/$UI_IMAGE_NAME:$CI_COMMIT_SHA"

Github Actions — добавьте новый step:

.github/workflows/ci-cd.yml
- name: Replace Nexus Credentials in NuGet.Config
  run: |
    sed -i "s|__NEXUS_USER__|${{ secrets.NEXUS_USER }}|g" NuGet.Config
    sed -i "s|__NEXUS_PASSWORD__|${{ secrets.NEXUS_PASSWORD }}|g" NuGet.Config

Полный .github/workflows/ci-cd.yml:

.github/workflows/ci-cd.yml
name: Docker CI/CD
 
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
 
env:
  API_IMAGE_NAME: github-api
  API_CONTAINER_NAME: github-api
  UI_IMAGE_NAME: github-ui
  UI_CONTAINER_NAME: github-ui
  UI_PORT: 4000:4000
  API_PORT: 4001:4001
 
  REPO_NAME: ${{ github.repository }}
  REGISTRY: ghcr.io
  SSH_HOST: ${{ secrets.SERVER_IP }}
  SSH_USER: ${{ secrets.SERVER_USERNAME }}
  SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
 
jobs:
  build_and_push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
 
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
 
      - name: Log in to the Container registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Replace Nexus Credentials in NuGet.Config
        run: |
          sed -i "s|__NEXUS_USER__|${{ secrets.NEXUS_USER }}|g" NuGet.Config
          sed -i "s|__NEXUS_PASSWORD__|${{ secrets.NEXUS_PASSWORD }}|g" NuGet.Config
 
      - name: Build and push API Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./API.Dockerfile
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.REPO_NAME}}/${{ env.API_IMAGE_NAME }}:${{ github.sha }}
          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.REPO_NAME}}/${{ env.API_IMAGE_NAME }}:buildcache
          cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.REPO_NAME}}/${{ env.API_IMAGE_NAME }}:buildcache,mode=max
 
      - name: Build and push UI Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./UI.Dockerfile
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.REPO_NAME}}/${{ env.UI_IMAGE_NAME }}:${{ github.sha }}
          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.REPO_NAME}}/${{ env.UI_IMAGE_NAME }}:buildcache
          cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.REPO_NAME}}/${{ env.UI_IMAGE_NAME }}:buildcache,mode=max
 
  deploy:
    needs: build_and_push
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Executing remote SSH commands to deploy
        uses: appleboy/ssh-action@master
        with:
          host: "${{ env.SSH_HOST }}"
          username: "${{ env.SSH_USER }}"
          key: "${{ env.SSH_KEY }}"
          script: |
            echo ${{ secrets.GITHUB_TOKEN }} | docker login -u ${{ github.actor }} --password-stdin ${{ env.REGISTRY }}
            docker pull "${{ env.REGISTRY }}/${{ env.REPO_NAME}}/${{ env.API_IMAGE_NAME }}:${{ github.sha }}"
            docker pull "${{ env.REGISTRY }}/${{ env.REPO_NAME}}/${{ env.UI_IMAGE_NAME }}:${{ github.sha }}"
            docker stop "${{ env.UI_CONTAINER_NAME }}" || true
            docker stop "${{ env.API_CONTAINER_NAME }}" || true
            docker rm "${{ env.UI_CONTAINER_NAME }}" || true
            docker rm "${{ env.API_CONTAINER_NAME }}" || true
            docker run -d --name "${{ env.API_CONTAINER_NAME }}" --restart=always -p "${{ env.API_PORT }}" "${{ env.REGISTRY }}/${{ env.REPO_NAME}}/${{ env.API_IMAGE_NAME }}:${{ github.sha }}"
            docker run -d --name "${{ env.UI_CONTAINER_NAME }}" --restart=always -p "${{ env.UI_PORT }}" "${{ env.REGISTRY }}/${{ env.REPO_NAME}}/${{ env.UI_IMAGE_NAME }}:${{ github.sha }}"

Шаг 5: Проверка результата

После успешного завершения Github Actions:

Кешированные NuGet-пакеты видны в интерфейсе Nexus:

Пакеты внутри репозитория nuget-group:

Заключение

В этом руководстве мы:

  1. Установили Nexus с помощью Docker (ручным способом и через Ansible)
  2. Привязали домен и HTTPS (NGINX + Let's Encrypt)
  3. Разобрались с типами репозиториев (Proxy, Hosted, Group)
  4. Интегрировали проекты Maven и NuGet с Nexus
  5. Настроили использование Nexus в пайплайнах Gitlab CI и Github Actions

В качестве следующего шага вы можете создать в Nexus репозитории для Docker, PyPI, Go, Cargo, Helm и других технологий и подключить к ним свои проекты — логика та же, отличается лишь формат конфигурации.

Дополнительные ресурсы