1 발단 — Poetry로 React 패키지를 관리할 수 있는가
PoC 저장소가 Python(FastAPI 백엔드 + Streamlit) 과 JavaScript(React + Vite 프론트엔드)가 공존하는 구조로 커지자 자연스러운 의문이 생긴다. 패키지 매니저를 하나로 통합할 수 없는가. Poetry로 react, vite 같은 npm 패키지까지 잡을 수 있다면 README에 “설치는 poetry install 하나”라고 적을 수 있다.
답은 단호하다. 불가능하다. Poetry는 PyPI와 Python wheel 만 이해하며 npm 레지스트리의 패키지를 읽지도 설치하지도 못한다. 거꾸로 npm 도 PyPI 에 손대지 못한다. 이는 도구의 한계가 아니라 설계상 의도된 분리다.
본 글에서는 그 분리가 왜 표준이 됐는지, 다언어 프로젝트의 매니페스트를 어떻게 분담하는지, 어디까지 한 파일로 묶을 수 있는지 정리한다.
2 패키지 매니저는 생태계 단위로 분리된다
각 언어 생태계는 자기 패키지 레지스트리·잠금 형식·메타데이터 스키마를 고집한다. 한 도구가 여러 생태계를 한꺼번에 다루려면 모든 레지스트리·해석기·잠금 알고리즘을 흡수해야 하는데, 그 비용이 분리해서 두는 비용보다 압도적으로 크다.
| 도구 | 관리 대상 | 레지스트리 |
|---|---|---|
| Poetry / pip / uv | Python 패키지 | PyPI |
| npm / yarn / pnpm / bun | JS 패키지 | npm registry |
| Bundler | Ruby gem | RubyGems |
| Cargo | Rust crate | crates.io |
| Go modules | Go 패키지 | Go proxy |
생태계 안에서는 도구가 여러 개라도 매니페스트 형식이 통일되어 있다. Python 의 pyproject.toml, JS 의 package.json 처럼. 그러나 생태계를 넘는 통합 표준은 존재하지 않는다.
React 가 Python 라이브러리가 아니라는 점이 핵심이다. React 는 JavaScript 라이브러리이고 npm 으로만 설치 가능하다. Vite(번들러) 도 Node.js 런타임에서 동작하며 Python 으로 대체할 수 없다. 따라서 PoC 저장소가 React 를 쓰는 한 npm(또는 동등 도구) 설치는 필수다.
3 파일별 역할 분담 — 생태계마다 매니페스트를 따로 둔다
업계 관례는 “생태계별 매니페스트를 각자 두고, 사람이 읽는 통합 가이드는 README 로 묶는다”이다.
| 파일 | 관리 범위 | 도구 |
|---|---|---|
pyproject.toml |
Python 패키지 정의 | Poetry / pip / uv |
poetry.lock |
Python 의존성 잠금 | Poetry |
package.json |
JS 패키지 정의 | npm / yarn / pnpm |
package-lock.json |
JS 의존성 잠금 | npm |
Gemfile / Cargo.toml / go.mod |
각 언어 | 해당 언어 도구 |
README.md |
사람이 읽는 설치 가이드 | — |
Python + JS 가 섞인 저장소라면 pyproject.toml 과 package.json 두 파일이 공존하는 게 정상이다. 한쪽 디렉터리에 모두 두든(루트 또는 frontend/ 분리) 잠금 파일까지 함께 커밋한다. pyproject.toml 만 두고 package.json 을 README 본문에 코드 블록으로 적는 식의 변종은 도구가 자동 인식하지 못해 의미가 없다.
4 시스템 런타임 — 언어 버전은 매니페스트에 들어가지 않는다
pyproject.toml 에 python = "^3.11" 처럼 적어도 그건 요구 버전 선언일 뿐 실제 Python 인터프리터를 설치해주지는 않는다. 인터프리터 자체는 OS 패키지나 별도 버전 매니저로 깐다. 이것이 두 번째 분리 차원이다.
| 파일 | 도구 | 범위 |
|---|---|---|
.python-version |
pyenv | Python 버전만 |
.nvmrc |
nvm (Node Version Manager) | Node 버전만 |
.tool-versions |
asdf / mise | 다중 런타임 (Python + Node + Ruby + …) |
Dockerfile |
Docker | 전체 OS + 런타임 + 패키지 |
devcontainer.json |
VS Code Dev Containers | 개발환경 스펙 |
다언어 프로젝트에서 한 파일로 여러 런타임을 묶고 싶다면 .tool-versions 가 가장 가깝다.
python 3.11.5
nodejs 20.11.0
asdf 또는 mise 가 깔린 머신이라면 이 파일이 있는 디렉터리에서 asdf install 한 번에 두 런타임이 모두 설치된다. 다만 사용자가 asdf/mise 를 사전에 설치해두어야 하므로 진입 장벽이 0 은 아니다.
가장 강력한 통합은 Dockerfile 이다. OS 부터 런타임·패키지까지 한 이미지에 모두 고정한다. 재현성은 최고지만, 빌드 시간과 디스크 비용이 따른다. 운영 환경에는 Docker 가 정석이고, 로컬 개발에는 .tool-versions 정도가 균형점이다.
5 실행 통합 — 패키지 아닌 “명령”을 한 파일로 묶기
매니페스트가 분리되어도 사용자 입장에서는 “한 명령으로 다 깔고 실행되면 좋겠다”는 요구가 자연스럽다. 이때는 패키지 매니저 관점이 아니라 명령 관점의 도구를 쓴다.
| 파일 | 내용 |
|---|---|
Makefile |
make install → poetry install + npm install 한 번에 |
Taskfile.yml (go-task) |
같은 역할, YAML 기반 |
justfile (just) |
현대적 Makefile 대안, POSIX 의존 적음 |
Dockerfile |
전체 환경을 컨테이너로 pin — 가장 재현성 높음 |
docker-compose.yml |
백엔드·프론트·DB 여러 서비스 한 번에 |
Makefile 은 1980년대 도구지만 현재도 다언어 프로젝트의 사실상 표준이다. 의존성 그래프 기반 빌드 추적이 강력하고 Linux/macOS 어디나 깔려 있다. 단점은 Windows 비호환과 들여쓰기를 탭으로만 받는 비표준 문법이라, 현대적 대안으로 just(justfile) 가 부상 중이다.
6 실제 적용 — Makefile 한 파일로 Python + JS 통합
다음은 Python(FastAPI 백엔드) + Python(PoC, Streamlit) + JS(React 프론트엔드) 가 공존하는 저장소의 Makefile 예시다. PoC 단계 저장소를 운영 단계로 가져갈 때 흔히 등장하는 구성이다.
.PHONY: install install-backend install-poc install-frontend \
dev-backend dev-frontend dev-poc
install: install-backend install-poc install-frontend
install-backend:
poetry install --no-root || true
.venv/bin/pip install -e . --no-deps
.venv/bin/pip install fastapi "uvicorn[standard]" sse-starlette
install-poc:
cd poc && poetry install
install-frontend:
cd frontend && npm install
dev-backend:
poetry run uvicorn services.api.main:app --reload --port 8000
dev-frontend:
cd frontend && npm run dev
dev-poc:
cd poc && poetry run streamlit run src/agent/app.py -- --cloud이로써 사용자는 make install 한 번으로 세 환경을 모두 설치하고, make dev-backend / make dev-frontend / make dev-poc 로 각 서비스를 띄운다. README 에는 “사전 요구사항(Python 3.11, Node 20, Poetry, npm) 설치 후 make install” 한 줄만 두면 충분하다.
poetry install --no-root || true 다음에 pip 으로 직접 설치하는 패턴은 PoC 단계에서 자주 등장한다. Poetry 의존성 해석이 실패하거나 editable install 만으로 충분한 경우의 백업 경로다. 운영 단계로 넘어가면 fallback 을 제거하고 Poetry 단독 경로로 정리한다.
7 점진적 도입 — 프로젝트 성숙도에 맞춘 매니페스트 추가 순서
모든 도구를 한 번에 다 쓸 필요는 없다. 프로젝트가 커지면서 단계적으로 도입하는 게 표준 흐름이다.
| 단계 | 추가하는 파일 | 효과 |
|---|---|---|
| PoC / 초기 | pyproject.toml + package.json + README.md |
매니페스트만 두고 README 로 사람이 묶음 |
| 팀 확장 | + .tool-versions |
신규 합류자가 런타임 버전을 추측하지 않아도 됨 |
| 자동화 시작 | + Makefile (또는 justfile) |
명령이 파편화되지 않고 한 곳에 모임 |
| 운영 진입 | + Dockerfile + docker-compose.yml |
전체 환경을 이미지로 고정, CI 통합 |
| 대규모 운영 | + devcontainer.json 또는 K8s 매니페스트 |
개발자 온보딩·프로덕션 배포 단순화 |
PoC 단계에서 Dockerfile부터 만들면 빌드 시간과 디스크 비용을 일찍 부담하게 되고, 거꾸로 운영 단계까지 README 로만 묶으면 환경 재현성이 흔들린다. 현재 단계에 맞는 도구만 더하는 게 비용 대비 효과가 가장 크다.
8 정리 — 다언어 프로젝트가 한 매니페스트로 묶이지 않는 이유
핵심은 한 줄로 정리할 수 있다.
패키지 매니저는 생태계 단위로 분리되어 있고, 그 분리는 의도된 것이다. 통합은 매니페스트가 아니라 명령 수준(Makefile)이나 환경 수준(Dockerfile)에서 일어난다.
다언어 프로젝트의 매니페스트 구성은 다음 3개 축으로 분해된다.
- 패키지 축:
pyproject.toml+package.json(생태계별 분리, 통합 불가) - 런타임 축:
.tool-versions또는Dockerfile(다중 런타임 또는 OS 전체 고정) - 명령 축:
Makefile또는justfile또는docker-compose.yml(실행 흐름 통합)
세 축을 모두 채울 필요는 없다. 프로젝트 규모와 사용자 페르소나에 맞춰 단계적으로 추가하면 된다.
9 관련 주제
- DevOps Concept - venv vs pyenv vs Poetry vs Conda 비교 분석 — Python 단일 언어 도구 비교 (본 글의 다언어 확장 출발점)
- Poetry - pyproject.toml 상세 가이드 — Python 매니페스트 표준 심화
- Poetry - CI/CD와 Docker 통합 — 운영 단계 Dockerfile 통합