
며칠 전에 퇴근하고 집에 와서, 커피 한 잔 내려놓고 개발자 커뮤니티를 좀 뒤적이고 있었어요. 사실 이런 시간이 은근히 재밌거든요. 일할 때는 바빠서 못 보던 도구나 글들을 천천히 보는 맛이 있잖아요. 그러다가 uv라는 파이썬 패키지 관리 도구를 다시 보게 됐습니다. 이름은 예전부터 몇 번 봤는데, 솔직히 처음엔 “또 새로운 패키지 매니저인가?” 싶었어요.
저는 파이썬을 꽤 오래 써왔습니다. 20년 가까이 개발하면서 pip는 거의 손에 붙은 도구였고, 중간중간 pipenv나 poetry도 써봤어요. 좋은 도구들이긴 한데, 이상하게 실무에서는 조금 무겁게 느껴질 때가 많았습니다. 설정도 많고, 팀원들끼리 맞춰야 할 것도 있고요. 그래서 결국 “에이, 그냥 pip랑 requirements.txt가 마음 편하지” 하면서 돌아오곤 했죠.
그런데 uv는 좀 달랐어요. Rust 기반이라 빠르다, pip보다 훨씬 빠르다, 가상환경부터 Python 버전 관리까지 한 번에 된다는 이야기가 자꾸 보이더라고요. 이런 말에 또 개발자가 안 흔들릴 수가 없죠. 그래서 말만 듣지 말고, 실제로 제가 쓰는 작은 API 프로젝트에 바로 넣어봤습니다. 오늘은 그때 느꼈던 점이랑, 쓰면서 알게 된 소소한 꿀팁을 친구한테 얘기하듯이 풀어볼게요.
uv를 처음 써봤을 때 제일 먼저 느낀 건 속도였어요
설치는 정말 간단했습니다. 그냥 아래처럼 한 줄이면 끝나요.
pip install uv
사실 여기까지는 별 감흥이 없었어요. 요즘 도구들 설치는 다들 쉽잖아요. 그런데 패키지를 설치해보는 순간 느낌이 확 왔습니다. 제가 테스트 삼아 numpy를 설치해봤는데, pip로는 보통 10초 넘게 걸리던 게 uv에서는 2초 남짓 만에 끝나더라고요.
처음엔 저도 의심했습니다. “이거 캐시가 남아 있었나?” 싶었죠. 그래서 가상환경도 새로 만들고, 프로젝트 폴더도 깨끗하게 비우고, 몇 번 더 해봤어요. 그래도 빨랐습니다. 이게 뭐랄까, 단순히 조금 빠른 정도가 아니라 기다리는 리듬 자체가 달라지는 느낌이었어요. pip 쓸 때는 커피 한 모금 마시고 기다리는 느낌이라면, uv는 “어? 벌써 끝났네?” 쪽에 가깝습니다.
uv가 마음에 들었던 지점은 단순히 설치 속도만은 아니었어요. 패키지 설치, 가상환경 생성, 의존성 관리, Python 버전 관리까지 한 도구 안에서 처리할 수 있다는 점이 꽤 좋았습니다. 예전에는 보통 pyenv, virtualenv, pip, requirements.txt를 조합해서 썼잖아요. 프로젝트 하나 새로 시작할 때마다 작은 의식처럼 반복하던 작업들이 있었고요. uv는 그 번거로운 손동작을 꽤 많이 줄여줍니다.
제가 직접 재본 설치 속도는 이 정도였습니다
테스트 환경은 제 개인 맥북이었고, 대략 아래 조건이었어요. 아주 과학적인 벤치마크라고 하기엔 민망하지만, 실무자가 체감하기엔 충분한 비교였습니다.
- 장비: Apple Silicon MacBook Pro
- OS: macOS Sonoma
- 네트워크: 기가 인터넷 환경
- 저장장치: SSD
- 테스트 방식: 새 가상환경 생성 후 패키지 설치 시간 측정
| 설치한 패키지 | pip 설치 시간 | uv 설치 시간 | 느낌 |
|---|---|---|---|
| numpy | 약 12.4초 | 약 2.1초 | 생각보다 확 빨라서 놀람 |
| pandas | 약 18.7초 | 약 3.5초 | 의존성 많은데도 시원함 |
| fastapi, uvicorn | 약 25.3초 | 약 4.8초 | API 프로젝트에서 특히 체감 큼 |
숫자로 보면 대략 5배에서 6배 정도 차이가 났습니다. 물론 환경마다 다를 수 있어요. 회사 네트워크가 느리거나, 내부 저장소를 쓰거나, 프록시가 끼어 있으면 결과가 달라질 수 있죠. 그래도 제가 느낀 핵심은 분명했습니다. uv는 실무에서 기다리는 시간을 줄여주는 도구입니다. 이게 은근히 큽니다. 하루에 몇 번씩 환경 만들고 패키지 설치하는 개발자라면 더 크게 느껴질 거예요.
작은 API 프로젝트에 uv를 넣어봤더니 작업 흐름이 꽤 단순해졌습니다
저는 최근에 내부용 작은 API 서버를 하나 만들 일이 있었어요. 규모는 크지 않았고, FastAPI 기반으로 간단한 조회 API 몇 개를 제공하는 정도였습니다. 예전 같았으면 대충 이런 순서로 갔을 겁니다.
python -m venv .venv로 가상환경 생성source .venv/bin/activate로 활성화pip install fastapi uvicorn으로 패키지 설치pip freeze > requirements.txt로 의존성 저장- 나중에 동료가 받으면
pip install -r requirements.txt실행
익숙하긴 한데, 살짝 번거롭죠. 특히 pip freeze는 조심해야 합니다. 내가 잠깐 테스트로 깔았던 패키지까지 다 들어가기도 하고, 운영에 필요 없는 것들이 섞이기도 하거든요. 실무에서 이런 작은 찌꺼기들이 나중에 묘하게 귀찮은 문제를 만듭니다.
이번에는 uv 방식으로 시작해봤습니다. 흐름이 꽤 깔끔했어요.
프로젝트 초기화와 가상환경 만들기
uv에서는 가상환경을 만들 때 아래처럼 실행하면 됩니다.
uv venv --python 3.12
이렇게 하면 프로젝트 폴더 안에 .venv가 생깁니다. 기존 venv랑 비슷한데, 명령어가 짧고 Python 버전 지정도 자연스럽습니다. 가상환경 활성화는 기존처럼 해도 됩니다.
source .venv/bin/activate
그런데 사실 uv를 쓰다 보면 매번 가상환경을 활성화하지 않아도 되는 순간이 많습니다. 이건 아래에서 다시 이야기할게요. 이 부분이 은근히 편합니다. 나이 들수록 손가락으로 반복하는 명령어가 줄어드는 게 참 고맙더라고요.
패키지는 uv add로 추가하는 게 마음 편했습니다
FastAPI와 uvicorn을 추가할 때는 이렇게 했습니다.
uv add fastapi uvicorn
이 명령어를 실행하면 단순히 패키지만 설치하는 게 아닙니다. pyproject.toml에 의존성을 기록하고, uv.lock 파일도 만들어줍니다. npm을 써본 분이라면 package.json과 package-lock.json 느낌이라고 생각하면 이해가 빠를 거예요.
이게 협업할 때 꽤 좋습니다. 동료가 저장소를 클론한 다음 아래 명령어만 실행하면, 같은 의존성 상태로 맞춰집니다.
uv sync
예전에는 “requirements.txt 최신 맞나요?”, “이거 pip freeze 다시 떠야 하나요?”, “내 로컬에서는 되는데요?” 같은 이야기가 종종 나왔어요. 다들 한 번쯤 겪어봤을 겁니다. uv를 쓴다고 그런 일이 100% 사라지는 건 아니지만, 적어도 의존성 관리의 기준점이 훨씬 또렷해집니다.
가상환경 활성화 없이 실행하는 uv run이 생각보다 편해요
제가 uv에서 제일 마음에 들었던 기능 중 하나가 uv run입니다. 가상환경을 따로 활성화하지 않아도 uv가 알아서 현재 프로젝트의 환경을 보고 실행해줍니다.
uv run python main.py
FastAPI 서버를 띄울 때도 이렇게 쓸 수 있습니다.
uv run uvicorn main:app --reload
이게 별거 아닌 것 같지만, CI/CD 파이프라인에서는 꽤 유용합니다. 셸 스크립트에서 가상환경 활성화하다가 경로 문제 나고, OS마다 스크립트 달라지고, 그런 자잘한 피곤함이 줄어요. 특히 GitHub Actions나 사내 Jenkins에서 테스트 돌릴 때 uv sync 후 uv run pytest처럼 흐름을 잡으면 훨씬 명확합니다.
uv add와 uv pip install은 쓰임새가 조금 달라요
처음 uv를 쓰면 살짝 헷갈리는 부분이 있습니다. 바로 uv add와 uv pip install의 차이예요. 둘 다 패키지를 설치하는 것처럼 보이거든요. 저도 처음엔 “아무거나 쓰면 되는 거 아닌가?” 했습니다.
근데 며칠 써보니 기준이 생기더라고요. 저는 이렇게 나눠 씁니다.
| 명령어 | 언제 쓰면 좋은지 | 파일 반영 여부 |
|---|---|---|
uv add |
프로젝트에 정식 의존성으로 추가할 때 | pyproject.toml, uv.lock 갱신 |
uv pip install |
기존 pip처럼 임시 설치하거나 빠르게 테스트할 때 | 기본적으로 프로젝트 의존성 파일에는 반영되지 않음 |
저는 새 프로젝트에서는 거의 무조건 uv add를 씁니다. 그래야 나중에 팀원이 봐도 “아, 이 패키지는 프로젝트가 공식적으로 필요로 하는구나” 하고 알 수 있거든요. 반대로 잠깐 테스트할 라이브러리라면 uv pip install을 씁니다. 예를 들어 어떤 JSON 라이브러리를 잠깐 비교해본다거나, 특정 버그를 재현하려고 패키지를 임시로 깔 때요.
이 작은 기준을 정해두면 팀에서도 혼선이 덜합니다. 도구 자체보다 이런 사용 규칙이 더 중요할 때가 많아요. 20년 개발하면서 느낀 건데, 좋은 도구를 쓰는 것보다 “우리 팀은 이 도구를 이렇게 쓴다”가 정해져 있는 게 훨씬 강합니다.
기존 프로젝트에는 uv pip 모드로 가볍게 얹어보는 게 좋습니다
이미 운영 중인 프로젝트를 한 번에 uv 방식으로 바꾸는 건 솔직히 부담스럽습니다. 특히 오래된 프로젝트일수록 requirements.txt 안에 역사와 사연이 다 들어 있거든요. 누가 왜 넣었는지 모르는 패키지도 있고, 지우면 터질 것 같은 버전 고정도 있습니다. 그런 프로젝트를 갑자기 pyproject.toml 기반으로 바꾸자고 하면 팀 분위기가 살짝 싸해질 수도 있어요.
이럴 때는 uv를 아주 가볍게 시작하면 됩니다.
uv pip install -r requirements.txt
이 방식은 기존 pip install -r requirements.txt와 거의 비슷하게 동작합니다. 그런데 설치 속도는 uv의 장점을 가져갈 수 있어요. 기존 구조를 건드리지 않으니 팀원 설득도 쉽습니다. 저도 회사 프로젝트에는 처음부터 전면 전환하지 않았어요. CI에서 패키지 설치하는 부분만 uv로 바꿔보고, 문제가 없는지 며칠 지켜봤습니다.
효과는 꽤 괜찮았습니다. 특히 CI에서 매번 새 환경을 만들고 의존성을 설치하는 프로젝트는 체감이 큽니다. 1분 걸리던 설치가 20초대로 줄면, 그 자체로 개발자들의 기다림이 줄어듭니다. 이런 게 쌓이면 하루가 조금 덜 답답해져요. 별것 아닌데, 일하는 기분이 달라집니다.
lock 파일 충돌은 가끔 나지만, 대응법만 알면 괜찮습니다
uv를 팀에서 쓰다 보면 uv.lock 파일이 충돌날 수 있습니다. 이건 npm의 lock 파일이나 poetry lock 파일에서도 비슷하게 겪는 일이죠. 두 사람이 동시에 패키지를 추가하거나, 서로 다른 브랜치에서 의존성을 건드리면 자연스럽게 생깁니다.
제가 겪었던 상황은 이랬어요. 저는 httpx를 추가했고, 다른 동료는 같은 시점에 pydantic-settings를 추가했습니다. 각자 브랜치에서는 문제없었는데, merge하는 순간 uv.lock이 충돌났죠.
이럴 때 무작정 손으로 lock 파일을 고치려고 하면 피곤해집니다. 가능하면 의존성 정의 파일을 먼저 정리하고, lock 파일은 다시 생성하는 쪽이 낫습니다.
uv lock --re-generate
다만 여기서 살짝 조심해야 합니다. lock 파일을 다시 만들면 하위 의존성 버전이 조금 달라질 수 있어요. 보통은 문제없지만, 운영 서비스라면 테스트를 꼭 돌려봐야 합니다. 저는 lock 파일을 재생성한 뒤에는 최소한 아래 정도는 확인합니다.
uv sync가 정상적으로 끝나는지uv run pytest로 테스트가 통과하는지- API 서버가 로컬에서 정상 기동되는지
- 주요 패키지 버전이 의도치 않게 크게 바뀌지 않았는지
사실 lock 파일은 귀찮은 존재처럼 보이지만, 운영 안정성 측면에서는 고마운 장치입니다. “내 컴퓨터에서는 되는데 서버에서는 안 돼요”라는 말을 줄여주니까요. 개발자 인생에서 그 말만 줄어도 꽤 평화로워집니다.
Python 버전 관리까지 uv로 해보니 pyenv 생각이 덜 났어요
uv를 쓰면서 의외로 마음에 들었던 기능이 Python 버전 설치와 관리였습니다. 저는 원래 pyenv를 꽤 오래 썼어요. 좋은 도구입니다. 지금도 충분히 훌륭하고요. 그런데 새 장비를 세팅할 때마다 PATH 잡고, 셸 설정 만지고, 특정 버전 빌드하다가 의존성 문제 나고… 이런 순간들이 가끔 있었습니다.
uv는 아래처럼 Python 자체를 설치할 수 있습니다.
uv python install 3.11
그리고 해당 버전으로 가상환경을 만들 수 있어요.
uv venv --python 3.11
이 흐름이 꽤 자연스럽습니다. 회사 노트북에서는 프로젝트마다 Python 버전이 조금씩 다른 경우가 많거든요. 어떤 건 3.10, 어떤 건 3.11, 신규 프로젝트는 3.12. 예전에는 pyenv로 관리하고, 가상환경 따로 만들고, 프로젝트마다 버전 맞추는 식이었는데 uv로도 충분히 처리 가능했습니다.
물론 pyenv를 당장 지우라는 이야기는 아닙니다. 저도 아직 기존 장비에는 pyenv가 남아 있어요. 다만 새 프로젝트나 새 장비에서는 uv만으로 먼저 해보고 있습니다. 도구를 줄이는 게 생각보다 마음을 편하게 하거든요. 개발 환경이 단순할수록 문제 생겼을 때 추적도 쉽습니다.
쓰다가 만난 자잘한 에러들도 있었어요
좋은 이야기만 하면 좀 광고 같잖아요. uv를 쓰면서 저도 몇 번 삐끗했습니다. 아주 큰 문제는 아니었지만, 처음 쓰는 분들은 비슷하게 당황할 수 있을 것 같아서 적어봅니다.
글로벌 pip에 깔아둔 패키지를 uv 가상환경에서 못 찾는 문제
예전에 제 개발 환경에는 글로벌 pip로 설치해둔 패키지가 몇 개 있었습니다. python-dateutil 같은 것들이요. 그러다 uv로 새 가상환경을 만들고 스크립트를 실행했더니 갑자기 import 에러가 났습니다.
ModuleNotFoundError: No module named 'dateutil'
처음엔 “어? 이거 설치돼 있는데?” 했죠. 그런데 생각해보니 글로벌 환경에 설치된 패키지였고, uv로 만든 가상환경에는 없는 게 당연했습니다. 해결은 간단합니다. 프로젝트 의존성으로 필요한 패키지를 명확하게 추가하면 됩니다.
uv add python-dateutil
또는 기존 lock 파일이 있는 프로젝트라면 아래처럼 맞춰주면 됩니다.
uv sync
이때 다시 느꼈습니다. uv 환경은 깔끔하게 격리해서 쓰는 게 정석입니다. 글로벌에 뭘 깔아두고 기대는 습관은 이제 조금씩 버리는 게 맞아요. 귀찮아 보여도, 나중에 디버깅 시간을 생각하면 이게 훨씬 낫습니다.
macOS에서 가상환경 생성이 실패했던 경우
한 번은 macOS에서 uv venv를 실행했는데 아래처럼 가상환경 생성에 실패한 적이 있었습니다.
Failed to create virtual environment
메시지만 보면 원인이 확 와닿지 않아서 잠깐 헤맸습니다. 제 경우에는 Xcode Command Line Tools가 오래된 상태였어요. 아래 명령어로 다시 설치하고 나니 해결됐습니다.
xcode-select --install
macOS에서 개발 환경 문제가 생기면 의외로 이쪽이 원인일 때가 많습니다. 특히 새 맥을 세팅했거나 OS 업데이트 직후라면 한 번 확인해보는 게 좋아요.
제가 uv를 쓰면서 정한 작은 기준들
며칠 써보고 나니 제 나름의 사용 원칙이 생겼습니다. 엄청 대단한 철학은 아니고요. 실무에서 덜 헷갈리려고 정한 기준입니다.
- 새 프로젝트는 가능하면 uv add로 의존성을 관리합니다.
- 기존 requirements.txt 프로젝트는 먼저 uv pip install -r requirements.txt로 가볍게 적용해봅니다.
- 가상환경 활성화가 애매한 스크립트나 CI에서는 uv run을 씁니다.
uv.lock은 Git에 포함하고, 충돌이 나면 팀원과 변경 내용을 확인한 뒤 재생성합니다.- 글로벌 pip에 기대지 않고, 프로젝트별 의존성을 명확히 기록합니다.
개발 도구는 결국 습관의 문제라고 생각합니다. 아무리 좋은 도구도 팀에서 쓰는 방식이 제각각이면 오히려 혼란스러워요. 반대로 단순한 도구라도 팀 규칙이 명확하면 오래 갑니다. uv는 그 규칙을 만들기 꽤 좋은 도구라는 인상을 받았습니다.
그래서 uv를 누구에게 추천하냐면요
솔직히 말하면, 모든 파이썬 개발자가 당장 uv로 갈아타야 한다고 생각하진 않습니다. 아주 간단한 스크립트 몇 개 돌리고, 패키지도 거의 설치하지 않는다면 기존 pip만으로도 충분합니다. 익숙한 도구를 억지로 바꿀 필요는 없어요. 저도 그런 강요는 별로 좋아하지 않습니다.
다만 아래에 해당한다면 uv는 한 번 써볼 만합니다. 꽤 높은 확률로 “어, 이거 괜찮은데?” 하실 거예요.
- 패키지 설치 시간이 답답했던 개발자
- CI/CD에서 의존성 설치 시간을 줄이고 싶은 팀
- 여러 Python 버전을 오가며 작업하는 개발자
- pipenv나 poetry가 조금 무겁게 느껴졌던 사람
- requirements.txt 관리가 슬슬 불편해진 프로젝트
- 팀원들과 같은 의존성 환경을 안정적으로 맞추고 싶은 경우
저는 앞으로 새로 시작하는 파이썬 프로젝트는 uv로 잡아볼 생각입니다. 기존 프로젝트도 한 번에 확 바꾸기보다는, CI 설치 단계나 로컬 개발 환경부터 조금씩 옮겨보고 있어요. 이런 건 급하게 갈아엎기보다, 손에 익혀가면서 바꾸는 게 오래 갑니다. 나이 들수록 더 그렇게 되더라고요. 빠른 도구도 좋지만, 팀이 편안하게 받아들이는 속도도 중요하니까요.
uv가 궁금하다면 공식 GitHub 저장소도 한 번 보는 걸 추천합니다. 문서도 꽤 잘 정리되어 있고, 업데이트 속도도 빠른 편입니다. 필요하면 여기서 확인해보시면 됩니다. astral-sh/uv GitHub
제 느낌을 편하게 말하자면 이렇습니다. pip에 익숙한 개발자라면 uv는 낯설지만 불편하지 않고, poetry가 무거웠던 사람에게는 꽤 산뜻한 선택지입니다. 파이썬 프로젝트를 자주 만들거나, 팀 단위로 의존성 관리 때문에 한 번쯤 고생해본 분이라면 주말에 30분만 잡고 한번 만져보세요. 아마 설치 속도에서 이미 살짝 마음이 흔들릴 겁니다. 저처럼요.
댓글
댓글 쓰기