Menu

go run, go build, go install 비교: Go 컴파일과 실행

go run, go build, go install이 각각 하는 일, Go가 바이너리 이름을 정하는 방식, GOOS와 GOARCH로 크로스 컴파일하는 법, 커밋 전에 돌릴 go fmt와 go vet 검사를 다룹니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

go run은 프로그램을 한 번에 컴파일하고 실행하며 바이너리를 남기지 않습니다. go build는 컴파일해서 실행 파일을 현재 디렉터리에 남깁니다. go install은 컴파일해서 실행 파일을 $HOME/go/bin에 둡니다. 셋 모두 네이티브 기계어로 컴파일하며, 어느 것도 인터프리트하지 않습니다.

명령컴파일실행바이너리 보관바이너리 위치
go run .아니요임시 디렉터리, 이후 삭제
go build아니요현재 디렉터리(또는 -o path)
go install아니요$GOBIN, 없으면 $GOPATH/bin

아래 프로그램이 이 페이지의 예제에서 쓰입니다. 에디터는 go run처럼 실행합니다.

go run

go run은 패키지(보통 현재 디렉터리를 뜻하는 .)나 .go 파일 목록을 받습니다:

go run .
go run main.go
go run ./cmd/server

패키지 뒤에 오는 것은 모두 프로그램에 인자로 전달됩니다:

go run . --port 8080 verbose
version: dev
built for: linux/amd64
args: [--port 8080 verbose]

go run main.go보다 go run .을 쓰세요. 파일 이름을 지정하면 그 파일만 컴파일되므로, 패키지에 파일이 하나 더 생기는 순간 그 파일에 정의된 함수가 undefined로 보고됩니다. 이 오류는 패키지와 import 페이지에서 자세히 다룹니다.

go run은 원격 프로그램을 설치하지 않고 특정 버전으로 실행할 수도 있어서 코드 생성기에 편리합니다:

go run golang.org/x/tools/cmd/stringer@v0.30.0 -type=Color

go build

go build는 현재 디렉터리의 패키지를 컴파일합니다. main 패키지라면 실행 파일을 쓰고, 라이브러리 패키지라면 컴파일해서 오류를 보고한 뒤 결과를 버립니다.

go mod init example.com/hello
go build
ls
go.mod  hello  main.go

바이너리 이름은 다음 규칙을 따릅니다:

실행한 명령바이너리 이름
모듈 example.com/hello에서 go buildhello
go build ./cmd/serverserver
go build main.gomain (첫 번째 파일 이름을 따름)
go build -o bin/app .bin/app
위의 어느 것이든 GOOS=windows와 함께같은 이름에 .exe 추가

자주 쓰게 될 형태가 두 가지 더 있습니다:

go build ./...      # compile every package in the module
go vet ./...        # static checks, covered below

./...는 "이 디렉터리와 그 아래의 모든 디렉터리"를 뜻합니다. main 패키지가 여럿일 때 go build ./...는 바이너리를 쓰지 않으며, "전부 컴파일되는가"를 빠르게 확인하는 용도입니다.

유용한 빌드 플래그

플래그하는 일
-o name출력 파일이나 디렉터리
-v컴파일하는 패키지 이름 출력
-race데이터 레이스 탐지기를 넣어 빌드(더 느리고 바이너리가 큼, 테스트용)
-trimpath재현 가능한 빌드를 위해 바이너리에서 로컬 파일 시스템 경로 제거
-ldflags "-s -w"심볼 테이블과 DWARF 디버그 정보를 제거해 바이너리를 작게 만듦
-ldflags "-X main.version=1.4.0"링크 시점에 문자열 변수 설정
-tags name//go:build name 제약이 걸린 파일 포함

대부분의 Go 프로젝트는 -X 플래그로 바이너리에 버전을 새겨 넣습니다. 패키지 수준 string 변수에만 동작합니다(상수에는 안 됩니다):

go build -o app -ldflags "-X main.version=1.4.0" .
./app
version: 1.4.0
built for: linux/amd64
args: []

바이너리는 모듈 버전과, Go 1.18부터는 빌드에 쓰인 VCS 커밋도 기록합니다. go version -m ./app이 이를 출력하고, 프로그램은 runtime/debug.ReadBuildInfo로 읽을 수 있습니다.

go install

go installgo build와 똑같이 빌드한 뒤 실행 파일을 $GOBIN으로, GOBIN이 설정되지 않았다면 $GOPATH/bin(기본값 $HOME/go/bin)으로 옮깁니다:

go install .
go env GOPATH

가장 흔한 용도는 Go로 작성된 도구 설치입니다. @version을 붙이면 go.mod를 건드리지 않고 프로그램을 설치합니다:

go install golang.org/x/tools/gopls@latest
go install honnef.co/go/tools/cmd/staticcheck@latest

설치한 뒤에도 셸이 도구를 찾지 못한다면 $HOME/go/binPATH에 없는 것입니다. 셸 프로필에 export PATH=$PATH:$(go env GOPATH)/bin을 추가하세요.

GOOS와 GOARCH로 크로스 컴파일

Go는 어떤 머신에서든 추가 툴체인 없이 다른 운영체제나 CPU용으로 빌드할 수 있습니다. 빌드 명령에 환경 변수 두 개를 설정하세요:

GOOS=linux GOARCH=amd64 go build -o app-linux-amd64 .
GOOS=linux GOARCH=arm64 go build -o app-linux-arm64 .
GOOS=darwin GOARCH=arm64 go build -o app-macos .
GOOS=windows GOARCH=amd64 go build -o app.exe .

