還記得上一篇文章,我們在 Ubuntu 24.04 上透過 Script 呼叫 GoDaddy v3 API,打造了一個屬於自己的 DDNS 服務。不過,如果每次都要在主機上手動設定環境與排程,之後要搬移或重新部署時,還是稍微麻煩了一點。
今天就繼續把上一篇的 Script 包裝成一個可以獨立運行的 Container,就可以直接透過 QNAP 的 Container Station 部署與管理,讓整個 DDNS 服務變得更加容易移植、維護與重新部署。
我們註冊一筆 A 紀錄 life.ivancheng.tw 進行測試

我們會使用到 Docker 進行打包,可以參考之前的文章安裝。
建立 Docker 專案目錄
mkdir -p ~/godaddy-ddns
cd ~/godaddy-ddns
建立 DDNS Script
nano ddns.sh
#!/bin/sh
set -eu
# =========================================================
# Environment Variables
# =========================================================
: "${GODADDY_PAT:?GODADDY_PAT is required}"
: "${DOMAIN:?DOMAIN is required}"
: "${RECORD_NAME:?RECORD_NAME is required}"
TTL="${TTL:-600}"
CHECK_INTERVAL="${CHECK_INTERVAL:-300}"
BASE_URL="https://api.godaddy.com/v3/domains/zones/${DOMAIN}/dns-records"
echo "========================================"
echo "GoDaddy DDNS"
echo "========================================"
echo "Domain : ${DOMAIN}"
echo "Record : ${RECORD_NAME}"
echo "TTL : ${TTL}"
echo "Check interval : ${CHECK_INTERVAL}s"
echo "========================================"
while true
do
echo ""
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Checking public IP..."
# -----------------------------------------------------
# 1. Get current public IPv4
# -----------------------------------------------------
CURRENT_IP=$(curl -4 -fsS \
https://api.ipify.org)
echo "Current IP : ${CURRENT_IP}"
# -----------------------------------------------------
# 2. Get DNS record from GoDaddy
# -----------------------------------------------------
DNS_RESPONSE=$(curl -4 -fsS \
-H "Authorization: Bearer ${GODADDY_PAT}" \
"${BASE_URL}?type=A&name=${RECORD_NAME}")
# -----------------------------------------------------
# 3. Extract record information
# -----------------------------------------------------
RECORD_ID=$(echo "${DNS_RESPONSE}" | jq -r '.items[0].recordId // empty')
DNS_IP=$(echo "${DNS_RESPONSE}" | jq -r '.items[0].data // empty')
DNS_NAME=$(echo "${DNS_RESPONSE}" | jq -r '.items[0].name // empty')
DNS_TYPE=$(echo "${DNS_RESPONSE}" | jq -r '.items[0].type // empty')
DNS_TTL=$(echo "${DNS_RESPONSE}" | jq -r '.items[0].ttl // empty')
# -----------------------------------------------------
# 4. Check record exists
# -----------------------------------------------------
if [ -z "${RECORD_ID}" ]; then
echo "ERROR: DNS record not found."
echo "Response:"
echo "${DNS_RESPONSE}"
sleep "${CHECK_INTERVAL}"
continue
fi
echo "Record ID : ${RECORD_ID}"
echo "DNS IP : ${DNS_IP}"
echo "DNS TTL : ${DNS_TTL}"
# -----------------------------------------------------
# 5. Compare IP
# -----------------------------------------------------
if [ "${CURRENT_IP}" = "${DNS_IP}" ]; then
echo "IP is unchanged. No update required."
else
echo "IP has changed!"
echo "Updating GoDaddy DNS..."
# -------------------------------------------------
# 6. Build request body
# -------------------------------------------------
REQUEST_BODY=$(jq -n \
--arg name "${DNS_NAME:-${RECORD_NAME}}" \
--arg type "${DNS_TYPE:-A}" \
--arg data "${CURRENT_IP}" \
--argjson ttl "${TTL}" \
'{
name: $name,
type: $type,
data: $data,
ttl: $ttl
}')
echo "Request:"
echo "${REQUEST_BODY}"
# -------------------------------------------------
# 7. Update DNS record
# -------------------------------------------------
RESPONSE=$(curl -4 -fsS \
-X PUT \
-H "Authorization: Bearer ${GODADDY_PAT}" \
-H "Content-Type: application/json" \
-d "${REQUEST_BODY}" \
"${BASE_URL}/${RECORD_ID}")
echo "DNS update successful."
echo "Response:"
echo "${RESPONSE}"
fi
# -----------------------------------------------------
# 8. Wait
# -----------------------------------------------------
echo "Next check in ${CHECK_INTERVAL} seconds..."
sleep "${CHECK_INTERVAL}"
done
賦予腳本執行權限
chmod +x ddns.sh
建立 Dockerfile
nano Dockerfile
FROM alpine:3.22
RUN apk add --no-cache \
curl \
jq \
ca-certificates
WORKDIR /app
COPY ddns.sh /app/ddns.sh
RUN chmod +x /app/ddns.sh
ENTRYPOINT ["/app/ddns.sh"]
建立 Docker 映像檔
docker build -t godaddy-ddns:1.0 .
[+] Building 10.5s (10/10) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 219B 0.0s
=> [internal] load metadata for docker.io/library/alpine:3.22 1.5s
=> [internal] load .dockerignore 0.1s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.1s
=> => transferring context: 4.00kB 0.0s
=> [1/5] FROM docker.io/library/alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce 0.3s
=> => resolve docker.io/library/alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce 0.2s
=> CACHED [2/5] RUN apk add --no-cache curl jq ca-certificates 0.0s
=> CACHED [3/5] WORKDIR /app 0.0s
=> [4/5] COPY ddns.sh /app/ddns.sh 0.6s
=> [5/5] RUN chmod +x /app/ddns.sh 1.5s
=> exporting to image 4.4s
=> => exporting layers 2.1s
=> => exporting manifest sha256:e65acd35b8ccccd67a204c789980a5c9d4b567f5c278963dea5c7352928f99bf 0.2s
=> => exporting config sha256:6fc4bffed745c9471e91a7d6368f2250a1e2fa9240b6d62fc824dd202417a24c 0.3s
=> => exporting attestation manifest sha256:7068182052893d26b5d2c5cc6abdd73c5d5abc3397b0e638282806b788039da1 0.3s
=> => exporting manifest list sha256:94867709559923428762c5ceda629a9a36002c31158d8076d8247725e677d01c 0.4s
=> => naming to docker.io/library/godaddy-ddns:1.0 0.0s
=> => unpacking to docker.io/library/godaddy-ddns:1.0
確認 Docker 映像檔
docker images
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
godaddy-ddns:1.0 948677095599 23.1MB 6.81MB
hello-world:latest 5e2309035332 25.9kB 9.49kB
nginx:latest 05b8cb60c354 253MB 69.2MB
先手動測試 Container
docker run --rm \
-e GODADDY_PAT=your_token \
-e DOMAIN=ivancheng.tw \
-e RECORD_NAME=life \
-e TTL="600" \
-e CHECK_INTERVAL=300 \
godaddy-ddns:1.0
應該看到下面畫面
========================================
GoDaddy DDNS
========================================
Domain : ivancheng.tw
Record : life
TTL : 600
Check interval : 300s
========================================
[2026-09-12 15:18:21] Checking public IP...
Current IP : 114.40.38.227
Record ID : 3f19dafd-e702-40e9-ad1f-3a5b01c16b62
DNS IP : 8.8.8.8
DNS TTL : 600
IP has changed!
Updating GoDaddy DNS...
Request:
{
"name": "life",
"type": "A",
"data": "114.40.38.227",
"ttl": 600
}
DNS update successful.
Response:
{"data":"114.40.38.227","name":"life","recordId":"3f19dafd-e702-40e9-ad1f-3a5b01c16b62","ttl":600,"type":"A"}
Next check in 300 seconds...
直接按 Ctrl + C,Docker 會收到中斷訊號,這個 Container 就會停止。
我們用了 –rm,所以停止後 Container 也會自動刪除。
到 DNS 查詢更新成功

接下來映像檔上傳到遠端倉庫
登入 Docker Hub 與輸入你的帳號與密碼
docker login -u jieshiun
Password:
WARNING! Your credentials are stored unencrypted in '/home/jieshiun/.docker/config.json'.
Configure a credential helper to remove this warning. See
https://docs.docker.com/go/credential-store/
Login Succeeded
將本機映像檔加上你的使用者名稱與儲存庫標籤
docker tag godaddy-ddns:1.0 jieshiun/godaddy-ddns:1.0
將標記好的映像檔上傳到遠端倉庫
docker push jieshiun/godaddy-ddns:1.0
映像檔僅有 6.5 MB,整體非常輕量。

接著回到 QNAP 的 Container Station 建立容器
映像檔輸入 jieshiun/godaddy-ddns:1.0,下一步。

設定容器,選擇進階設定。

之前建立的 DDNS Script 環境變數如下
# =========================================================
# Environment Variables
# =========================================================
: "${GODADDY_PAT:?GODADDY_PAT is required}"
: "${DOMAIN:?DOMAIN is required}"
: "${RECORD_NAME:?RECORD_NAME is required}"
TTL="${TTL:-600}"
CHECK_INTERVAL="${CHECK_INTERVAL:-300}"
我們至少需要新增以下環境變數
- GODADDY_PAT:您申請的 PAT,例如 gd_pat_XXXXXXXXXXXX。
- DOMAIN:您的網域名稱,例如 ivancheng.tw。
- RECORD_NAME:您的紀錄名稱,例如 life。
點選套用,下一步。

接著按完成就可以了

容器馬上就運行起來,並幫我們更新 DNS 紀錄指向正確的 IP 位置。

對於家中使用浮動 IP,又有 NAS、Web Server 或其他需要從 Internet 存取的服務來說,這是一個相對簡單又容易維護的 DDNS 解決方案。
未來如果需要搬到其他 Linux 主機、NAS,甚至雲端 VM,也不需要重新安裝環境,只要準備好 Docker Image,設定好環境變數,就可以直接運行。
今天的分享就到這邊,感謝收看。


