just 커맨드 러너 사용법: make를 대체하는 태스크 러너

이번 포스팅에서는 just 커맨드 러너 사용법에 대해서 정리하고자 한다. 프로젝트를 다루다 보면 빌드, 테스트, 배포, 로그 확인 같은 명령을 매번 길게 치거나 README를 뒤져 복사하게 된다. just는 이런 프로젝트별 명령을 justfile 하나에 이름 붙여 저장해 두고, just test 한 줄로 실행하게 해주는 도구다. make와 비슷하지만 빌드 시스템이 아니라 순수 명령 실행에 초점을 맞춰, 문법이 단순하고 파라미터·의존성·다른 언어 스크립트를 훨씬 편하게 다룬다. 이 글에서는 설치와 첫 justfile부터 변수와 파라미터, 레시피 의존성, .env 로드, shebang 레시피, make와의 차이, 그리고 실제로 돌려 본 결과까지 실무 순서대로 짚는다.

make 말고 just를 쓰는 이유

just는 프로젝트에서 자주 쓰는 명령을 justfile에 모아 이름으로 실행하는 커맨드 러너다. 2026년 8월 기준 GitHub 스타 35.3k를 넘긴 Rust 구현으로, 리눅스·macOS·윈도우 어디서든 셸만 있으면 동작한다. 핵심은 “빌드 시스템이 아니라 명령 실행기”라는 점이다. make처럼 파일 타임스탬프를 따지거나 의존 그래프를 빌드하지 않고, 그냥 저장해 둔 명령(레시피)을 실행한다.

make를 명령 러너로 쓸 때의 불편함은 겪어 본 사람은 안다. 탭과 스페이스를 구분하는 문법, $(VAR)$$ 이스케이프, .PHONY 선언, 실제 파일이 아닌 타깃에서 생기는 오작동 같은 것들이다. just는 이 문제들을 처음부터 없앤다. 변수는 {{name}}으로 쓰고, 파라미터는 이름으로 받으며, 모든 레시피는 기본적으로 “그냥 실행”되므로 .PHONY 같은 선언이 필요 없다. 즉 make의 빌드 기능은 버리고, 명령을 모아 두는 용도만 깔끔하게 남긴 도구다.

이 도구는 프로젝트의 진입점 역할을 한다. 새 팀원이 저장소를 받아 just --list만 치면 이 프로젝트에서 할 수 있는 작업 목록이 한눈에 나온다. 개발 환경을 표준화하는 devcontainerDocker Compose watch 같은 도구와 함께 쓰면, “이 프로젝트는 어떻게 빌드하고 테스트하지?”라는 질문의 답을 justfile 하나로 정리할 수 있다. 이 글의 예시는 셸 명령 기준이며, 어떤 언어의 프로젝트든 동일하게 적용된다.

설치와 첫 justfile 만들기

just는 패키지 매니저로 설치하고, 프로젝트 루트에 justfile을 만들면 바로 쓸 수 있다. macOS·Linux는 Homebrew, 어느 플랫폼이든 Rust의 cargo로 설치한다. 설치 후 justfile에 레시피(작업) 하나를 정의하고 just로 실행해 본다.

# macOS / Linux (Homebrew)
brew install just

# 모든 플랫폼 (Rust cargo)
cargo install just
ShellScript

설치가 끝나면 프로젝트 루트에 justfile이라는 파일을 만든다. 레시피는 이름: 다음 줄에 들여쓰기로 명령을 적는 형태다.

# justfile — 프로젝트 루트에 저장

# 인사 (첫 레시피가 기본 레시피가 된다)
hello:
    echo "Hello, just!"

# 여러 명령을 순서대로
build:
    echo "컴파일 시작"
    echo "빌드 완료"
Plaintext

각 레시피는 이름: 아래에 들여쓴 명령들로 구성된다. just hello를 실행하면 hello 레시피의 명령이, just build를 실행하면 build의 두 명령이 순서대로 실행된다. 인자 없이 just만 치면 파일의 첫 번째 레시피(여기서는 hello)가 실행되고, just --list를 치면 사용 가능한 레시피 목록이 출력된다. 레시피 위의 # 주석--list에 설명으로 함께 표시된다.

변수와 파라미터로 유연하게

