如何在 QNAP Container Station 部署 GoDaddy DDNS

還記得上一篇文章,我們在 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,設定好環境變數,就可以直接運行。

今天的分享就到這邊,感謝收看。