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.
Минимальные требования к серверу
| ОС | RAM | CPU | Диск | Статический IP |
|---|---|---|---|---|
| Ubuntu 20.04+ или Rocky Linux 8+ | 8 ГБ | 4 ядра | 80 ГБ | Да |
Nexus активно работает с метаданными, поэтому объём RAM и дискового пространства критически важен. На 4 ГБ RAM он запустится, но в реальной эксплуатации будет заметно тормозить.
Если Docker ещё не установлен, сначала установите его — Руководство по установке Docker (opens in a new tab)
- Manual
- Ansible
Ручная установка
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.
- Debian Based
- Red Hat Based
1-> Устанавливаем NGINX и Certbot:
sudo apt update
sudo apt install nginx certbot python3-certbot-nginx -y2-> Создайте A-запись у вашего DNS-провайдера — направьте домен на IP-адрес сервера. Например, привязка субдомена nexus.helm.uz к серверу:
3-> Создаём конфигурационный файл NGINX:
sudo nano /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 nginx6-> Получаем SSL-сертификат от Let's Encrypt:
sudo certbotCertbot запросит ваш 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-репозиторий работает точно так же:
- Ваш проект запрашивает пакет
spring-boot-starter - Nexus сначала ищет его в своём кеше
- Если не находит — скачивает из Maven Central и сохраняет в кеш
- При следующем запросе того же пакета — отдаёт из кеша, не обращаясь в интернет
Результат: Сборка ускоряется, интернет-трафик снижается, а при недоступности внешнего репозитория проект всё равно собирается.
В 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 вашего проекта следующую конфигурацию:
<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 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 платформу:
Gitlab — Settings -> CI/CD -> Variables:
NEXUS_USER — имя пользователя Nexus:
NEXUS_PASSWORD — пароль Nexus:
Github — Settings -> 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:
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:
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:
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:
- 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:
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 в корневом каталоге проекта:
<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 платформу:
Gitlab — Settings -> CI/CD -> Variables:
NEXUS_USER:
NEXUS_PASSWORD:
Github — Settings -> Secrets -> New repository secret:
NEXUS_USER:
NEXUS_PASSWORD:
Шаг 3: Обновление Dockerfile
Чтобы .NET SDK в процессе сборки использовал NuGet.Config, добавьте в Dockerfile:
COPY NuGet.Config /root/.nuget/NuGet/NuGet.ConfigПолный 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:
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:
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:
- 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:
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:
Заключение
В этом руководстве мы:
- Установили Nexus с помощью Docker (ручным способом и через Ansible)
- Привязали домен и HTTPS (NGINX + Let's Encrypt)
- Разобрались с типами репозиториев (Proxy, Hosted, Group)
- Интегрировали проекты Maven и NuGet с Nexus
- Настроили использование Nexus в пайплайнах Gitlab CI и Github Actions
В качестве следующего шага вы можете создать в Nexus репозитории для Docker, PyPI, Go, Cargo, Helm и других технологий и подключить к ним свои проекты — логика та же, отличается лишь формат конфигурации.
Дополнительные ресурсы
Дополнительные ресурсы
- Развёртывание Java Spring Boot: GitLab CI и GitHub Actions (opens in a new tab)
- Установка и настройка GitLab Server (opens in a new tab)
- GitLab CI | Релизы и интеграции (opens in a new tab)
- Flutter CI/CD с GitHub Actions (opens in a new tab)
- GitHub Actions CI/CD (opens in a new tab)
- Установка Jenkins на Linux-серверы (opens in a new tab)
- От кода до сервера: Docker CI/CD с Jenkins и интеграция с Discord (opens in a new tab)
- Kubernetes CI/CD | GitHub Actions + Argo CD | GitOps (opens in a new tab)
Дата: 2025.02.08 (8 февраля 2025)
Последнее обновление: 2026.02.12 (12 февраля 2026)
Автор: Otabek Ismoilov
| Telegram (opens in a new tab) | GitHub (opens in a new tab) | LinkedIn (opens in a new tab) |
|---|