just는 변수와 파라미터로 레시피를 유연하게 만든다. 변수는 이름 := "값"으로 선언해 {{이름}}으로 참조하고, 파라미터는 레시피 이름 뒤에 적어 실행 시 인자로 넘긴다. 파라미터에는 기본값과 가변 개수도 지원한다.

# 변수 선언
registry := "ghcr.io/myteam"
version := "1.0.0"

# 변수 사용
image:
    echo "이미지: {{registry}}/app:{{version}}"

# 파라미터를 받는 레시피
greet name:
    echo "안녕하세요, {{name}}님"

# 기본값이 있는 파라미터
deploy env="staging":
    echo "{{env}} 환경에 배포"

# 가변 파라미터 (0개 이상)
test *args:
    pytest {{args}}
Plaintext

registry := "..."처럼 선언한 변수는 {{registry}}로 어디서든 참조된다. greet name은 ‘just greet 홍길동'처럼 인자를 받아 {{name}}에 채운다. deploy env="staging"'just deploy'면 기본값 staging으로, ‘just deploy prod'면 prod로 동작한다. test *args는 ‘just test -v tests/'처럼 넘긴 인자를 그대로 {{args}}에 펼쳐, 뒤에 무엇을 붙이든 명령에 전달한다. make의 위치 인자와 달리 이름으로 받으므로 어떤 값이 어디로 가는지 명확하다.

레시피끼리 엮기: 의존성

여러 작업을 순서대로 실행해야 할 때는 레시피 의존성을 쓴다. 레시피 이름 뒤에 다른 레시피 이름을 나열하면, 그 레시피들이 먼저 실행된 뒤 본문이 실행된다. 의존 레시피에 인자를 넘길 수도 있다.

# lint, test 를 먼저 실행한 뒤 build
build: lint test
    echo "빌드 진행"

lint:
    echo "린트 검사"

test:
    echo "테스트 실행"

# 의존 레시피에 인자 전달
release version: (tag version)
    echo "{{version}} 릴리스"

tag version:
    echo "git tag {{version}}"
Plaintext

build: lint testjust build를 실행하면 linttestbuild 순으로 처리한다. CI에서 흔히 쓰는 “검사부터 통과해야 빌드”를 한 줄로 표현한다. release version: (tag version)처럼 괄호를 쓰면 의존 레시피 tagversion 인자를 넘길 수 있다. 그래서 just release 2.0.0tag 2.0.0을 먼저 실행하고 릴리스로 이어진다. 의존성은 중복 실행되지 않으므로, 여러 레시피가 같은 준비 작업에 의존해도 그 작업은 한 번만 돈다.

.env와 환경변수 다루기

프로젝트 명령은 환경변수에 자주 의존한다. just는 set dotenv-load := true를 justfile 상단에 두면 같은 디렉터리의 .env 파일을 자동으로 읽어 레시피 환경에 넣는다. just 변수를 환경변수로 내보내려면 exportset export := true를 쓴다.

# .env 파일을 자동 로드
set dotenv-load := true

# just 변수를 환경변수로도 내보내기
export DATABASE_URL := "postgres://localhost/app"

# 사용할 쉘 지정 (기본은 sh)
set shell := ["bash", "-uc"]

run:
    # .env의 값과 export한 변수가 환경변수로 들어온다
    echo "DB: $DATABASE_URL"
    ./app --port $PORT
Plaintext

set dotenv-load := true를 켜면 .envKEY=VALUE들이 레시피 실행 시 환경변수로 주입된다. 비밀 값이나 환경별 설정을 justfile에 직접 쓰지 않고 .env로 분리할 수 있어, 저장소에 커밋되면 안 되는 값을 안전하게 다룬다. export DATABASE_URL := "..."은 just 변수를 환경변수로도 노출해, 레시피 안의 $DATABASE_URL로 접근하게 한다. set shell로 레시피를 실행할 셸을 바꿀 수도 있는데, bash -uc로 두면 미정의 변수 사용 시 에러가 나 실수를 빨리 잡는다.

shebang 레시피로 다른 언어 쓰기

just의 강력한 기능 중 하나는 shebang 레시피다. 레시피 본문의 첫 줄이 #!로 시작하면, just는 그 레시피 전체를 하나의 스크립트 파일로 저장해 실행한다. 덕분에 여러 줄 로직을 한 프로세스에서 돌리거나, bash가 아닌 Python·Node 같은 다른 언어로 레시피를 작성할 수 있다.

