`.mise.toml` 하나로 팀 런타임 버전 드리프트 없애기 — Node.js·Bun·Python부터 CI·Docker까지
팀 프로젝트에서 "제 로컬에선 빌드되는데요"라는 말이 들린다면, 십중팔구 런타임 버전 불일치 때문입니다. 어떤 팀원은 nvm으로 Node 18을 쓰고, 다른 팀원은 asdf로 20을 쓰고, CI 서버는 아무도 모르게 22를 씁니다. .nvmrc는 있지만 asdf 쓰는 팀원은 모르고 지나치고, volta로 고정한 Node 버전이 pyenv 가상환경과 묘하게 충돌하는 일이 실제로 벌어집니다.
**mise(발음: "meez", 프랑스어로 "배치된")**는 Rust로 작성된 폴리글랏 런타임 버전 관리자입니다. 언어별로 분산돼 있던 nvm·asdf·pyenv·volta의 역할을 .mise.toml 파일 하나로 통합하고, 그 파일을 로컬·GitHub Actions·Docker 빌드까지 동일하게 읽어 환경 드리프트를 없앱니다. CalVer로 전환된 최신 버전 기준으로 작성했으며, 버전 정보는 공식 저장소에서 직접 확인하는 것이 좋습니다.
시작하기 전에: mise 설치와 쉘 훅 등록
나머지 내용을 따라하려면 먼저 mise를 설치하고 쉘 훅을 등록해야 합니다. 쉘 훅이 없으면 mise install을 해도 버전이 전환되지 않습니다.
macOS (Homebrew)
brew install miseLinux / macOS (공식 설치 스크립트)
curl https://mise.run | sh쉘 훅 등록
설치 후 쉘 설정 파일에 아래 줄을 추가하고 터미널을 재시작합니다.
# ~/.zshrc
eval "$(mise activate zsh)"# ~/.bashrc
eval "$(mise activate bash)"훅이 등록되면 디렉터리를 이동할 때마다 .mise.toml을 읽어 해당 버전을 자동으로 활성화합니다.
핵심 개념
.mise.toml의 세 가지 역할
mise는 단일 설정 파일을 세 개의 축으로 나눕니다.
| 기능 | 역할 | 기존 대체 도구 |
|---|---|---|
| 런타임 버전 관리 | 디렉터리별 도구 버전 자동 전환 | nvm, asdf, pyenv, rbenv, volta |
| 환경 변수 관리 | 프로젝트별 환경 변수 선언 | direnv |
| 태스크 러너 | 빌드·테스트·배포 스크립트 정의 | make, npm scripts |
기본 .mise.toml은 이런 모습입니다.
[tools]
node = "22"
bun = "1.2"
python = "3.13"
[env]
NODE_ENV = "development"
PORT = "3000"기존 .nvmrc, .python-version, .tool-versions 파일도 인식하기 때문에 마이그레이션 중에도 기존 파일이 계속 동작합니다. 대부분의 asdf 플러그인과도 호환됩니다.
버전 범위 지정 전략
버전 문자열 형식에 따라 동작이 달라집니다. 세 가지 중 하나를 선택하세요.
# 완전 고정 — 프로덕션 빌드의 재현 가능성이 중요할 때
node = "22.4.0"# 패치 버전 자동 수신 — 22.4.x 범위에서 최신 패치를 받음
node = "22.4"# 마이너·패치 모두 자동 수신 — 보안 패치를 자동으로 적용할 때
node = "22"팀 개발 환경에는 메이저만 고정해 보안 패치를 자동으로 받고, 프로덕션 배포 이미지에는 완전한 버전을 명시하는 방식이 실무에서 많이 쓰입니다.
설정 파일 우선순위
mise는 여러 계층의 설정 파일을 순서대로 병합합니다. 아래로 갈수록 우선순위가 높으며, 나중에 선언된 값이 앞선 값을 덮어씁니다.
flowchart TD
G["~/.config/mise/config.toml\n전역 기본값"]
P["상위 디렉터리 .mise.toml\n모노레포 루트 등"]
L[".mise.toml\n프로젝트 루트 커밋"]
Lo["mise.local.toml\n개인 오버라이드 git-ignore"]
G --> P --> L --> Lo
Lo --> R["최종 활성 버전·환경 변수"]이 계층 구조 덕분에 팀 공유 설정과 개인 설정을 파일 수준에서 분리할 수 있습니다.
디렉터리 이동 시 자동 버전 전환
쉘 훅이 등록된 상태에서 디렉터리를 이동하면 다음 흐름으로 버전이 전환됩니다.
flowchart TD
Move["디렉터리 이동"]
Move --> Found{".mise.toml 존재?"}
Found -->|"없음"| Global["전역 버전 적용"]
Found -->|"있음"| Installed{"해당 버전 설치됨?"}
Installed -->|"예"| Active["버전 즉시 활성화"]
Installed -->|"아니오"| Prompt["mise install 안내 출력"]
Global --> Active
Prompt --> Install["설치 완료 후 활성화"]
Install --> ActiveRust 구현이라 shell eval 기반 초기화 과정이 없어 nvm·asdf 대비 전환이 빠릅니다. 프로젝트 A(Node 18)에서 프로젝트 B(Node 22) 디렉터리로 이동하는 순간 버전이 즉시 전환됩니다.
Trust 모델: 왜 신뢰 프롬프트가 뜨는가
이 부분을 먼저 이해해야 나머지 섹션의 설정들이 의미 있게 연결됩니다.
.mise.toml의 [env], [hooks], [tasks] 섹션은 임의 코드를 실행할 수 있습니다. 그래서 mise는 이 섹션이 포함된 파일을 처음 읽을 때 신뢰 여부를 확인합니다. 방법은 두 가지입니다.
mise trust: 해당 파일을 영구적으로 신뢰 목록에 등록합니다. 한 번 실행하면 이후 프롬프트가 뜨지 않습니다. 로컬 개발 환경이나 Dev Containers에 적합합니다.MISE_YES=1: 단일 커맨드 실행 시 모든 프롬프트를 자동으로 수락합니다. 신뢰 목록에 영구 저장하지 않으므로 Docker 빌드나 CI처럼 상태를 저장하지 않는 환경에 적합합니다.
flowchart LR
Read[".mise.toml 읽기"]
Read --> HasCode{"env/hooks/tasks\n포함 여부"}
HasCode -->|"없음"| Auto["자동 활성화"]
HasCode -->|"있음"| Trusted{"신뢰 등록 여부"}
Trusted -->|"등록됨"| Auto
Trusted -->|"미등록"| Prompt["신뢰 프롬프트 출력"]
Prompt --> T["mise trust\n영구 등록"]
Prompt --> E["MISE_YES=1\n일회성 수락"]
T --> Auto
E --> Auto각 환경에서 어떤 방식을 쓰는지는 아래 섹션에서 명확히 구분합니다.
실전 적용
팀 개발 환경 세팅: 공유할 것과 개인화할 것 분리
팀 설정의 핵심은 커밋 대상과 git-ignore 대상을 파일 수준에서 분리하는 것입니다.
# .mise.toml — git에 커밋
[tools]
node = "22.4"
bun = "1.2"
python = "3.13"
[env]
NODE_ENV = "development"
_.file = ".env.local" # .env.local 내용을 환경 변수로 로드# mise.local.toml — git-ignore (mise 도구 설정 오버라이드용)
[tools]
node = "20" # 특정 팀원이 다른 버전을 테스트해야 할 때# .env.local — git-ignore (단순 환경 변수 파일)
DATABASE_URL=postgres://localhost:5432/mydb_dev
API_SECRET=로컬-시크릿mise.local.toml은 mise 설정 자체(도구 버전, 태스크 등)를 개인이 오버라이드할 때 쓰고, .env.local은 시크릿 같은 단순 환경 변수를 담는 파일입니다. _.file = ".env.local" 한 줄로 .env.local을 로드하므로 direnv를 따로 설치할 필요가 없습니다.
.gitignore에 미리 추가해 두는 것이 좋습니다.
echo "mise.local.toml" >> .gitignore
echo ".env.local" >> .gitignore신규 팀원 온보딩 흐름이 이렇게 바뀝니다.
git clone https://github.com/your-org/your-repo
cd your-repo
mise trust # [env]/[tasks] 사용 시 신뢰 등록
mise install # .mise.toml에 명시된 모든 도구 일괄 설치여러 페이지짜리 "개발 환경 세팅 문서"를 상당 부분 대체할 수 있습니다.
GitHub Actions 연동
공식 jdx/mise-action을 사용하면 .mise.toml을 그대로 CI에서 읽습니다. 아래 예시에서 mise run build와 mise run test는 .mise.toml의 [tasks] 섹션에 정의된 태스크를 실행합니다(아래 "태스크 러너" 섹션 참고).
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v4
with:
cache: true
github_token: ${{ secrets.GITHUB_TOKEN }}
- run: mise run build
- run: mise run testcache: true를 설정하면 이미 다운로드한 도구를 캐시해 실행 시간이 줄어듭니다. jdx/mise-action은 자체적으로 신뢰 처리를 해주기 때문에 별도의 mise trust나 MISE_YES=1이 필요하지 않습니다. 실제 최신 버전은 jdx/mise-action 저장소에서 확인하세요.
Docker 통합
Docker 빌드처럼 신뢰 상태가 저장되지 않는 환경에서는 MISE_YES=1을 사용합니다. mise로 설치한 도구는 shim 경로를 PATH에 추가해야 CMD나 RUN에서 찾을 수 있습니다.
FROM debian:12-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
curl ca-certificates && \
rm -rf /var/lib/apt/lists/* && \
curl https://mise.run | sh
# mise 바이너리와 shim 경로를 PATH에 추가
ENV PATH="/root/.local/bin:/root/.local/share/mise/shims:$PATH"
WORKDIR /app
COPY .mise.toml .
RUN MISE_YES=1 mise install
COPY . .
CMD ["node", "server.js"]공식 설치 스크립트(curl https://mise.run | sh)로 바이너리를 받고, ENV PATH로 shim 경로를 등록하는 것이 핵심입니다. shim 경로가 없으면 mise install이 성공해도 이후 node not found 에러가 납니다.
Dev Containers 통합
.devcontainer/devcontainer.json의 postCreateCommand에 등록하면 컨테이너 생성 시 자동으로 설치됩니다. 로컬처럼 신뢰를 영구 저장할 수 있으므로 mise trust를 먼저 실행합니다.
{
"postCreateCommand": "curl https://mise.run | sh && mise trust && mise install"
}태스크 러너
[tasks] 섹션으로 팀 공통 스크립트를 정의하면 mise run <task>로 실행할 수 있습니다. GitHub Actions 예시의 mise run build, mise run test가 아래 정의를 참조합니다.
[tasks.build]
run = "bun run build"
[tasks.test]
run = "bun test"
depends = ["build"]
[tasks.dev]
run = "bun run dev"
[tasks.lint]
run = "bun run lint"depends를 선언하면 태스크 간 실행 순서를 보장합니다. 모노레포 환경에서 패키지 간 태스크 교차 참조도 지원하므로 복잡한 모노레포 빌드 오케스트레이션도 가능합니다. 자세한 문법은 mise 태스크 문서를 참고하세요.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 단일 파일 | .mise.toml 하나로 런타임·환경 변수·태스크를 모두 관리, 설정 파일 파편화 해소 |
| 빠른 전환 | shell eval 기반 초기화가 없어 nvm·asdf 대비 버전 전환이 빠름 |
| 하위 호환성 | 대부분의 asdf 플러그인, .nvmrc, .python-version, .tool-versions 인식 |
| 팀 일관성 | CI·로컬·Docker 동일 설정 파일 사용으로 버전 불일치 문제 해소 |
| 환경 변수 통합 | direnv 없이 프로젝트별 환경 변수 관리 가능 |
| 태스크 러너 | make·npm run 대체 가능, 크로스 플랫폼 태스크 정의 |
| Renovate 지원 | .mise.toml 도구 버전 업데이트 PR 자동 생성 |
단점 및 주의사항
| 항목 | 내용 |
|---|---|
| Trust 모델 이해 필수 | [env], [hooks], [tasks]는 임의 코드 실행 가능. 환경별 적절한 신뢰 처리 필요 |
| 공급망 보안 | 여러 백엔드(GitHub Releases, npm, PyPI 등)에서 바이너리를 다운로드하며 기본적으로 해시 검증을 보장하지 않음. 완전한 재현 가능 빌드가 필요하다면 lockfile(.mise.lock) 활용을 검토할 것 |
| Windows 제한 | 일부 asdf 플러그인이 UNIX 전용이라 Windows에서 동작하지 않을 수 있음 |
| 학습 곡선 | nvm만 쓰던 개발자에게 태스크·환경 변수 통합 개념이 초기 진입 장벽이 될 수 있음 |
실무에서 흔히 겪는 실수
1. 쉘 훅이 등록되지 않은 경우
mise install을 했는데도 버전이 전환되지 않는다면 대부분 쉘 훅 문제입니다. 설치 후 시작하기 전에 섹션의 훅 등록 단계를 빠뜨리지 않았는지 확인하세요.
2. mise.local.toml과 .env.local을 .gitignore에 추가하지 않는 경우
개인 시크릿이 담긴 파일이 실수로 커밋되는 상황을 방지하려면 처음 프로젝트를 세팅할 때 바로 추가해 두는 것이 좋습니다.
echo "mise.local.toml" >> .gitignore
echo ".env.local" >> .gitignore3. Docker나 커스텀 CI에서 shim 경로를 PATH에 추가하지 않는 경우
mise install이 성공해도 PATH에 shim 경로가 없으면 node나 python 커맨드를 찾지 못합니다. jdx/mise-action을 쓰는 GitHub Actions에서는 자동으로 처리되지만, 직접 Dockerfile을 작성하거나 다른 CI 환경을 구성할 때는 명시적으로 추가해야 합니다.
마치며
.mise.toml을 버전 관리에 커밋하는 순간, 그 파일이 팀의 런타임 계약서가 됩니다. 로컬 개발, GitHub Actions, Docker 빌드, Dev Containers가 모두 같은 파일을 읽습니다.
지금 바로 시작한다면 이 순서로 접근해볼 수 있습니다.
- 설치 및 쉘 훅 등록 —
brew install mise(macOS) 또는curl https://mise.run | sh로 설치하고, 쉘 설정 파일에eval "$(mise activate zsh)"추가 .mise.toml생성 및 커밋 — 현재 프로젝트의 런타임 버전을.mise.toml에 선언하고mise trust && mise install로 확인 후 팀 레포에 커밋- CI 연동 — GitHub Actions 워크플로에
jdx/mise-action을 추가하면 로컬과 CI의 버전 갭이 사라집니다
참고 자료
- mise-en-place 공식 문서
- GitHub: jdx/mise
- GitHub: jdx/mise-action
- Continuous Integration | mise-en-place
- mise Docker Cookbook
- Getting Started with Mise | Better Stack
- Mise vs asdf | Better Stack
- Can Mise replace Volta?
- Automating Development Environment with Mise
- Best Practices for Using Mise
- Automated Dependency Updates for mise | Renovate Docs
- How to Use mise for Tool Version Management
- mise and Dev Containers Setup Guide