
Intro
프로젝트, 특히 보안이 중요한 기업이나 공공기관의 프로젝트를 하다 보면 외부의 인터넷과 격리된 폐쇄망인 경우들이 많이 있다. 이런 경우엔 라이브러리를 다운로드 받는 경로가 제한된다. 예를 들어 파이썬은 PyPI 서버에 연결할 수 없기 때문에, 미리 Anaconda 환경을 구성해가기도 한다.
하지만 데스크탑이 아닌 노트북 환경이라면, Anaconda의 큰 용량은 적지 않은 부담이 된다. 또한 내게 있는 패키지를 동료 개발자에게 공유하는 것도 번거로운 일이 된다.
오늘 소개할 devpi 는 사설 PyPI 서버를 구축할 수 있게 해주는 라이브러리이다. 이 라이브러리를 통해 사설 PyPI 서버를 구축한 뒤, 폐쇄망 환경에 도입하여 사용할 수 있다. 또는, 내부 폐쇄망과 외부 인터넷을 연결하는 DMZ 에 PyPI 서버를 구축할 수도 있다.
devpi
개념
- Python 패키지를 관리하기 위한 사설 PyPI 서버
- 그리고 이를 이용해 패키징, 테스트 및 릴리즈 작얼을 수행하는 데 도움이 되는 명령줄 도구
- MIT 라이선스
주요 기능
빠른 PyPI 미러
어떤 개발자가 특정 패키지를 devpi-server를 통해 최초로 설치할 때 devpi 는 이 패키지를 공식 PyPI 서버로부터 받아 로컬에 캐시해둔다. 이후 다른 누군가로부터 동일한 패키지를 요청받는 경우, devpi-server는 캐시된 라이브러리를 제공한다.
개인이 생성한 인덱스를 업로드, 테스트, 스테이징 가능
devpi-server는 개발자가 만든 PyPI 패키지를 올릴 수 있는 저장 공간을 제공한다. devpi 는 개발자가 만들어 등록한 패키지를 자동으로 테스트하고, 통과한 것만 배포용 저장소로 이동시킬 수 있다.
인덱스 상속
인덱스 간 상속을 통해 개발 및 운영 환경을 분리하여 패키지를 관리할 수 있다. 이를 통해 PyPI 인덱스에 악성 릴리즈를 업로드하는 공격자로부터 안전하게 보호할 수 있다.
1
2
3
4
root/pypi ← 공식 PyPI 캐시
team/dev ← 개발용 인덱스
team/staging ← 검증용 인덱스
team/prod ← 운영용 인덱스
team/dev: 개발자가 자유롭게 패키지를 업로드할 수 있음.root/pypi를 상속한다.team/prod: 검증된 패키지만 존재하는 공간- 인덱스는 부모 인덱스의 패키지를 그대로 사용할 수 있다.
- 부모 인덱스와 자식 인덱스에서 같은 패키지가 존재할 경우 자식 인덱스가 우선된다.
웹 인터페이스
웹 인터페이스와 검색 플러그인을 설치하면, 웹 화면에서 패키지, 버전, 문서를 탐색하고 검색할 수 있다.

복제 replication
주 devpi 서버를 실시간으로 복제하는 보조 devpi 서버를 둘 수 있다. 이는 주 서버가 다운되었을 때 보조하는 “장애 대응”의 역할도 하며, 공식 PyPI 서버에 비해 물리적인 거리가 가까운 경우, “속도 개선”의 효과를 볼 수 있게 해준다.
1
2
3
4
5
6
[ Primary devpi ]
|
-------------------------
| | |
[ Replica A ] [ Replica B ] [ Replica C ]
- Primary: 업로드·관리 담당
- Replica: 읽기 전용(보통), 동기화된 데이터 제공
내보내기와 가져오기 Exporting Importing
기존 devpi 서버의 모든 설정과 패키지를 파일로 백업한 뒤, 새 devpi 서버에서 그대로 복원할 수 있다
Jenkins 연동
Jenkins 와 연동하여, 패키지 업로드 시 자동으로 테스트에 연동할 수 있다. (CI)
인덱스
devpi, 그리고 PyPI 서버의 구조를 이해하기 위해서는 인덱스(Index) 개념을 먼저 이해해야 한다.
즉, 인덱스란 “패키지를 담는 논리적인 저장소 단위”로, 여러 개의 Python 패키지와 그 버전들을 하나의 그룹으로 묶어 관리하는 공간이다.
여기서 말하는 저장소는 실제 디렉터리나 물리적인 파일 위치라기보다는, 어떤 패키지를 어디에서 어떻게 보이게 할 것인가를 정의하는 논리적 구획에 가깝다.
인덱스는 devpi에서 패키지를 관리하는 기본 단위이며, 모든 패키지의 조회·설치·업로드는 특정 인덱스를 기준으로 이루어진다.
devpi server 의 작동 원리 도식

(1) 어떤 User 가 devpi-server 로 패키지 설치(pip install)를 요청한다.
(2) devpi 서버는 요청받은 패키지를 자신이 가지고 있는지 확인하고, 없다면 공식 PyPI 서버로부터 받아온다.
(3) 공식 PyPI 서버로부터 받아온 패키지는 캐시하여 보관해둔다.
(4) 요청 User 에게 패키지를 전달한다.
(5) 다른 User 로부터 동일한 패키지를 요청받는다면
(6) devpi 서버는 요청받은 패키지를 가지고 있는지 확인하고
(7) 가지고 있을 경우(=요청받은 패키지가 캐시되어 있는 경우), 그 패키지를 요청자에게 제공한다.
devpi 서버 구축하기
1. 설치
- devpi-server 패키지 설치
1
2
3
pip install devpi-server
pip install devpi-client # client, 필요시
pip install devpi-web # web ui, 필요시
2. devpi-server 초기화 (devpi-init)
- devpi-server 를 사용하기 위해서는 먼저 초기화 작업을 해줘야 한다.
1
2
devpi-init [--serverdir /레지스트리/경로]
# 경로를 지정하지 않으면 자신의 home 아래에 생성된다.
- devpi init 결과로 생긴 디렉터리
1
2
3
4
./
├── .nodeinfo
├── .serverversion
└── .sqlite
3. devpi-server 구동
- devpi 서버 구동
1
2
devpi-server --host=0.0.0.0 --port 3141 --serverdir /레지스트리/경로
# 백그라운드로 실행시키려면 nohup
- 서버가 잘 띄워졌는지 브라우저를 통해 접속해보자 (
http://{HOST}:{PORT})

위 캡쳐에서 눈여겨봐야 할 점이 세 군데 있다.
첫 번째는username: 위 캡쳐에서 “username”: “root” 부분
두 번째는indexes: 위 켑쳐에서 “indexex”: {”pypi” …} 부분
세 번째는mirror_url: “mirror_url”: “https://pypi.org/simple/’ 부분
username과index를 붙여 PyPI 서버로 접근할 수 있는 URL 이 만들어지므로 필수로 확인!
(참고) 도커 및 도커 컴포즈로 devpi-server 실행
- Dockerfile
1
2
3
4
5
6
# Dockerfile.devpi
FROM python:3.14-slim
WORKDIR /devpi
RUN pip install devpi-server devpi-client devpi-web
- docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# docker-compose.devpi.yml
services:
devpi_server:
build:
context: .
dockerfile: Dockerfile.devpi
image: devpi:latest
container_name: devpi_server
restart: unless-stopped
ports:
- "3141:3141"
volumes:
- ./devpi:/devpi
command: >
sh -c "
if [ ! -f /devpi/registry/.serverversion ]; then
devpi-init --serverdir /devpi/registry;
fi &&
devpi-server --host 0.0.0.0 --port 3141 --serverdir /devpi/registry
"
- 실행
1
sudo docker compose -f ./docker-compose.devpi.yml up -d
패키지 캐싱 (첫 번째 패키지 설치)
devpi 서버는, 자신을 통해 설치 요청을 받은 패키지를 캐싱하여 보관해둔다. 그리고 이후부터는 동일한 패키지 설치 요청을 받을 때, 공식 PyPI 서버가 아닌 자신이 가지고 있는 패키지를 제공한다.
따라서 “첫 번째 패키지 설치”란, devpi 서버가 특정 패키지를 처음으로 받아 내부에 캐싱해두는 의미를 가진다.
이번 섹션에서는 패키지 캐싱을 위해 devpi-server로 패키지 설치 요청을 보내는 방법을 소개해본다. (아래 도식에서 1번 단계에 해당)

devpi 서버를 패키지 인덱스로 사용하기
devpi 서버를 패키지 인덱스로 사용하는 방법은 아래를 참고한다.
- pip install 시
--index-url옵션 부여
1
2
3
4
5
6
7
# 문법
pip install --index-url http://{HOST}:{PORT}/{user}/{index}/+simple \
패키지1 [패키지2 패키지3 ...]
# 예시
pip install --index-url http://192.168.0.2:3141/root/pypi/+simple \
pandas requests pydantic
- pip 설정의
root/pypi인덱스에 대한index-url지정
1
2
3
4
5
# pip 설정파일 위치 (POSIX) : $HOME/.pip/pip.conf
# pip 설정파일 위치 (Windows) : $HOME/pip/pip.ini
[global]
index-url = http://localhost:3141/root/pypi/+simple/ # 이곳을 변경
1
2
# 이후부터는 그냥 pip install
pip install pandas requests pydantic
- uv 프로젝트에서는
pyproject.toml파일에서 index-url 설정
1
2
3
4
5
# pyproject.toml 에 아래 추가
[tool.uv.pip]
index-url = "http://{HOST}:{PORT}/{user}/{index}"
# 이후엔 uv pip install ... 로 하면 된다.
테스트
🤖 : devpi 가 설치된 서버
🧑💻 : 사용자. devpi 로 패키지 설치 요청을 보내는 클라이언트
- 🤖 우선 서버에서 인터넷에 연결할 수 있음을 확인한다.
1
2
3
4
5
6
$ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=29.5 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=116 time=29.4 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=116 time=32.8 ms
...
- 🤖 devpi-server 구축
1
2
3
pip install devpi-server
devpi-init
nohup devpi-server --host 0.0.0.0 --port 3141 &
- 🤖 패키지 캐싱
1
2
pip install --index-url http://localhost:3141/{user}/{index}/+simple\
pandas requests pydantic
- 🤖 서버에서 인터넷으로 나가는 게이트웨이 라우트를 삭제
1
$ sudo ip route del default
- 🤖 인터넷과 연결이 끊김을 확인
1
2
$ ping 8.8.8.8
ping: connect: Network is unreachable
- 🧑💻 노트북에서 devpi 서버에 설치된 라이브러리를 요청
1
2
3
4
5
6
7
8
9
10
% uv pip install requests --index-url http://{HOST}:{PORT}/{user}/{index}/+simple
# 출력 -- 성공!
Resolved 5 packages in 10.55s
Installed 5 packages in 10ms
+ certifi==2026.1.4
+ charset-normalizer==3.4.4
+ idna==3.11
+ requests==2.32.5
+ urllib3==2.6.3
- 🧑💻 노트북에서 devpi 서버에 설치되지
않은라이브러리를 요청
→ 패키지를 찾지 못함
1
2
3
4
5
% uv pip install tensorflow --index-url http://{HOST}:{PORT}/{user}/{index}
× No solution found when resolving dependencies:
╰─▶ Because tensorflow was not found in the package registry
and you require tensorflow, we can conclude that your requirements
are unsatisfiable.
- 🤖 서버의 인터넷 연결 복구 그리고 인터넷 연결 테스트
1
2
3
4
5
6
$ sudo ip route add default via <게이트웨이 IP>
$ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=29.4 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=116 time=30.0 ms
...
- 🧑💻 노트북에서 devpi 서버에 설치되지
않은라이브러리를 요청
→ 설치 성공
1
2
3
4
5
6
7
8
9
10
% uv pip install tensorflow --index-url http://HOST:PORT/root/pypi
Resolved 38 packages in 1m 27s
Prepared 33 packages in 45.95s
Installed 33 packages in 242ms
+ absl-py==2.3.1
+ astunparse==1.6.3
...
+ tensorflow==2.20.0
...
Reference
https://pypi.org/project/devpi-server/
https://devpi.net/docs/devpi/devpi/stable/%2Bd/index.html
devpi server로 PyPI mirror 서버 구축
Comments