본문으로 건너뛰기

GOROOT, GOPATH, 모듈 캐시

이 챕터에서 다루는 것

Go 관련 글을 검색하면 GOPATH 이야기가 잔뜩 나오는데, 정작 지금 Go를 설치하면 GOPATH를 설정하라는 안내가 없다. 이 불일치가 입문자를 가장 많이 헷갈리게 한다. 이 챕터는 어떤 경로가 아직 살아 있고 무엇이 유물인지를 정리한다.

go env: 모든 답이 여기 있다

go 명령이 참고하는 설정은 전부 go env로 볼 수 있다.

go env GOROOT GOPATH GOMODCACHE GOCACHE GOBIN
/usr/local/go
/Users/sgn04088/go
/Users/sgn04088/go/pkg/mod
/Users/sgn04088/Library/Caches/go-build

마지막 줄이 빈 것은 GOBIN이 설정돼 있지 않다는 뜻이다. 인자 없이 go env만 치면 전체 목록이 나온다.

:::info 이 문서의 경로에 대해 위는 이 기계(macOS, 사용자 sgn04088)의 실제 출력이다. Linux에서는 GOCACHE~/.cache/go-build, Windows에서는 %LocalAppData%\go-build가 된다. GOROOT와 GOPATH는 OS와 무관하게 같은 형태다. :::

GOROOT — Go 자신이 설치된 곳

ls /usr/local/go
CONTRIBUTING.md
LICENSE
PATENTS
README.md
SECURITY.md
VERSION
api
bin
codereview.cfg
doc
go.env
lib
misc
pkg
src
test
  • bin/gogofmt 실행 파일
  • src/표준 라이브러리 전체 소스. fmt, net/http 구현이 여기 있다. 나중에 "이 함수가 실제로 뭘 하지?" 싶을 때 직접 열어 보게 될 디렉터리다.
  • pkg/tool/ — 컴파일러, 링커, vet 같은 내부 도구
  • go.env — GOTOOLCHAIN·GOPROXY 등의 기본값이 적힌 파일

GOROOT는 직접 설정할 필요가 없다. go 실행 파일이 자기 위치로부터 알아서 계산한다. export GOROOT=...를 시키는 문서를 보면 대개 아주 오래된 글이다. 툴체인을 두 개 깔고 GOROOT를 손으로 지정했다가 서로 엇갈리는 사고가 흔하다.

GOPATH — 남은 역할은 두 개뿐

ls -l ~/go
drwxr-xr-x@ 8 sgn04088 staff 256 Aug 8 13:11 bin
drwxr-xr-x@ 5 sgn04088 staff 160 Oct 16 2025 pkg

기본값은 $HOME/go(Windows는 %USERPROFILE%\go)다. 모듈 시대에 GOPATH가 하는 일은 이 두 디렉터리를 담아 두는 것뿐이다.

  • $GOPATH/pkg/mod — 내려받은 의존성이 저장되는 모듈 캐시
  • $GOPATH/bingo install이 만든 실행 파일이 놓이는 곳

GOPATH 시대에는 무엇이 달랐나

2018년 이전(모듈 이전)에는 이랬다.

  • 모든 Go 코드가 $GOPATH/src 안에 있어야 했다. 프로젝트를 ~/projects/myapp에 두면 빌드가 안 됐다. ~/go/src/github.com/you/myapp이어야 했다.
  • 버전 개념이 없었다. go get은 저장소의 기본 브랜치를 그냥 받아 왔다. 두 프로젝트가 같은 라이브러리의 다른 버전을 요구하면 방법이 없었다. dep, glide, govendor 같은 서드파티 도구가 그 틈을 메웠다.
  • $GOPATH/src가 곧 import 경로였다. 디렉터리 구조가 이름 공간이었다.

Go 1.11의 모듈, Go 1.16의 모듈 기본화로 이 전부가 사라졌다. 지금은 아무 디렉터리에서나 go mod init을 하면 그게 프로젝트다.

:::warning 오래된 글을 구별하는 신호 다음 중 하나라도 있으면 GOPATH 시대의 글이다. 그대로 따라 하지 말자.

  • export GOPATH=...를 설정하라고 한다
  • 프로젝트를 $GOPATH/src 아래에 만들라고 한다
  • GO111MODULE=on을 켜라고 한다 (지금은 그게 기본값이다)
  • dep init, glide install 같은 도구가 나온다 :::

GOMODCACHE — 의존성이 실제로 저장되는 곳

