다언어 프로젝트 매니페스트 통합 관리: pyproject.toml + package.json + .tool-versions + Makefile

Python·JS 혼합 프로젝트에서 의존성·런타임·실행 명령을 어떻게 분담할 것인가

Python과 JavaScript가 한 저장소에 공존할 때, 어느 한 도구로 모든 의존성을 묶을 수 있는지에서 시작해 파일별 역할 분담(pyproject.toml, package.json, .tool-versions, Makefile, Dockerfile)과 점진적 도입 단계를 정리한다.

Engineering
저자

Kwangmin Kim

공개

2026년 04월 27일

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.tomlpackage.json 두 파일이 공존하는 게 정상이다. 한쪽 디렉터리에 모두 두든(루트 또는 frontend/ 분리) 잠금 파일까지 함께 커밋한다. pyproject.toml 만 두고 package.json 을 README 본문에 코드 블록으로 적는 식의 변종은 도구가 자동 인식하지 못해 의미가 없다.

4 시스템 런타임 — 언어 버전은 매니페스트에 들어가지 않는다

pyproject.tomlpython = "^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 installpoetry 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” 한 줄만 두면 충분하다.

install-backend 의 fallback 구조

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개 축으로 분해된다.

  1. 패키지 축: pyproject.toml + package.json (생태계별 분리, 통합 불가)
  2. 런타임 축: .tool-versions 또는 Dockerfile (다중 런타임 또는 OS 전체 고정)
  3. 명령 축: Makefile 또는 justfile 또는 docker-compose.yml (실행 흐름 통합)

세 축을 모두 채울 필요는 없다. 프로젝트 규모와 사용자 페르소나에 맞춰 단계적으로 추가하면 된다.

9 관련 주제

Subscribe

Enjoy this blog? Get notified of new posts by email: