Вам потрібен внутрішній репозиторій Python‑пакетів, щоб команди могли швидко ставити і публікувати колісця без викладання в публічний PyPI? Тоді devpi — саме те. Я, Kernelka, покажу як розгорнути його на сервер Linux з systemd і Nginx, щоб усе працювало стабільно, безпечно і зручненько 🚀

Що таке devpi і навіщо

devpi — це приватний PyPI з вбудованим кешем публічного PyPI, керуванням користувачами та індексами. Ви отримуєте:

  • внутрішнє сховище для власних пакетів;
  • дзеркало публічного PyPI для швидкості та надійності;
  • веб‑інтерфейс (через плагін devpi-web) і API для автоматизації;
  • просту інтеграцію з Nginx і systemd.

Це класичний кейс для сервер Linux в компанії або лабораторії, де доступ до Інтернету обмежений або потрібен контроль над залежностями.

Підготовка оточення

Встановлення devpi

Нижче — універсальний спосіб через віртуальне оточення Python в Linux (пакети системи не чіпаємо):

# 1) Створюємо системного користувача та каталоги
sudo useradd --system --create-home --home-dir /srv/devpi --shell /usr/sbin/nologin devpi
sudo install -d -o devpi -g devpi -m 0750 /srv/devpi /srv/devpi/data

# 2) Ставимо devpi в окремий venv
sudo -u devpi python3 -m venv /srv/devpi/venv
sudo -u devpi /srv/devpi/venv/bin/pip install --upgrade pip
sudo -u devpi /srv/devpi/venv/bin/pip install 'devpi-server[threading]' devpi-client devpi-web

# 3) Початкова ініціалізація сховища
sudo -u devpi /srv/devpi/venv/bin/devpi-init --serverdir /srv/devpi/data

Початкове налаштування користувача та індексу

# Тимчасово стартуємо сервер локально (для первинної конфігурації)
sudo -u devpi /srv/devpi/venv/bin/devpi-server \
  --serverdir /srv/devpi/data --host 127.0.0.1 --port 3141 &

# Налаштовуємо root пароль і створюємо окремого користувача та індекс
sudo -u devpi /srv/devpi/venv/bin/devpi use http://127.0.0.1:3141
sudo -u devpi /srv/devpi/venv/bin/devpi login root --password ''
sudo -u devpi /srv/devpi/venv/bin/devpi user -m root password 'StrongPass!'
sudo -u devpi /srv/devpi/venv/bin/devpi user -c company password 'S3cret!'
sudo -u devpi /srv/devpi/venv/bin/devpi login company --password 'S3cret!'
sudo -u devpi /srv/devpi/venv/bin/devpi index -c company/dev bases='root/pypi' volatile=false

# Зупиняємо тимчасовий процес devpi-server (якщо він у фоні)
pkill -f 'devpi-server.*3141' || true

Запуск через systemd

Сервіс під systemd дасть автозапуск, рестарт при збої і логування. Це якраз той випадок, де згодиться практика cron та systemd timers для резервних завдань.

# Створюємо юніт /etc/systemd/system/devpi.service (права root)
cat <<'EOF' | sudo tee /etc/systemd/system/devpi.service >/dev/null
[Unit]
Description=devpi private PyPI
After=network-online.target
Wants=network-online.target

[Service]
User=devpi
Group=devpi
ExecStart=/srv/devpi/venv/bin/devpi-server --serverdir /srv/devpi/data \
  --host 127.0.0.1 --port 3141 --threads 4
Restart=on-failure
Environment=PYTHONUNBUFFERED=1

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now devpi
sudo systemctl status devpi --no-pager -l

Реверс‑проксі через Nginx

Nginx додасть TLS, ліміти розміру завантажень і зручний домен. Ось базове nginx налаштування:

# Конфіг /etc/nginx/sites-available/devpi.conf
sudo tee /etc/nginx/sites-available/devpi.conf >/dev/null <<'EOF'
server {
  listen 80;
  server_name pypi.example.lan;
  client_max_body_size 100M;

  location / {
    proxy_pass http://127.0.0.1:3141;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 300;
  }
}
EOF

# Активація сайту
sudo ln -s /etc/nginx/sites-available/devpi.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Для внутрішніх доменів зазвичай достатньо HTTP, але краще додати TLS (власний корпоративний сертифікат або ACME).

Використання: інсталяція та публікація пакетів

Налаштування pip для інсталяції

