Docker with GoのMulti Stage Buildで"no such file or directory"と出てハマった話

対象のDockerfile。特に特殊なことは行っていない素朴なDockerfile。

FROM golang:1.14.4 as builder

WORKDIR /app
COPY . .
RUN go mod download
RUN go build path/to/main.go

FROM alpine
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/path/to/main /app/main
EXPOSE 5000
ENTRYPOINT [ "/app/main" ]

build log

$ docker build .
Sending build context to Docker daemon  24.16MB
Step 1/10 : FROM golang:1.14.4 as builder
 ---> 00d970a31ef2
Step 2/10 : WORKDIR /app
 ---> Using cache
 ---> 290d933fcd92
Step 3/10 : COPY . .
 ---> 5c338d20e119
Step 4/10 : RUN go mod download
 ---> Running in fa40233a9ad4
Removing intermediate container fa40233a9ad4
 ---> 32ededdd590c
Step 5/10 : RUN go build cmd/app/main.go
 ---> Running in 715374ae5931
Removing intermediate container 715374ae5931
 ---> 0df4e6ac142b
Step 6/10 : FROM alpine
 ---> a24bb4013296
Step 7/10 : RUN apk add --no-cache ca-certificates
 ---> Using cache
 ---> 63a365fab492
Step 8/10 : COPY --from=builder /app/main /app/main
 ---> 262cef1fcd0f
Step 9/10 : EXPOSE 5000
 ---> Running in d2cc58f47b7f
Removing intermediate container d2cc58f47b7f
 ---> 49e6a57ba3bd
Step 10/10 : ENTRYPOINT [ "/app/main" ]
 ---> Running in 887cc0dbb3a8
Removing intermediate container 887cc0dbb3a8
 ---> d5c6b638a8a7
Successfully built d5c6b638a8a7

buildしたimageを実行すると exec user process caused "no such file or directory" と出て実行が出来ない。

$ docker run -it d5c6b638a8a7
standard_init_linux.go:211: exec user process caused "no such file or directory"

Docker公式のDocumentを眺めると CGO_ENABLED=0 という変数を指定している。

docs.docker.com

CGO_ENABLEについて公式のDocumentに書いてある。

golang.org

The cgo tool is enabled by default for native builds on systems where it is expected to work. It is disabled by default when cross-compiling. > You can control this by setting the CGO_ENABLED environment variable when running the go tool: set it to 1 to enable the use of cgo, and to > 0 to disable it. The go tool will set the build constraint "cgo" if cgo is enabled. The special import "C" implies the "cgo" build constraint, as > > though the file also said "// +build cgo". Therefore, if cgo is disabled, files that import "C" will not be built by the go tool. (For more about >build constraints see https://golang.org/pkg/go/build/#hdr-Build_Constraints).

今回のMulti Stage Buildのようなビルド環境と実行環境が違うクロスコンパイル時には CGO_ENABLE=0 にする必要があるとのこと。go envで見てみると案の定 CGO_ENABLE=1 となっている。

Step 6/11 : RUN go env
 ---> Running in f2d4bf833350
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/app/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build835780014=/tmp/go-build -gno-record-gcc-switches"

Dockerfileのbuild箇所に CGO_ENABLE=0 を指定することで正常にビルドが出来ました。

FROM golang:1.14.4 as builder

WORKDIR /app
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 go build path/to/main.go
RUN go env

FROM alpine
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/path/to/main /app/main
EXPOSE 5000
ENTRYPOINT [ "/app/main" ]
$ docker build .
Sending build context to Docker daemon  24.16MB
Step 1/11 : FROM golang:1.14.4 as builder
 ---> 00d970a31ef2
Step 2/11 : WORKDIR /app
 ---> Using cache
 ---> 290d933fcd92
Step 3/11 : COPY . .
 ---> 1e985aa7595b
Step 4/11 : RUN go mod download
 ---> Running in 4f47920a2f07
Removing intermediate container 4f47920a2f07
 ---> b3af89cc0fae
Step 5/11 : RUN CGO_ENABLED=0 go build cmd/app/main.go
 ---> Running in 5418122649bb
Removing intermediate container 5418122649bb
 ---> a22ffab8bc7c
Step 6/11 : RUN go env
 ---> Running in f2d4bf833350
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/app/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build835780014=/tmp/go-build -gno-record-gcc-switches"
Removing intermediate container f2d4bf833350
 ---> 658a897f518b
Step 7/11 : FROM alpine
 ---> a24bb4013296
Step 8/11 : RUN apk add --no-cache ca-certificates
 ---> Using cache
 ---> 63a365fab492
Step 9/11 : COPY --from=builder /app/main /app/main
 ---> 4aea7dfd10cd
Step 10/11 : EXPOSE 5000
 ---> Running in 17d50b507678
Removing intermediate container 17d50b507678
 ---> e35b39616e48
Step 11/11 : ENTRYPOINT [ "/app/main" ]
 ---> Running in 2a03dd0d5636
Removing intermediate container 2a03dd0d5636
 ---> 7f1b74a479da
Successfully built 7f1b74a479da
$ docker run -it 7f1b74a479da

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.1.16
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on 127.0.0.1:5000

ref

qiita.com docs.docker.com golang.org github.com