昨天帶大家認識了什麼是 Docker?後,今天就來分享如何把我們寫過的專案推上 Docker Hub 為整個社群貢獻一份心力吧!
這是一套把專案建置成容器的過程,但我們其實只需要轉成映像檔就能順利地推上 Docker Hub,但我還是會帶大家完整做一次,那我們就開始吧!

(圖片來源:https://medium.com/swlh/understand-dockerfile-dd11746ed183)
首先我們需要在我們的專案下在額外創建一個Dockerfile的檔案(跟我們之前介紹到的 Makefile 是同一個意思)

如果是用 GoLand 開發的,可以很輕鬆的直接建立現成模板。
# 第 1 階段:建置階段
FROM --platform=$BUILDPLATFORM golang:1.23.2-alpine3.20 AS builder
# 設定工作目錄
WORKDIR /app
# 定義目標作業系統和架構的建置參數
ARG TARGETOS
ARG TARGETARCH
# 定義 Go 快取的建置參數
ARG GOCACHE=/app/.cache/go-build
ARG GOMODCACHE=/app/.go/pkg/mod
# 設定 Go 的環境變數
ENV GOCACHE=${GOCACHE} \
    GOPATH=/app/.go \
    GOMODCACHE=${GOMODCACHE}
# 複製 go.mod 和 go.sum 檔案
COPY go.mod go.sum ./
# 使用快取下載相依套件
RUN --mount=type=cache,target=${GOMODCACHE} \
    go mod download
# 複製專案的所有檔案
COPY . .
# 建立快取目錄
RUN mkdir -p ${GOCACHE} ${GOMODCACHE}
# 使用快取建置 Go 應用程式
RUN --mount=type=cache,target=${GOCACHE} \
    --mount=type=cache,target=${GOMODCACHE} \
    CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o my-first-go-service
# 第 2 階段:最終映像
FROM alpine:3.20
# 設定工作目錄
WORKDIR /root/
# 從建置階段複製編譯好的二進位檔
COPY --from=builder /app/my-first-go-service .
# 開放應用程式的埠號
EXPOSE 8080
# 定義預設的執行指令來啟動應用程式
CMD ["./my-first-go-service"]
(無關緊要的小提示:程式區段因為沒有 Dockerfile 的語法高亮格式,所以在尋找格式的途中,突然發現 swift 可以直接無縫銜接上,不愧是我大🍎,太神啦XD)
(無關緊要的小提示2:因為swift的註解是 // ,但是 Dockerfile 的註解是 # 所以如要直接使用上面內容,請手動修正註解~)
第一段的
From我是從 (📎Golang 官方映像檔)中尋找的,如果版本過時了,請再從此連結中查看最新版的寫法。
| 關鍵字 | 描述 | 
|---|---|
| FROM | 指定基礎映像檔, AS用於命名階段,方便多階段建置中引用該階段。 | 
| WORKDIR | 設定 Docker 容器中的工作目錄,後續指令會在該目錄下執行。 | 
| ARG | 定義建置過程中的參數,這些參數可以在建置過程中設置特定值。 | 
| ENV | 設定環境變數,這些變數在容器執行期間會被保留並使用。 | 
| COPY | 將檔案或目錄從本地檔案系統複製到容器內指定的路徑。 | 
| RUN | 執行指定的命令,例如安裝相依套件、建置程式等。在建置階段時運行,並將結果保存到映像檔中。 | 
| --mount=type=cache | 用於快取目錄,減少重複下載或建置相依檔案的時間。 | 
| CGO_ENABLED | 設定 Go 編譯選項,用來控制是否啟用 CGO(C 語言擴展)。 | 
| GOOS | 設定要編譯的 Go 應用程式的目標作業系統(例如 Linux、Windows)。 | 
| GOARCH | 設定要編譯的 Go 應用程式的目標架構(例如 amd64、arm64)。 | 
| EXPOSE | 宣告容器中開放的埠號,這通常用來告訴其他開發者該應用程式會在哪個埠上運行。 | 
| CMD | 指定容器啟動時要執行的命令。 | 
參考資料:https://itnext.io/blazing-fast-golang-docker-builds-1e8829f743ca
從這裡開始,以下內容皆在專案下的 terminal 執行
go env GOCACHE GOMODCACHE
</* Output: */>
/Users/imac/Library/Caches/go-build
/Users/imac/go/pkg/mod
# 設置 GOCACHE 的路徑
export GOCACHE=<#LOCAL_GOCACHE#>
# 設置 GOMODCACHE 的路徑
export GOMODCACHE=<#LOCAL_GOMODCACHE#>
docker build --no-cache --compress \
  --platform <#platform1#>,<#platform2#> \
  --build-arg GOCACHE=${GOCACHE} \
  --build-arg GOMODCACHE=${GOMODCACHE} \
  -t <#DOCKER_HUB_USERNAME#>/<#IMAGE_NAME#>:<#IMAGE_TAG#> .
# example
docker build  --no-cache --compress --platform linux/amd64,linux/arm64 --build-arg GOCACHE=${GOCACHE} --build-arg GOMODCACHE=${GOMODCACHE} -t mia1001/my-first-service:latest . 
--no-cache:強制不使用本地快取來進行映像的建構。雖然這會拖慢建置速度,但是這麼做可以減少外部因素導致建置映像檔失敗的問題。
--compress:這個選項可以減少建構的映像大小,透過壓縮圖層來優化映像
--platform linux/amd64,linux/arm64:platform 讓我們能建置多平台的映像檔(小提示:這裡的多平台指的是不同架構的CPU,我們的OS還是只能為同一個,因為我們在前面的 Dockerfile 開頭就是指定我們可以運行的OS類型了)
--build-arg:建置時參數。這些參數會被傳遞到 Dockerfile 中,可以用來設定一些環境變數,並且僅在映像檔建置過程中有效。
t:標記映像檔,格式為 username/repository:tag。latest 是標籤,可以根據需要更改。- 最後的
.:表示 Dockerfile 位於當前目錄。
<#內容#>:這是在 Xcode 裡面可以去替換內容的寫法,以下都用這種方式來做替換說明。
無版本是屬於通用型的,如果不是很明白的話,希望這篇問題紀錄能幫助你更好理解。)因為我們的 platform 正常來說都會搭配 buildx 來做使用,但是你可以透過下列步驟來省去這個麻煩事項。
Docker app,然後我們從右下方的通知中,來去找到 Multi-platform image 下的 Enable 選項來做開啟。
Enable and restart 就完成了!
在推送到 Docker Hub 之前,我們可以先在本地測試映像檔是否運行正常。
docker run -d \
  -p <#CONTAINER_PORT#>:<#LOCAL_PORT#> \
  --name <#CONTAINER_NAME#> \
  <#DOCKER_HUB_USERNAME#>/<#IMAGE_NAME#>:<#IMAGE_TAG#>
# example
docker run -d -p 8080:8080 --name my-go-app-container mia1001/my-first-go-service:latest
-d:以分離模式運行容器。
-p:將容器的 8080 端口映射到本地的 8080 端口(根據您的應用調整)。
--name:為容器命名。
mia1001/my-first-go-service:latest:指定要運行的映像檔。
可以通過訪問 http://localhost:8080 來檢查應用是否正常運行。
如果確認都正常的話,我們可以從 Docker app 來做這步驟,或是你想用下面指令也可以。
// 停止正在運行的容器
docker stop <#CONTAINER_NAME#>
// 移除容器
docker rm <#CONTAINER_NAME#>
這不是確保我們能正常推送服務到我們自己的帳號下。
docker login
如有需要,系統將提示您輸入 Docker Hub 的使用者名稱和密碼。
docker push <#DOCKER_HUB_USERNAME#>/<#IMAGE_NAME#>:<#IMAGE_TAG#>
# example
docker push mia1001/my-first-go-service:latest
推送過程中,Docker 將上傳映像檔的各個層(layers)。根據映像檔的大小和網絡速度,這可能需要一些時間。
如此圖,有出現就代表成功了
我們點進去後也可以看到我們當前服務支援的平台有哪些
小提示: IMAGE_TAG 的用處就在於,我們可以很清楚知道其他人的映像檔中,不同 TAG 所支援的平台有哪些。

docker pull <#DOCKER_HUB_USERNAME#>/<#IMAGE_NAME#>:<#IMAGE_TAG#>
docker run -d -p <#CONTAINER_PORT#>:<#LOCAL_PORT#> <#DOCKER_HUB_USERNAME#>/<#IMAGE_NAME#>:<#IMAGE_TAG#>
# example
docker pull mia1001/my-first-go-service:latest
docker run -d -p 8080:8080 mia1001/my-first-go-service:latest
恭喜你!完成這最後一步後,你就能算是另一種形式的開源家了~
(前提:我明明都是很認真在做分享,但大家好像對閒話比較感興趣,真是奇怪 )
)
今年會想挑戰寫Go是因為,我也是在今年寒假才開始接觸 Golang ,雖然學過幾個月的時間,可是沒有能用在開發的地方,而且當初在學習時還沒有養成寫筆記的習慣,於是就順道寫個鐵人賽當複習,還能順便研究一下沒碰觸過的領域好像也蠻不錯的(雖然每次好像都遊走在快斷賽的邊緣)?
在這30天之中,我感覺自己有比較會去撰寫一篇教學文(前面跟後面的風格差蠻多的,但我也懶得再去調整了XD),而且很高興的部分在於,自己能從那個每天看網路上各大文章的人,轉變成分享自己的學習與收穫,那感覺確實蠻不錯的,而且在寫分享文時,總會有一股動力推著自己去嘗試研究沒接觸過的方向(畢竟我研究不出來就真的要斷賽了 )。
)。
總結而言,如果看完後這30天的教學後,想在這條路上精進的,可以多加閱讀(不同版本更新的內容)(但我通常很懶,只想看別人介紹)如[這篇] / [另一篇],或是也可以參加一年一次的GopherDay,我也有參加今年的GopherDay,雖然我感覺自己好像聽的一知半解(可能跟我太菜有關),但我還是能被現場的熱情給吸引,而且他的紀念衣我也好喜歡穿著它,總感覺穿上後就莫名有熱誠開發了?
那我的建議大概就到這邊,感謝各位觀看這30天的歷程,有緣在跟大家相見。