go env GOMODCACHE
/Users/sgn04088/go/pkg/mod

내려받은 모듈은 버전별 디렉터리로 풀려서 저장된다.

ls -d ~/go/pkg/mod/golang.org/x/*
/Users/sgn04088/go/pkg/mod/golang.org/x/arch@v0.28.0
/Users/sgn04088/go/pkg/mod/golang.org/x/exp
/Users/sgn04088/go/pkg/mod/golang.org/x/exp@v0.0.0-20231110203233-9a3e6036ecaa
/Users/sgn04088/go/pkg/mod/golang.org/x/exp@v0.0.0-20250620022241-b7579e27df2b
/Users/sgn04088/go/pkg/mod/golang.org/x/mod@v0.23.0

버전이 경로에 박혀 있어서 여러 버전이 동시에 공존한다. 프로젝트 A가 x/exp의 2023년 버전을, 프로젝트 B가 2025년 버전을 써도 충돌하지 않는다. node_modules처럼 프로젝트마다 복사본을 두지 않으므로 디스크도 아낀다.

두 가지 특이한 점

1. 캐시는 읽기 전용이다.

ls -ld ~/go/pkg/mod/golang.org/x/tools@v0.20.0
dr-xr-xr-x@ 26 sgn04088 staff 832 Jul 31 2025 /Users/sgn04088/go/pkg/mod/golang.org/x/tools@v0.20.0

쓰기 권한이 없다. 실수로 고치면 이렇게 된다.

permission denied: /Users/sgn04088/go/pkg/mod/golang.org/x/tools@v0.20.0/LICENSE

의도적이다. 의존성 소스를 로컬에서 몰래 고치고 "내 기계에서는 되는데"가 되는 상황을 막는다. 정말로 고쳐야 한다면 포크하고 replace 지시자를 쓴다(6-4에서 다룬다).

2. 대문자가 !소문자로 인코딩된다.

ls ~/go/pkg/mod/github.com | head -8
!abirdcfly
!admin!benni
!alwx!sin
!antonboom
!burnt!sushi
!click!house
!djarvur
!masterminds

github.com/BurntSushi/tomlgithub.com/!burnt!sushi/toml로 저장돼 있다. 대소문자를 구분하지 않는 파일 시스템(macOS 기본값, Windows)에서 Foofoo가 충돌하는 것을 막기 위한 인코딩이다. 처음 보면 캐시가 깨진 줄 알기 쉽다. 정상이다.

원본 다운로드는 따로 보관된다

ls ~/go/pkg/mod/cache
download
lock

cache/download에는 압축된 원본 .zip과 해시가 들어 있다. go.sum 검증에 쓰이는 게 이쪽 사본이다. 풀린 디렉터리를 지워도 여기서 다시 복원할 수 있어서, 네트워크 없이도 재빌드가 된다.

GOCACHE — 빌드 결과 캐시

go env GOCACHE
/Users/sgn04088/Library/Caches/go-build

컴파일된 패키지 오브젝트와 테스트 실행 결과가 여기 저장된다. 앞 챕터에서 본 콜드 빌드 3.4초 / 웜 빌드 0.06초의 차이가 바로 이 캐시다.

내용물은 해시로 나뉜 디렉터리라 사람이 읽을 만한 게 없다.

ls ~/Library/Caches/go-build | head -8
00
01
02
03
04
05
06
07

go test를 두 번 연속 돌렸을 때 (cached)가 뜨는 것도 이 캐시 덕분이다. 입력(소스, 빌드 플래그, 환경 변수)이 하나도 안 바뀌면 결과를 그대로 재사용한다.

GOCACHE는 손으로 지워도 안전하다. 지우면 다음 빌드가 느려질 뿐이다.

GOBIN — go install의 목적지

go env GOBIN

비어 있으면 $GOPATH/bin이 쓰인다. 이 기계에서는 이렇다.

ls ~/go/bin
dlv
gofumpt
goimports
golangci-lint
gopls
staticcheck

전부 go install로 깐 개발 도구들이다.

경고

$GOPATH/bin이 PATH에 없으면 go install로 설치한 도구를 이름으로 실행할 수 없다. 셸 설정에 다음을 넣어 두자.

export PATH=$PATH:$(go env GOPATH)/bin

go env -w로 설정 바꾸기

환경 변수를 셸 설정 파일에 넣는 대신, Go 전용 설정 파일에 저장할 수 있다.

go env GOENV
/Users/sgn04088/Library/Application Support/go/env
go env -w GOBIN=$HOME/go/bin
go env GOBIN
/Users/sgn04088/go/bin

파일에는 이렇게 기록된다.

GOBIN=/Users/sgn04088/go/bin

되돌리려면 -u(unset)다.

go env -u GOBIN

우선순위는 셸 환경 변수 > go env -w 설정 파일 > 기본값이다. 그래서 셸에 export GOFLAGS=...가 남아 있으면 go env -w GOFLAGS=...가 무시되는 것처럼 보인다. "분명히 바꿨는데 안 먹는다" 싶으면 env | grep GO로 셸 쪽을 먼저 확인하자.

캐시 비우기

디스크가 얼마나 차 있는지부터 보자.

du -sh ~/go/pkg/mod ~/Library/Caches/go-build
724M /Users/sgn04088/go/pkg/mod
290M /Users/sgn04088/Library/Caches/go-build

지우는 명령은 두 개다. -n을 붙이면 실행하지 않고 무슨 일을 할지만 보여 준다. 위험한 명령은 항상 이걸로 먼저 확인하는 습관을 들이자.

go clean -n -modcache
rm -rf /Users/sgn04088/go/pkg/mod
go clean -modcache # 실제로 삭제
go clean -cache # 빌드 캐시 삭제

go clean -modcache는 모듈 캐시를 통째로 날린다. 다음 빌드에서 의존성을 전부 다시 내려받는다. 이걸 쓸 상황은 사실 드물다.

  • 캐시가 손상됐다는 확신이 있을 때 (해시 불일치 에러가 계속 날 때)
  • 디스크를 확보해야 할 때

:::danger 읽기 전용 권한 때문에 실패할 수 있다 캐시 디렉터리는 읽기 전용이라 rm -rf ~/go/pkg/mod를 직접 치면 권한 오류가 난다. 반드시 go clean -modcache를 쓰자. 이 명령은 권한을 먼저 풀고 지운다. :::

흔히 하는 실수

  • GOROOT를 손으로 export한다. Go를 업그레이드하거나 GOTOOLCHAIN이 다른 버전으로 전환하면 값이 어긋난다. 설정하지 말자.
  • 프로젝트를 ~/go/src 아래에 만든다. 지금은 아무 데나 두면 된다. ~/go 안에 두면 오히려 모듈 캐시와 섞여 헷갈린다.
  • $GOPATH/bin을 PATH에 넣지 않는다. go install은 성공했는데 명령을 못 찾는다.
  • go clean -modcache를 문제 해결의 첫 수단으로 쓴다. 재다운로드에 몇 분이 날아간다. 대부분의 의존성 문제는 go mod tidy로 해결된다(6-3).
  • 모듈 캐시의 소스를 편집한다. 권한이 막아 주지만, chmod로 뚫고 고치면 다음 go clean 때 사라진다.

정리

변수이 기계의 값역할직접 설정?
GOROOT/usr/local/goGo 설치 위치, 표준 라이브러리 소스하지 말 것
GOPATH~/gopkg/modbin을 담는 상위 디렉터리거의 불필요
GOMODCACHE~/go/pkg/mod내려받은 의존성 (읽기 전용, 버전별)거의 불필요
GOCACHE~/Library/Caches/go-build빌드·테스트 결과 캐시불필요
GOBIN(비어 있음 → ~/go/bin)go install 결과물 위치선택
  • 소스 코드는 아무 디렉터리에나 두면 된다. GOPATH 시대는 끝났다.
  • 설정 확인은 go env, 변경은 go env -w, 되돌리기는 go env -u.
  • $GOPATH/bin은 PATH에 넣어 두자.

연습문제

  1. go env 전체 출력을 보고, 이 챕터에서 다루지 않은 변수 중 이름만으로 역할을 짐작할 수 있는 것 다섯 개를 골라 보자. go help environment로 답을 맞춰 보자.

  2. go env -w GOFLAGS=-v를 설정한 뒤 아무 프로젝트나 go build해 보자. 출력이 어떻게 달라지는가? 끝나면 go env -u GOFLAGS로 되돌리자.

  3. 모듈 캐시에서 아무 라이브러리나 하나 골라 소스를 열어 읽어 보자 (ls ~/go/pkg/mod/github.com/...). 그 디렉터리에 파일을 하나 만들려고 하면 어떤 일이 생기는가? 왜 그렇게 설계했을지 설명해 보자.