# 여러 줄 bash를 하나의 스크립트로 실행 (변수·조건문이 유지된다)
deploy env:
    #!/usr/bin/env bash
    set -euo pipefail
    if [ "{{env}}" = "prod" ]; then
        echo "프로덕션 배포 — 확인이 필요합니다"
    else
        echo "{{env}} 배포"
    fi

# Python으로 작성한 레시피
stats:
    #!/usr/bin/env python3
    import platform
    print(f"실행 환경: {platform.system()}")
Plaintext

일반 레시피는 각 줄이 별도 셸에서 실행되지만, shebang 레시피는 본문 전체가 하나의 스크립트로 실행된다. 그래서 if·for 같은 제어문이나 중간 변수를 자연스럽게 쓸 수 있다. 위 deploy 레시피는 set -euo pipefail로 안전장치를 걸고 조건문으로 분기하는데, 이게 shebang 없이는 줄마다 셸이 끊겨 동작하지 않는다. stats처럼 첫 줄을 #!/usr/bin/env python3로 두면 그 레시피는 Python으로 실행되므로, 언어를 섞어 쓰는 프로젝트에서 특히 유용하다.

just와 make, 무엇이 다른가

just와 make는 겉보기에 비슷하지만 목적이 다르다. make는 파일 의존성을 추적해 바뀐 것만 다시 빌드하는 빌드 시스템이고, just는 명령을 모아 실행하는 러너다. 아래 표로 자주 부딪히는 차이를 정리했다.

항목makejust
목적빌드 시스템(파일 의존성 추적)명령 러너(그냥 실행)
변수 참조$(VAR), 이스케이프 $${{VAR}}
파라미터위치 인자 / 환경변수이름 있는 파라미터·기본값·가변
가짜 타깃.PHONY 선언 필요불필요(기본이 명령 실행)
다른 언어번거로움shebang 레시피로 자연스럽게
목록 보기기본 지원 없음just --list
들여쓰기반드시 탭탭·스페이스 모두 허용

표에서 보듯 파일을 산출물로 만드는 진짜 빌드가 필요하면 make(또는 각 언어의 빌드 도구)가 맞다. 반면 “이 명령 저장해 두고 짧게 부르고 싶다”가 목적이라면 just가 훨씬 편하다. 실제로 많은 프로젝트가 빌드는 언어별 도구(Gradle·Cargo·npm)에 맡기고, 그 도구들을 감싸는 진입점으로 just를 둔다. 여러 CLI 도구를 함께 정리하고 싶다면 2026년 모던 CLI 도구 모음도 참고할 만하다.

직접 돌려본 결과: justfile 하나로 실행해 보기

앞의 문법을 모아 실제 justfile을 만들어 돌려봤다. 아래 justfile은 기본 레시피, 의존성(build → lint·test), 파라미터, 기본값을 한 파일에 담은 것이다. 각 명령 앞의 @는 실행할 명령줄 자체는 출력하지 않고 결과만 보이게 하는 접두어다.

# 기본 레시피: 목록 보여주기
default:
    @just --list

# 프로젝트 빌드 (lint, test 를 먼저 실행)
build: lint test
    @echo "빌드 완료"

# 코드 린트 검사
lint:
    @echo "린트 검사 통과"

# 테스트 실행
test:
    @echo "테스트 5개 통과"

# 인사 (파라미터)
greet name:
    @echo "안녕하세요, {{name}}님"

# 배포 (기본값이 있는 파라미터)
deploy env="staging":
    @echo "{{env}} 환경에 배포"
Plaintext

이 justfile을 프로젝트 루트에 두고 just --list, just build, just greet 홍길동, just deploy를 차례로 실행한 결과가 아래 캡처다. 레시피 위의 주석이 --list에 설명으로 붙고, 의존성이 순서대로 도는 것을 실제 출력으로 확인할 수 있다.