# Створюємо конфіг користувача pip на робочих станціях
mkdir -p ~/.config/pip
cat <<'EOF' > ~/.config/pip/pip.conf
[global]
index-url = https://pypi.example.lan/company/dev/+simple/
EOF

# Перевірка інсталяції пакета з вашого індексу
python3 -m pip install requests

Якщо у вас самопідписаний TLS, додайте довірений корінний сертифікат у систему або використайте параметр trusted-host у тимчасових випадках.

Публікація власних пакетів

# У каталозі вашого пакета
python3 -m pip install --upgrade build twine
python3 -m build

# Завантаження у приватний індекс
TWINE_USERNAME=company TWINE_PASSWORD='S3cret!' \
  twine upload --repository-url 'https://pypi.example.lan/company/dev/' dist/*

Після завантаження ваші колеги зможуть ставити пакет прямо з devpi. Це дуже зручно для python в Linux середовищах розробки та CI.

Альтернативні способи

Docker та контейнеризація

Швидкий старт у контейнері, якщо вам ближча Docker на сервері:

# Мінімальний запуск devpi у контейнері (дані збережуться у volume)
sudo docker run -d --name devpi \
  -p 127.0.0.1:3141:3141 \
  -v devpi-data:/data \
  devpi/devpi

Далі знову ставимо Nginx спереду. Плюс контейнеризація спрощує оновлення.

TLS і контроль доступу

devpi вже має аутентифікацію, а Nginx може додати IP‑обмеження чи mTLS. Для мінімального захисту в локальній мережі достатньо залишити devpi на 127.0.0.1 і випускати зовні тільки Nginx 🛡️

GUI‑спосіб: devpi-web

Плагін devpi-web вже встановили. Він додає веб‑інтерфейс огляду пакетів та індексів.

  1. Відкрийте https://pypi.example.lan у браузері.
  2. Увійдіть як ваш користувач (наприклад, company).
  3. Переглядайте індекси, шукайте пакети, дивіться метадані та історію завантажень.

FAQ

Devpi не стартує як сервіс.
Перевірте логи: journalctl -u devpi -e. Часто це права на /srv/devpi/data або помилки у шляху до venv.

Отримую 413 Request Entity Too Large при завантаженні.
Підніміть client_max_body_size у Nginx (наприклад, 200M), перезавантажте Nginx.

pip лається на сертифікат.
Додайте корпоративний корінний сертифікат у системне сховище та в CA для Python. Тимчасово можна додати trusted-host або використати HTTP у закритій мережі.

Як оновити devpi без простою?
Робіть бекап, зупиняйте сервіс, оновлюйте пакети у venv, запускайте міграцію схемою devpi-server --export/--import за потреби, потім стартуйте. Тримайте Nginx із сторінкою 503‑maintenance на час вікна.

Чи можна віддати тільки кеш PyPI без власних пакетів?
Так, використовуйте індекс root/pypi напряму як index-url.

Де логи Nginx?
/var/log/nginx/access.log та /var/log/nginx/error.log. Перевіряйте також nginx -t після змін.

Порада від Kernelka

Зробіть простий резервний копіювальник через systemd timer, щоб не згадувати про бекапи в останню хвилину.

# Скрипт бекапу /usr/local/bin/devpi-backup
sudo tee /usr/local/bin/devpi-backup >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
TS=$(date +%F-%H%M)
mkdir -p /var/backups
sudo tar -czf /var/backups/devpi-$TS.tgz -C /srv/devpi data
EOF
sudo chmod +x /usr/local/bin/devpi-backup

# Юніт і таймер для щоденного бекапу о 2:30
sudo tee /etc/systemd/system/devpi-backup.service >/dev/null <<'EOF'
[Unit]
Description=Backup devpi data

[Service]
Type=oneshot
ExecStart=/usr/local/bin/devpi-backup
EOF

sudo tee /etc/systemd/system/devpi-backup.timer >/dev/null <<'EOF'
[Unit]
Description=Daily devpi backup

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true

[Install]
WantedBy=timers.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now devpi-backup.timer

Підсумок

  • Розгорнули devpi як приватний PyPI за Nginx на сервер Linux.
  • Запустили сервіс через systemd і налаштували доступ за доменом.
  • Налаштували pip для інсталяції та twine для публікації пакетів.
  • Додали резервне копіювання через cron та systemd timers і отримали базовий GUI.
  • Тепер внутрішні Python‑пакети розгортаються швидко і безпечно ✅