前面分享過用 MSBuild Target 處理 Docker 編譯、將映像檔另存 tar、scp 上傳 Linux 端、ssh 執行指令載入映象檔等一連串作業的組合拳,這篇打算再朝 CI/CD 前進一小步 - 改用 Container Image Registry 管理映像檔。

如果 GitHub 上的專案,Github 有自己的容器映像註冊服務 - GHCR (GitHub Container Registry),免費用戶可存放 500MB (月傳輸量 1GB)、Pro 訂閱則有 2GB (月傳輸量 1GB),GHCR 與 GitHub Repository、Actions、權限系統高度整合,是很好的選擇。若是企業組織,Azure DevOps Server (原 TFS Server)有整套的 CI/CD 解決方案,還能做到全地端跟 AD 無縫整合,加上為商用軟體有廠商支援,應該是許多公司的首選。

不過,這篇的討論情境是我的居家服務 Side Project,有一堆私人專案使用自架 Gitea 伺服器 做版控,想將程式碼或容器映像檔完全封閉在內網,與 Github、Docker Hub 這些雲端方案無緣,映象註冊服務也得自己架設。

之前自架過 Docker Regisry,因 Docker 程式預設只接受 HTTPS 安全連線,記得當時光自簽憑證就費了不少手腳。前陣子花點小錢買了個 DNS 網域名稱,結合 Cloudflare DNS API 走 DNS-01 取得萬用字元 TLS 憑證,憑證管理成本瞬間歸零,哈! 但,這回要繼續用 Docker 官方 Registry嗎?

查了一下,發現 Gitea 本身就內建容器註冊服務 (Container Registry),其遵循 OCI 標準,也支援 Docker。我的版控主機是用 Gitea,直接用它內建的容器註冊服務自然是首選。

整理使用 Gitea 管理容器映像的步驟:

  1. 建立專用 Token
    在 User Settings / Applications / Manage Access Tokens 輸入 Token 名稱,只開啟 Package 的讀寫權限,如此,即便 Token 不幸外洩,風險範圍可縮小到只有 Package (Container Image):

    建立 Token 後,記下十六進位數字的 Token 值備用(紅箭頭處,只會顯示這一次,要抄好):
  2. 使用 docker login gitea.domain-name.net -u <username> 登入,密碼用剛才產生的 Token 值
  3. 若有用 nginx 當 Reverse Proxy,建議在 config 加上 client_max_body_size 512M; 放大上傳長度限制以免 push 時出現 HTTP 413
  4. 在本機先建置好 Docker 映像,為映像加上 tag,格式為 gitea.domain-name.net/<username>/<img-name>:<verion>,為方便後續使用,建議同時建立數字版號及 latest Tag
  5. 使用指令 docker push gitea.domain-name.net/<username>/<img-name>:<verion> 推送到 Gitea,大功告成!

接著,進入 https://gitea.domain-name.net/<username> 個人頁,點選 Packages 就能看到剛才上傳的容器映像檔囉!

至於 Linux 端,我在 Debian 跑 docker login 時收到警告說我的密碼是用明碼儲存不安全:

~$docker login gitea.my-domain.net -u jeffrey
Password:

WARNING! Your credentials are stored unencrypted in '/home/jeffrey/.docker/config.json'.
Configure a credential helper to remove this warning. See
https://docs.docker.com/go/credential-store/

Login Succeeded

坦白說,這裡可以牙一咬「明碼就明碼,駭客都能登進來,還有什麼好防?」,睜隻眼閉隻眼就算了。不過,我還是把握機會,學習在 Linux 安全地保存密碼 (結果在這段花的時間是 Gitea 部分的三倍,噗)。

Linux 保存密碼的選擇蠻多,但許多需配合 GNOME 等桌面環境,我的應用場景主要是 ssh 操作與自動部署,故選擇 CLI 類的解決方案。以下是我測試成功的做法:

sudo apt update
sudo apt install -y pass gnupg2 golang-docker-credential-helpers
which docker-credential-pass # 顯示 /usr/bin/docker-credential-pass
# 建立 GPG key
gpg --full-generate-key
# 列出 Key
gpg --list-secret-keys --keyid-format LONG

AI 及一些教學建議用 RSA,但依先前的研究心得(GPG 筆記 - 產生 ECC 金鑰及加解密檔案),我選擇用預設選項 ECC。 --list-secret-keys 列出的結果範例如下,找出 sec ed25519/0123456789ABCDEF 的 0123456789ABCDEF 部分:

gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
/home/jeffrey/.gnupg/pubring.kbx
--------------------------------
sec   ed25519/0123456789ABCDEF 2026-05-19 [SC]
      E33AB706BB79C199ED30775ABA445A7B6F36192F
uid                 [ultimate] jeffrey <[email protected]>
ssb   cv25519/FEDCBA987654321 2026-05-19 [E]

執行 pass init 0123456789ABCDEF 建立 Password Store:

修改 ~/.docker/config.json 把內容置換成 { "credsStore": "pass" } 後,重新執行 docker login gitea.my-domain.net -u jeffrey 登入,用 pass ls 查詢可看到己存入資料:

之後測試 docker pull gitea.domain-name.net/<username>/<img-name>:<verion> 若順利下載就成功了~

這裡有個小眉角是 git pull / push 時可能會冒出以下錯誤 error getting credentials - err: exit status 1, out: exit status 2: gpg: public key decryption failed: No such file or directory gpg: decryption failed: No such file or directory,Docker 使用 pass(GPG 的密碼管理器)後,當 GPG agent 鑰匙圈過期或鎖定時就會出現這類錯誤。

解法 1 手動輸入密碼

隨意對一段文字做簽章觸發密碼輸入,之後密碼會 Cache 一陣子,再執行 git pull 便會成功。

export GPG_TTY=$(tty) # 指定由 TTY 輸入密碼,可設在 ~/.bashrc
echo "plain" | gpg --clearsign > /dev/null

解法 2 自動化腳本時改明碼輸入

GPG 的密碼輸入故意隔離在獨立程式 pinentry (pinentry-gnome, pinentry-curses, pinetry-tty),不讓主程式直接碰觸密碼以提高安全性,但不適用無人參與的自動化腳本情境。

要以明碼密碼解鎖 GPG 解密 Token 程序挺複雜的。先要設定 ~/.gnupg/gpg-agent.conf,加入 allow-loopback-pinentry;設定 '~/.gnupg/gpg.conf' 加入 pinentry-mode loopback,修改後 gpgconf --kill gpg-agent 重啟 gpg-agent。

docker pull/push 前用以下程式解鎖 gpg-agent 的加密 subkey cache,之後便可用記憶的密碼完成 Gitea 登入,呼~

echo "<gpg-password>" | gpg \
  --batch \
  --passphrase-fd 0 \        # 從 fd 0(stdin)讀取密碼
  --pinentry-mode loopback \ # 使用 loopback(不叫 pinentry)
  -d ~/.password-store/docker-credential-helpers/Z2...lu/jeffrey.gpg 2>&1 /dev/null
# gpg-agent 有解密金鑰 Cache,可取出 Gitea 密碼完成登入
docker compose pull

總之,使用 Gitea Push 及 Pull 容器技能習得,下一步就可以來研究實現 git push 後容器自動換版的自動化流程囉~

Explains using Gitea’s built-in Container Registry for private Docker image management, including token setup, push/pull workflow, nginx limits, secure Docker credential storage with pass and GPG, and handling GPG agent issues for automated deployment.


Comments

Be the first to post a comment

Post a comment