캡처에서 세 가지가 눈에 띈다. 첫째, just --list가 각 레시피와 함께 위에 달아 둔 주석을 설명으로 보여주고, deploy env="staging"처럼 파라미터와 기본값까지 그대로 표시한다. 둘째, just build를 실행하니 의존성으로 지정한 lint·test가 먼저 순서대로 돌고(린트 검사 통과 → 테스트 5개 통과) 마지막에 build 본문(빌드 완료)이 실행된다. 셋째, just greet 홍길동은 파라미터를, just deploy는 기본값 staging을, just deploy prod는 넘긴 인자 prod를 각각 정확히 반영한다. 문법으로 설명한 동작이 그대로 재현되는 것을 볼 수 있다.

FAQ

justfile은 어디에 두고, 이름은 꼭 justfile이어야 하나요?

프로젝트 루트에 justfile 또는 .justfile로 두는 게 일반적이다. just는 명령을 실행한 디렉터리부터 상위로 올라가며 justfile을 찾으므로, 하위 디렉터리에서 just를 쳐도 루트의 justfile이 실행된다. 파일 이름은 justfile·Justfile·.justfile을 모두 인식하며, just --justfile path/to/file로 다른 위치의 파일을 지정할 수도 있다.

make의 Makefile을 just로 어떻게 옮기나요?

타깃을 레시피로, $(VAR){{VAR}}로 바꾸는 게 기본이다. make의 .PHONY 선언은 그냥 지우면 된다(just는 모든 레시피가 명령 실행이다). 위치 인자로 받던 부분은 이름 있는 파라미터로 바꾸면 더 명확해진다. 파일 산출물의 증분 빌드에 의존하던 부분만 주의하면 되는데, 그건 애초에 빌드 도구의 몫이므로 just로 옮기면서 언어별 빌드 도구에 넘기는 게 낫다.

레시피가 여러 줄인데 중간 변수나 if문이 안 먹혀요.

일반 레시피는 각 줄이 별도의 셸에서 실행되기 때문이다. if·for나 중간에 설정한 변수를 유지하려면 shebang 레시피로 만든다. 레시피 첫 줄에 #!/usr/bin/env bash를 넣으면 본문 전체가 하나의 스크립트로 실행돼, 제어문과 변수가 정상 동작한다.

just 변수와 .env 값 중 뭐가 우선인가요?

set dotenv-load := true로 로드한 .env의 값과 justfile의 변수는 별개다. justfile의 export VAR := "..."로 정의한 값이 같은 이름의 .env 값과 겹칠 수 있으니, 환경별로 달라지는 값은 .env에, 프로젝트 공통 값은 justfile 변수에 두는 식으로 역할을 나누는 게 혼란이 적다. 실행 시점에 어떤 값이 들어갔는지 확인하려면 레시피에서 echo $VAR로 찍어 보면 된다.

just –list에 설명을 어떻게 붙이나요?

레시피 바로 위 줄에 # 설명을 적으면 그 주석이 just --list 출력에 함께 표시된다. 그래서 각 레시피 위에 한 줄 주석만 달아 두면, 저장소를 처음 받은 사람도 just --list만으로 이 프로젝트에서 무엇을 할 수 있는지 파악한다. 레시피를 그룹으로 묶어 보여주는 기능도 있어, 프로젝트가 커지면 분류해 정리할 수 있다. 이 밖의 세부 기능은 just 공식 매뉴얼에 정리돼 있다.

마치며

지금까지 just 커맨드 러너 사용법을 설치부터 make와의 비교까지 정리해 보았다. 개인적으로 just를 도입하고 가장 좋았던 건, 프로젝트마다 다르던 “명령을 어디에 적어 두는가” 문제가 사라진 것이다. 예전에는 자주 쓰는 명령을 README 구석이나 셸 히스토리, 개인 alias에 흩어 두고 매번 찾아 헤맸는데, justfile 하나로 모으고 나니 “그 명령 뭐였지”를 검색하는 일이 없어졌다. 새 팀원에게도 “일단 just --list 쳐 봐”라고 한마디면 끝난다.

도입은 부담 없이 시작하길 권한다. 거창하게 모든 명령을 옮길 필요 없이, 지금 가장 자주 치는 명령 두세 개만 justfile에 레시피로 옮겨 보면 된다. just test, just run처럼 짧게 부르는 맛을 한 번 보면, 자연스럽게 나머지 명령도 하나씩 justfile로 모으게 된다. 프로젝트의 명령을 한곳에 문서화하면서 동시에 실행까지 되는 도구라, 팀 단위로 쓸 때 특히 값어치를 한다.