用 Docker 編譯並匯出執行檔 (compile & export)

Posted on  Jun 16, 2023  in  Docker  by  Amo Chen  ‐ 2 min read

最近在 macOS 用 Go 語言開發專案,不過遇到一些 Cross-Compiling 無法運作的問題,礙於時間壓力只得想辦法避開 Cross-Compiling 的問題,最直覺的方式就是直接在最終需要部署的作業系統中編譯,所以決定暫時用相同作業系統版本的 Docker container 以進行編譯,再將 Docker image 內最終編譯完成的執行檔匯出即可。

Docker 官方也提供文件指引如何匯出檔案,但是過程可能會遇到讀寫的問題,所以本文重新以比較簡單的方式介紹如何匯出 Docker image 內的檔案。

本文環境

  • macOS
  • Docker

專案結構

本文的專案結構如下,為了著重介紹匯出的部分,所以專案結構非常簡單,只有 go.mod , go.sum , main.go , Dockerfile 4 個檔案,是一個非常簡單的 Go 專案:

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

其中 main.go 的程式碼如下,是一個非常簡單的 fiber HTTP server:

package main

import (
    "log"

    "github.com/gofiber/fiber/v2"
)

func main() {
    app := fiber.New()

    app.Get("/", func (c *fiber.Ctx) error {
        return c.SendString("Hello, World!")
    })

    log.Fatal(app.Listen(":3000"))
}

安裝 fiber package 的指令如下:

$ go get github.com/gofiber/fiber/v2

Dockerfile 解說

Dockerfile 的檔案內容如下:

ARG GO_VERSION=1.20
FROM golang:${GO_VERSION} AS base
WORKDIR /src
RUN --mount=type=cache,target=/go/pkg/mod/ \
    --mount=type=bind,source=go.sum,target=go.sum \
    --mount=type=bind,source=go.mod,target=go.mod \
    go mod download -x

FROM base as build-stage
RUN --mount=type=cache,target=/go/pkg/mod/ \
    --mount=type=bind,target=. \
    mkdir /app && \
    go build -o /app/main main.go

FROM scratch AS binaries
COPY --from=build-stage /app/main /

上述的 Dockerfile 使用 multi-stage builds 技巧,在 base 階段將專案資料夾內的 go.sumgo.mod 綁定(bind)到 base container 內,並執行 go mod download -x 下載所需要的 Go packages 。

p.s. --mount=type=cache,target=/go/pkg/mod/ 是將 /go/pkg/mod/ 做為快取的意思,讓其他階段的 Docker builds 可以使用 /go/pkg/mod 這個快取,這是由於 Golang Docker image 預設的 GOPATH/go ,所以執行 go mod download -x 時, Go packages 會下載到 /go/pkg/mod/ 內,只要將這個路徑 cache 起來,其他階段的 Docker builds 就不用再額外下載一次 Go packages 。

接著,在 build-stage 階段,將當前資料夾綁定到 build-stage container 內,讓 Docker 容器可以讀取到 main.go ,並額外建立一個資料夾 /app 作為編譯指令 go build -o /app/main main.go 輸出執行檔的地方。

如果不建立 /app 資料夾作為輸出的地方的話,而執行 go build -o main main.go 指令時,就有可能遇到檔案權限的問題(錯誤訊息 read-only file system 如下所示),所以建立 1 個額外的資料夾作為輸出的地方,可以解決這個問題。

 > [build-stage 1/1] RUN --mount=type=cache,target=/go/pkg/mod/     --mount=type=bind,target=.     go build -o main main.go:
#0 5.944 command-line-arguments: go build command-line-arguments: copying /tmp/go-build4172345745/b001/exe/a.out: open main: read-only file system

binaries 階段使用 scratch image 將編譯好的檔案從 build-stage/app/main 複製到 binaries 的根目錄 / 下。

匯出 Docker image 內的檔案

最後只要執行以下指令,將 binaries 階段的檔案輸出到當前資料夾下,也就是 --target=binaries --output=. 的部分:

$ docker build --target=binaries --output=.  .

p.s. scratch image 是 Docker 設計的 1 個空的 image, 使用 scratch 可以確保我們執行上述指令時,不會輸出其他多餘的檔案,

執行成功的話,就可以看到當前資料夾多了 1 個檔名為 main 的執行檔:

$ tree .
.
├── Dockerfile
├── go.mod
├── go.sum
├── main     <--- hola!
└── main.go

這就是用 Docker 編譯並匯出檔案的方法。

重新編譯

如果需要重新編譯,請先執行以下指令,刪除 Docker builder 所有 cache 以確保每次的編譯都是乾淨的:

$ docker builder prune -af

以上, Happy Coding!

References

scratch - Docker Official Image

Export binaries | Docker Documentation

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!