PowerShell에서는 환경 변수를 다르게 설정합니다:

$env:GOOS = "linux"; $env:GOARCH = "amd64"; go build -o app-linux .

가장 흔한 조합:

GOOSGOARCH대상
linuxamd64대부분의 서버와 컨테이너
linuxarm64AWS Graviton, 64비트 OS를 쓰는 Raspberry Pi
darwinarm64Apple Silicon Mac
darwinamd64Intel Mac
windowsamd6464비트 Windows
jswasm브라우저의 WebAssembly

go tool dist list는 지원되는 모든 조합을 출력합니다(Go 1.24 기준 48개).

크로스 컴파일이 이렇게 쉬운 것은 순수 Go 코드일 때뿐입니다. cgo(C 코드, 예를 들어 일부 SQLite 드라이버)를 쓰는 패키지는 대상용 C 크로스 컴파일러가 필요합니다. 크로스 컴파일할 때는 cgo가 기본적으로 꺼지며, CGO_ENABLED=0을 명시하면 완전히 정적인 Linux 바이너리도 얻을 수 있습니다. 최소한의 scratch나 distroless 컨테이너 이미지에 원하는 것이 바로 그것입니다.

go fmt와 go vet

모든 작업 흐름과 CI에 들어가야 할 검사가 두 가지 있습니다.

go fmt는 파일을 공식 스타일 하나로 다시 씁니다. 탭, 정렬된 필드, 정규화된 공백입니다. gofmt -l -w를 감싼 명령입니다:

go fmt ./...
gofmt -l .     # list files that are not formatted; empty output means clean

go vet은 컴파일은 되지만 거의 확실히 잘못된 코드를 보고합니다. Printf 형식 불일치가 대표적인 예입니다:

package main

import "fmt"

func main() {
	count := 3
	fmt.Printf("%s items\n", count)
}
go vet ./...
# example.com/hello
# [example.com/hello]
./main.go:7:2: fmt.Printf format %s has arg count of wrong type int

이 프로그램은 컴파일되고 %!s(int=3) items를 출력하는데, vet이 잡으라고 있는 바로 그런 버그입니다. 다른 vet 검사로는 sync.Mutex를 값으로 복사하는 것, 도달할 수 없는 코드, 문법이 잘못된 구조체 태그, 호출되지 않는 context.CancelFunc 등이 있습니다. go test는 이 검사 중 일부를 자동으로 실행하고, go build는 하나도 실행하지 않습니다.

그 밖의 go 하위 명령

명령용도
go test ./...테스트 실행
go mod tidy빠진 의존성 추가, 쓰지 않는 의존성 제거
go get pkg@version의존성 추가 또는 변경
go clean -cache빌드 캐시 비우기
go envGo 설정 출력
go doc fmt.Println터미널에서 문서 보기
go list -m all빌드에 쓰이는 모든 모듈 나열

두 번째 빌드가 빠른 이유

Go는 컴파일된 패키지를 빌드 캐시(go env GOCACHE)에 저장합니다. 다시 빌드할 때는 소스나 의존성이 바뀐 패키지만 다시 컴파일하므로, 바뀐 것이 없는 프로젝트에서 go run .은 거의 즉시 시작합니다. Go 버전이나 환경 변수를 바꾼 뒤 빌드가 이상하게 동작한다면 go clean -cache로 비우세요. 그럴 일은 드뭅니다.

자주 묻는 질문

go run과 go build의 차이는 무엇인가요?

go run은 프로그램을 임시 디렉터리에 컴파일해서 실행하고 바이너리를 버립니다. go build는 프로그램을 컴파일해서 바이너리를 현재 디렉터리에 쓰므로 다시 실행하거나 다른 곳으로 복사할 수 있습니다. 개발하는 동안에는 go run을, 실행 파일이 필요할 때는 go build를 쓰세요.

go build의 출력 파일 이름은 어떻게 정하나요?

-o를 씁니다: go build -o myapp .myapp을 씁니다(Windows에서는 -o myapp.exe라고 직접 쓰세요). -o가 없으면 go build는 패키지 import 경로의 마지막 요소를 따서, .go 파일을 넘겼다면 첫 번째 파일을 따서 바이너리 이름을 정하고, Windows용으로 빌드할 때는 .exe를 붙입니다.

Go를 Linux나 Windows용으로 크로스 컴파일하려면 어떻게 하나요?

빌드할 때 GOOSGOARCH를 설정합니다: GOOS=linux GOARCH=amd64 go build -o app-linux . 또는 GOOS=windows GOARCH=amd64 go build .(.exe를 만듭니다). 순수 Go 코드라면 추가 툴체인이 필요 없습니다. go tool dist list는 지원되는 모든 조합을 출력합니다.

go install은 바이너리를 어디에 두나요?

$GOBIN이 설정되어 있으면 그곳에, 아니면 $GOPATH/bin에 두며, 기본값은 $HOME/go/bin입니다. 설치한 도구를 이름으로 실행하려면 그 디렉터리를 PATH에 추가하세요. go install example.com/tool@latest는 도구를 모듈에 추가하지 않고 설치합니다.

go build가 go vet을 실행하나요?

아니요. go build는 컴파일만 합니다. go testgo vet 검사의 일부를 자동으로 실행하지만, 전체 검사를 하려면 보통 CI에서 gofmt -l . 옆에 go vet ./...를 직접 실행하세요.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기