dockergodocker-multi-stage-build

Can't execute go binary with docker multi stage build


I try to build the go app as follow, my main.go file is at cmd/app/main.go.

However, when I try running docker build --no-cache . and docker run <container_id>. It gives me exec ./bin/app: no such file or directory

I've already test that running go build -o ./bin/app ./cmd/app and ./bin/app is able to run correctly.

Here is my Dockerfile

# Build phase
FROM golang:1.20 AS builder

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download && go mod verify

COPY . .
RUN go build -o ./bin/app ./cmd/app

# Production phase
FROM alpine:3.14

WORKDIR /app

COPY --from=builder /app/bin/app ./bin/app

ENTRYPOINT [ "./bin/app" ]

I tried to access into the container docker run -it -t fyno/server/multi /bin/sh

/app # cd bin
/app/bin # ls -la
total 11636
drwxr-xr-x    2 root     root          4096 Apr 12 05:04 .
drwxr-xr-x    1 root     root          4096 Apr 12 05:04 ..
-rwxr-xr-x    1 root     root      11904381 Apr 12 05:04 app
/app/bin # ./app
/bin/sh: ./app: not found
/app/bin # 

Thanks.

how to fix the problem?


Solution

  • First, there are some problems with the paths, causing your no such file or directory error.
    I wrote a minimal Dockerfile example and renamed the app binary which was causing confusion because it was located in an app directory in your example. I hope it makes more sense now.

    Second, after fixing the paths inaccuracies in the Dockerfile, you run into a more subtle problem, when trying to run the go binary: not found, because the the golang builder image is using Debian GLIBC 2.31-13+deb11u5 2.31 while the runner image is using musl libc (x86_64) Version 1.2.2.

    The easiest fix is to set CGO_ENABLED=0 when building. If you really want to use cgo to compile, find a builder and a runner images that are compatible in this regard.
    Several alternatives and workarounds are provided for a similar question here.

    Third, you also mentioned about the .env file in a comment so I also included in the MVP a simple read-display for an environment variable injected with docker run --env ....

    .
    ├── cmd
    │   └── main.go
    ├── Dockerfile
    ├── go.mod
    └── go.sum
    

    Dockerfile:

    # Build phase
    FROM golang:1.20 AS builder
    # Next line is just for debug
    RUN ldd --version
    WORKDIR /build
    COPY go.mod go.sum ./
    RUN go mod download && go mod verify
    COPY . .
    WORKDIR /build/cmd
    RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o go-binary
    
    # Production phase
    FROM alpine:3.14
    # Next line is just for debug
    RUN ldd; exit 0
    WORKDIR /app
    COPY --from=builder /build/cmd/go-binary .
    ENTRYPOINT [ "/app/go-binary"]
    

    main.go:

    package main
    
    import (
        "os"
        "time"
    
        "github.com/rs/zerolog/log"
    )
    
    func main() {
        yourVar := os.Getenv("YOUR_VAR")
        for {
            time.Sleep(time.Second)
            log.Info().Msg(yourVar)
        }
    }
    

    Build and run:

    docker build --no-cache -t stack-overflow-go-docker:v1.0 .
    docker run --env YOUR_VAR=your-value stack-overflow-go-docker:v1.0
    
    {"level":"info","time":"2023-04-14T21:12:37Z","message":"your-value"}
    {"level":"info","time":"2023-04-14T21:12:38Z","message":"your-value"}