🔥 uv 완전 가이드 (3): 패키지 배포, 마이그레이션, Docker 통합
강의 목차

지난 두 편에서 uv의 기본기와 프로젝트 관리를 다뤘다. 여기까지만 해도 일상적인 Python 개발에는 충분하다. 하지만 실제 프로덕션 환경에서는 패키지를 배포하고, 기존 프로젝트를 마이그레이션하고, Docker 이미지를 빌드하고, CI/CD 파이프라인을 구성해야 한다. 이번 글에서 그 전부를 다룬다.
패키지 빌드와 배포
빌드 시스템 설정
패키지를 배포하려면 pyproject.toml에 빌드 시스템이 정의되어 있어야 한다. 없으면 uv가 레거시 setuptools로 폴백하는데, 이건 권장하지 않는다.
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"hatchling, flit, setuptools 중 원하는 빌드 백엔드를 쓰면 된다. uv는 자체 빌드 백엔드도 제공하고 있다.
uv build
uv builduv builddist/ 디렉토리에 소스 배포판(.tar.gz)과 바이너리 배포판(.whl)이 생긴다:
dist/
├── hello-world-0.1.0-py3-none-any.whl
└── hello-world-0.1.0.tar.gzdist/
├── hello-world-0.1.0-py3-none-any.whl
└── hello-world-0.1.0.tar.gz워크스페이스에서 특정 패키지만 빌드하려면:
uv build --package my-libuv build --package my-lib배포 전에는 --no-sources 플래그를 붙여서 빌드하는 게 좋다. tool.uv.sources 없이도 빌드가 되는지 확인하기 위해서다:
uv build --no-sourcesuv build --no-sources버전 관리
uv version 명령으로 버전을 관리할 수 있다:
# 현재 버전 확인
uv version
# hello-world 0.1.0
# 정확한 버전으로 변경
uv version 1.0.0
# hello-world 0.1.0 => 1.0.0
# 시맨틱 버전 범프
uv version --bump minor
# hello-world 1.0.0 => 1.1.0
uv version --bump patch
# hello-world 1.1.0 => 1.1.1# 현재 버전 확인
uv version
# hello-world 0.1.0
# 정확한 버전으로 변경
uv version 1.0.0
# hello-world 0.1.0 => 1.0.0
# 시맨틱 버전 범프
uv version --bump minor
# hello-world 1.0.0 => 1.1.0
uv version --bump patch
# hello-world 1.1.0 => 1.1.1프리릴리스 버전도 된다:
# 패치 + 베타
uv version --bump patch --bump beta
# hello-world 1.3.0 => 1.3.1b1
# 베타에서 안정 버전으로
uv version --bump stable
# hello-world 1.3.1b2 => 1.3.1# 패치 + 베타
uv version --bump patch --bump beta
# hello-world 1.3.0 => 1.3.1b1
# 베타에서 안정 버전으로
uv version --bump stable
# hello-world 1.3.1b2 => 1.3.1--dry-run으로 미리 확인할 수도 있다:
uv version 2.0.0 --dry-run
# hello-world 1.0.0 => 2.0.0 (실제 변경 안 됨)uv version 2.0.0 --dry-run
# hello-world 1.0.0 => 2.0.0 (실제 변경 안 됨)uv publish
PyPI에 배포하려면:
uv publishuv publish인증은 토큰으로 한다. PyPI는 더 이상 사용자명/비밀번호 방식을 지원하지 않는다:
# 토큰 설정
uv publish --token pypi-AgEIcHlwaS5v...
# 또는 환경변수로
export UV_PUBLISH_TOKEN=pypi-AgEIcHlwaS5v...
uv publish# 토큰 설정
uv publish --token pypi-AgEIcHlwaS5v...
# 또는 환경변수로
export UV_PUBLISH_TOKEN=pypi-AgEIcHlwaS5v...
uv publishTestPyPI에 먼저 테스트 배포하고 싶다면, pyproject.toml에 인덱스를 추가한다:
[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = trueuv publish --index testpypiuv publish --index testpypi배포 후 설치 테스트:
uv run --with hello-world --no-project -- python -c "import hello_world"uv run --with hello-world --no-project -- python -c "import hello_world"GitHub Actions에서 Trusted Publishing
GitHub Actions에서 배포하면 토큰 없이도 PyPI에 올릴 수 있다. Trusted Publishing을 쓰면 된다:
# .github/workflows/publish.yml
name: "Publish"
on:
push:
tags:
- v*
jobs:
run:
runs-on: ubuntu-latest
environment:
name: pypi
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v8
- name: Install Python 3.13
run: uv python install 3.13
- name: Build
run: uv build
- name: Smoke test (wheel)
run: uv run --isolated --no-project --with dist/*.whl tests/smoke_test.py
- name: Smoke test (source distribution)
run: uv run --isolated --no-project --with dist/*.tar.gz tests/smoke_test.py
- name: Publish
run: uv publish# .github/workflows/publish.yml
name: "Publish"
on:
push:
tags:
- v*
jobs:
run:
runs-on: ubuntu-latest
environment:
name: pypi
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v8
- name: Install Python 3.13
run: uv python install 3.13
- name: Build
run: uv build
- name: Smoke test (wheel)
run: uv run --isolated --no-project --with dist/*.whl tests/smoke_test.py
- name: Smoke test (source distribution)
run: uv run --isolated --no-project --with dist/*.tar.gz tests/smoke_test.py
- name: Publish
run: uv publishPyPI 프로젝트 설정에서 GitHub를 Trusted Publisher로 등록하는 걸 잊지 말자.
pip에서 마이그레이션
기존에 pip + requirements.txt로 관리하던 프로젝트를 uv로 옮기는 건 생각보다 간단하다.
핵심 차이 이해하기
pip 워크플로우에서 uv로 넘어갈 때 달라지는 점:
| pip + pip-tools | uv |
|---|---|
requirements.in | pyproject.toml의 [project] dependencies |
requirements.txt (잠긴 버전) | uv.lock |
requirements-dev.txt | [dependency-groups] dev |
| 플랫폼별 여러 락파일 | 하나의 유니버설 uv.lock |
python -m venv + source .venv/bin/activate | uv run (자동 관리) |
가장 큰 차이는 uv.lock이 크로스 플랫폼이라는 점이다. pip-tools에서는 Linux용, macOS용, Windows용 락파일을 따로 만들어야 했다. uv에서는 하나의 락파일이 모든 플랫폼을 커버한다.
마이그레이션 순서
1단계: 프로젝트 초기화
uv inituv init2단계: 기존 의존성 가져오기
requirements.in(또는 requirements.txt)에서 가져온다. 기존 잠긴 버전을 유지하고 싶다면 -c 옵션으로 제약을 건다:
uv add -r requirements.in -c requirements.txtuv add -r requirements.in -c requirements.txt이렇게 하면 기존 버전이 최대한 유지된 채로 uv.lock이 생성된다.
3단계: 개발 의존성 가져오기
uv add --dev -r requirements-dev.in -c requirements-dev.txtuv add --dev -r requirements-dev.in -c requirements-dev.txtrequirements-dev.in이 부모 requirements.in을 -r로 포함하고 있다면, 해당 줄을 제거하고 가져와야 한다:
sed '/^-r /d' requirements-dev.in | uv add --dev -r - -c requirements-dev.txtsed '/^-r /d' requirements-dev.in | uv add --dev -r - -c requirements-dev.txt문서 빌드용 의존성 같은 추가 그룹이 있다면:
uv add -r requirements-docs.in -c requirements-docs.txt --group docsuv add -r requirements-docs.in -c requirements-docs.txt --group docs4단계: 플랫폼별 제약 처리
기존에 플랫폼별 락파일이 있었다면, 마커를 추가해야 한다:
# Windows용 락파일에 마커 추가
uv pip compile requirements.in -o requirements-win.txt \
--python-platform windows --no-strip-markers
# 모든 플랫폼 제약을 합쳐서 가져오기
uv add -r requirements.in -c requirements-win.txt -c requirements-linux.txt# Windows용 락파일에 마커 추가
uv pip compile requirements.in -o requirements-win.txt \
--python-platform windows --no-strip-markers
# 모든 플랫폼 제약을 합쳐서 가져오기
uv add -r requirements.in -c requirements-win.txt -c requirements-linux.txt5단계: 검증
uv sync
uv run pytest # 테스트가 있다면uv sync
uv run pytest # 테스트가 있다면이제 requirements.in, requirements.txt, requirements-dev.in, requirements-dev.txt 파일들은 삭제해도 된다. pyproject.toml과 uv.lock이 그 역할을 대신한다.
프로젝트 환경의 차이
pip과 달리 uv는 "활성화된 가상환경" 개념에 의존하지 않는다. 각 프로젝트의 .venv를 자동으로 관리하고, uv run으로 실행하면 항상 올바른 환경에서 명령이 돌아간다.
# pip 방식
source .venv/bin/activate
pytest
# uv 방식
uv run pytest# pip 방식
source .venv/bin/activate
pytest
# uv 방식
uv run pytestuv run을 쓰면 락파일과 환경이 항상 동기화되어 있는 게 보장된다. 가상환경을 직접 활성화해서 쓸 수도 있지만, uv run이 더 안전하다.
Docker 통합
uv와 Docker의 조합은 강력하다. 빌드 속도가 빨라지고, 이미지 크기를 줄이기 위한 최적화 옵션도 풍부하다.
기본 설정
가장 간단한 방법은 공식 distroless 이미지에서 바이너리를 복사하는 거다:
FROM python:3.12-slim-trixie
COPY --from=ghcr.io/astral-sh/uv:0.11.7 /uv /uvx /bin/FROM python:3.12-slim-trixie
COPY --from=ghcr.io/astral-sh/uv:0.11.7 /uv /uvx /bin/버전을 꼭 핀하자. latest는 재현성을 깨뜨린다.
uv에서 제공하는 파생 이미지를 직접 사용할 수도 있다:
FROM ghcr.io/astral-sh/uv:0.11.7-python3.12-trixie-slimFROM ghcr.io/astral-sh/uv:0.11.7-python3.12-trixie-slimAlpine, Debian, Python 버전별로 다양한 이미지가 준비되어 있다.
프로젝트 설치
FROM python:3.12-slim-trixie
COPY --from=ghcr.io/astral-sh/uv:0.11.7 /uv /uvx /bin/
WORKDIR /app
COPY . /app
ENV UV_NO_DEV=1
RUN uv sync --locked
CMD ["uv", "run", "my_app"]FROM python:3.12-slim-trixie
COPY --from=ghcr.io/astral-sh/uv:0.11.7 /uv /uvx /bin/
WORKDIR /app
COPY . /app
ENV UV_NO_DEV=1
RUN uv sync --locked
CMD ["uv", "run", "my_app"].dockerignore에 .venv를 반드시 추가하자. 로컬 가상환경을 이미지에 넣으면 플랫폼 불일치로 문제가 생긴다.
레이어 최적화 (핵심)
의존성과 프로젝트 코드를 분리하면 빌드 캐시를 훨씬 효율적으로 쓸 수 있다:
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:0.11.7 /uv /uvx /bin/
WORKDIR /app
# 1단계: 의존성만 먼저 설치 (캐시 활용)
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project
# 2단계: 프로젝트 코드 복사 후 설치
COPY . /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --lockedFROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:0.11.7 /uv /uvx /bin/
WORKDIR /app
# 1단계: 의존성만 먼저 설치 (캐시 활용)
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project
# 2단계: 프로젝트 코드 복사 후 설치
COPY . /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked의존성이 바뀌지 않으면 1단계가 캐시되고, 코드만 변경되면 2단계만 다시 실행된다. 빌드 시간이 극적으로 줄어든다.
멀티스테이지 빌드
프로덕션 이미지에서 소스 코드를 제외하고 싶다면:
# 빌드 스테이지
FROM python:3.12-slim AS builder
COPY --from=ghcr.io/astral-sh/uv:0.11.7 /uv /uvx /bin/
ENV UV_PYTHON_DOWNLOADS=0
WORKDIR /app
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project --no-editable
COPY . /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-editable
# 프로덕션 스테이지
FROM python:3.12-slim
COPY --from=builder /app/.venv /app/.venv
CMD ["/app/.venv/bin/my_app"]# 빌드 스테이지
FROM python:3.12-slim AS builder
COPY --from=ghcr.io/astral-sh/uv:0.11.7 /uv /uvx /bin/
ENV UV_PYTHON_DOWNLOADS=0
WORKDIR /app
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project --no-editable
COPY . /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-editable
# 프로덕션 스테이지
FROM python:3.12-slim
COPY --from=builder /app/.venv /app/.venv
CMD ["/app/.venv/bin/my_app"]--no-editable를 쓰면 소스 코드에 대한 의존성 없이 패키지가 설치되니까, .venv만 복사하면 된다.
추가 최적화
바이트코드 컴파일 — 시작 시간을 줄인다:
ENV UV_COMPILE_BYTECODE=1
RUN uv sync --lockedENV UV_COMPILE_BYTECODE=1
RUN uv sync --locked캐시 마운트 — 빌드 간 캐시를 유지한다:
ENV UV_LINK_MODE=copy
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --lockedENV UV_LINK_MODE=copy
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --lockedGitHub Actions 통합
기본 설정
공식 setup-uv 액션을 쓴다:
name: CI
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v8
with:
version: "0.11.7"
- name: Install the project
run: uv sync --locked --all-extras --dev
- name: Run tests
run: uv run pytest testsname: CI
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v8
with:
version: "0.11.7"
- name: Install the project
run: uv sync --locked --all-extras --dev
- name: Run tests
run: uv run pytest tests멀티 Python 버전 테스트
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v8
with:
python-version: ${{ matrix.python-version }}
- name: Install and test
run: |
uv sync --locked --all-extras --dev
uv run pytest testsjobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v8
with:
python-version: ${{ matrix.python-version }}
- name: Install and test
run: |
uv sync --locked --all-extras --dev
uv run pytest tests캐싱
setup-uv에 캐시 기능이 내장되어 있다:
- name: Install uv
uses: astral-sh/setup-uv@v8
with:
enable-cache: true - name: Install uv
uses: astral-sh/setup-uv@v8
with:
enable-cache: true그 외 통합
uv는 이 외에도 다양한 도구와 통합된다:
- GitLab CI/CD — 공식 Docker 이미지를 사용해 GitLab에서도 동일하게 활용 가능
- Jupyter —
uv run --with jupyter jupyter lab으로 프로젝트 환경에서 바로 노트북 실행 - pre-commit —
astral-sh/uv-pre-commit훅으로 락파일 검증 자동화 - FastAPI —
uv add fastapi --extra standard후uv run fastapi dev - PyTorch — 가속기(CPU/CUDA/ROCm)별 인덱스 설정 지원
- Renovate / Dependabot — 자동 의존성 업데이트 지원
- AWS Lambda — 멀티스테이지 빌드로 Lambda 배포
정리하며
세 편에 걸쳐 uv를 처음부터 끝까지 다뤘다. Python 설치부터 스크립트 실행, 프로젝트 관리, 도구 활용, 패키지 배포, 마이그레이션, Docker 통합까지.
솔직히 처음에는 "또 하나의 패키지 매니저"라고 생각했다. Python 생태계에는 이미 pip, Poetry, PDM, Rye가 있으니까. 그런데 직접 써보니 차원이 다르다. 단순히 빠른 게 아니라, 흩어져 있던 도구들을 하나로 통합하면서도 각각의 기능을 제대로 구현해냈다.
2026년 현재, 새 Python 프로젝트를 시작한다면 uv를 안 쓸 이유가 없다. 기존 프로젝트 마이그레이션도 uv add -r requirements.in 한 줄로 시작할 수 있으니, 한번 시도해 볼 만하다.
시리즈: uv 완전 가이드
참고 자료
- uv 공식 문서 — Building and publishing — 패키지 빌드와 배포 가이드
- uv 공식 문서 — Migration — pip에서 uv로 마이그레이션 가이드
- uv 공식 문서 — Docker integration — Docker 통합 가이드
- uv 공식 문서 — GitHub Actions — GitHub Actions 통합 가이드
- uv-docker-example — Docker 베스트 프랙티스 예제 프로젝트
- astral-sh/setup-uv — GitHub Actions용 공식 uv 설치 액션 (v8+)










