如何使用 Docker 自架 Shlink 網址縮短服務
本指南說明如何於 VPS 部署 Shlink 網址縮短服務。包含 Postgres 資料庫配置、DNS 設定、API 金鑰管理及反向代理設定。透過 Docker Compose 實作,確保您的短網址系統具備統計分析與 QR Code 生成功能。
建置內容
自架 URL shortener 是一個小型伺服器,可將長連結轉換成由你管理的短連結,並統計每次點擊。這裡選用 Shlink:它是開放原始碼軟體,提供 Docker image,搭配資料庫即可在單一容器中完成整個工作。本指南會在 VPS 上部署 Shlink,透過正式的短網址網域提供服務,並設定 HTTPS、API key、QR code 與點擊統計。
要呈現商業 URL shortener 的使用方式,需要兩個元件。API server 負責回應重新導向並保存資料。Web client 是獨立的 static app,會在瀏覽器中連線至該 API。你可以同時執行兩者,也可以只執行 API,改用 command line 操作。
以下版本號是截至 2026 年 10 月的當前版本:Shlink 5.1 和 shlink-web-client 4.8。
先將短網域指向伺服器
網域即是產品本身。s.example.com/abc123 是使用者看到的連結,因此請在安裝任何軟體前,先選定一個簡短的網域。Shlink 會將網域儲存在每一組短網址中,若事後變更網域,所有已發佈的連結將會失效。
請為該短網域建立一筆 DNS A 記錄,並指向您的 VPS 公用 IPv4 位址。若伺服器具備 IPv6,也請一併加入 AAAA 記錄。在繼續後續步驟前,請先確認該網域已正確解析。
dig +short s.example.com A輸出結果必須為您的伺服器位址。若結果為空,代表記錄尚未完成傳播;若此時繼續執行後續步驟,將會因無法為未解析的名稱核發 TLS (transport layer security) 憑證,而導致不明確的錯誤。
Compose 檔案
Shlink 需要資料庫。SQLite 適合測試,但只要計畫長期使用,就應選擇 Postgres,因為造訪資料列會持續增加,而 Postgres 對索引及並行寫入的處理能力較佳。將以下內容放入 /opt/shlink/compose.yaml。
services:
shlink:
image: shlinkio/shlink:stable
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
environment:
DEFAULT_DOMAIN: s.example.com
IS_HTTPS_ENABLED: "true"
DB_DRIVER: postgres
DB_HOST: database
DB_NAME: shlink
DB_USER: shlink
DB_PASSWORD: ${DB_PASSWORD}
depends_on:
- database
database:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: shlink
POSTGRES_USER: shlink
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- shlink_db:/var/lib/postgresql/data
web-client:
image: shlinkio/shlink-web-client:stable
restart: unless-stopped
ports:
- "127.0.0.1:8081:8080"
volumes:
shlink_db:發布的兩個埠都繫結至 127.0.0.1,因此在下一節設定反向代理前,網際網路無法連入任何服務。Docker 會在主機防火牆之前寫入自己的轉送規則,因此即使主機防火牆看似封閉,單純使用 8080:8080 行仍會讓應用程式對外暴露。繫結至迴路位址即可避免這個問題。以這種方式執行的任何應用程式都適用相同模式,詳情請參閱 VPS 上的 Docker Compose 指南。
資料庫密碼來自 compose 檔案旁的 .env 檔案,因此不會寫入 YAML。
sudo mkdir -p /opt/shlink
printf 'DB_PASSWORD=%s\n' "$(openssl rand -base64 24)" | sudo tee /opt/shlink/.env
sudo chmod 600 /opt/shlink/.env啟動服務,並監看 API 啟動。
cd /opt/shlink
sudo docker compose up -d
sudo docker compose logs -f shlink第一次啟動會執行資料庫 migration,因此所需時間比後續啟動更長。服務穩定後,確認本機可以取得回應。
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/rest/health200 表示 API 正常運作,且資料庫連線有效。503 表示 Shlink 正在執行,但資料庫檢查失敗。健康檢查端點遇到任何資料庫錯誤時會回覆 503,不會回覆 500。如果 curl 改為輸出 000,表示該埠沒有任何服務回應:容器在啟動檢查期間停止,而 Docker 正在重新啟動容器,因此請查看上方的日誌。無論哪種情況,通常都是因為 .env 中的 DB_PASSWORD 與建立 Postgres 時使用的值不一致;Postgres image 只會在初始化空的資料目錄時讀取 POSTGRES_PASSWORD。之後編輯密碼不會生效,除非移除 volume 並重新啟動。
在前端終止 HTTPS
Shlink 透過 8080 埠提供純 HTTP 服務。TLS 應在反向代理中處理,唯一重要的設定是轉送原始主機名稱。Shlink 會讀取 Host 標頭,據此判斷短網址代碼所屬的網域。因此,會改寫該標頭的代理會讓原本存在的連結回傳 404,並將造訪統計附加到錯誤的網域。
server {
server_name s.example.com;
listen 80;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}接著申請憑證。完整操作流程(包括續期計時器)請參閱 Ubuntu 24.04 上的 nginx Certbot 指南。
sudo certbot --nginx -d s.example.comcompose 檔案中的 IS_HTTPS_ENABLED: "true" 會讓 Shlink 在回傳的短網址中輸出 https://。它本身不會啟用 TLS。將它留在 false HTTPS 代理後方,API 回傳的每個連結都會是 http:// 連結,之後再重新導向。這會多一次往返,也會在網頁用戶端中顯示異常。
建立 API key
沒有 API key 就無法呼叫 API。請在容器內透過 CLI 產生 API key。
sudo docker compose exec shlink shlink api-key:generate --name "web client"此命令只會輸出一次 API key。請立即複製,因為它會以雜湊形式儲存,之後無法再次顯示。shlink api-key:list會顯示各個 API key 的名稱及啟用狀態,但不會顯示 API key 本身。使用 shlink api-key:disable 加上名稱即可撤銷 API key。
每個 REST 呼叫都會在 X-Api-Key 標頭中攜帶 API key。
curl -H "X-Api-Key: YOUR_KEY" https://s.example.com/rest/v3/short-urls包含 shortUrls key 的 JSON 物件表示 API key 可正常使用。若 401 的 type 為 https://shlink.io/api/error/invalid-api-key,且標題為 Invalid API key,表示 API key 錯誤、已停用或已過期。
透過命令列建立短連結
CLI 是建立連結最快的方式,且非常適合用於指令碼。
sudo docker compose exec shlink shlink short-url:create https://example.com/a/very/long/path
sudo docker compose exec shlink shlink short-url:create https://example.com/docs --custom-slug docs --tag reference--custom-slug 能讓您建立具備可讀性的連結,而非隨機產生的代碼。Slug 在每個網域中皆為唯一,因此若嘗試建立已存在的 slug,系統會失敗並拒絕覆寫現有連結。--tag 可以重複使用,標籤(tags)則是您後續彙整統計數據時的分類依據。
列出現有連結,接著查看單一連結的流量。
sudo docker compose exec shlink shlink short-url:list
sudo docker compose exec shlink shlink short-url:visits docsshort-url:visits 會針對每次點擊輸出一行紀錄,包含日期、參照來源(referrer)與使用者代理(user agent)。除非您設定了 GEOLITE_LICENSE_KEY 環境變數,否則國家與城市欄位將保持空白;該變數為免費的 MaxMind 金鑰,供 Shlink 下載 GeoLite2 資料庫使用。若未設定,系統仍會記錄造訪次數,僅無法進行地理定位。
Web 用戶端與 QR code
Web 用戶端目前位於 127.0.0.1:8081,需要設定專屬的代理項目;如果不想將其公開,也可以使用 SSH tunnel。首次載入時,系統會要求輸入伺服器 URL 和 API key。輸入 https://s.example.com 及你產生的 key。用戶端會將兩者儲存在瀏覽器儲存空間中,並直接呼叫你的 API,因此資料不會經過其他服務。將介面與 API 分離是一種值得注意的架構模式,因為同樣的方式也讓 Halcyon 能將 Jellyfin 媒體庫包裝成 1990 年代的出租店,而不必修改後方的媒體伺服器。
QR code 由 Web 用戶端產生,而不是由 API 產生。Shlink 5 移除了在短網址後附加 /qr-code 的舊方法,因此該網址不再回傳圖片。Web 用戶端會直接在瀏覽器中繪製 QR code,不會向 Shlink 發出請求。在短網址清單中開啟任一連結的 QR code 選項,設定大小、格式與邊界後下載結果。若 QR code 會以較小尺寸列印或部分遭到遮蓋,請提高錯誤修正等級,以確保仍可掃描。同一個對話方塊也能設定顏色與 logo。
讓服務持續運作
短網址服務可能會無聲失效。連結停止重新導向,但沒有人通知你,因為點擊連結的人可能以為連結本來就已失效。請將 uptime check 指向實際的短網址,而不是首頁;凡不是重新導向的回應,都應觸發警示。自架的 Uptime Kuma 執行個體很適合執行這項工作,也能檢查指定的狀態碼。
請備份資料庫,而不是容器。只要執行一個命令即可匯出資料庫。
sudo docker compose exec -T database pg_dump -U shlink shlink | gzip > shlink-$(date +%F).sql.gz只要有這個檔案和 compose 檔案,就能在新伺服器上重建整個服務。伺服器上的每個應用程式都需要各自保存這一組檔案。相片庫是較棘手的情況,因為 PhotoPrism 與 Immich 不僅將資料列存放在資料庫中,也會將原始檔案存放在磁碟上。因此,只有資料庫匯出檔可以還原帳戶與設定,卻無法還原任何相片。升級時會先執行 sudo docker compose pull,再執行 sudo docker compose up -d;Shlink 啟動時會執行任何新的 migration。請在 pull 之前先建立匯出檔,因為 migration 無法回復。
FAQ
為什麼加入反向代理後,短網址會回傳 404?
Shlink 會根據 Host 標頭中的網域名稱來比對短代碼。若代理伺服器傳送的是其自身名稱或內部位址,Shlink 就會嘗試在該網域下尋找代碼,但因該網域並無連結而回傳 404。請在 nginx 的 location 區塊中設定 proxy_set_header Host $host; 並重新載入代理服務。連結將立即恢復正常,無須重啟容器。
我需要使用 Postgres,還是 SQLite 就足夠了?
SQLite 適合用來測試 Shlink,且不需要額外的容器。若您要發布重要的連結,請改用 Postgres,因為每次點擊都會產生一筆訪問記錄,而 SQLite 會序列化寫入操作。日後轉換需要匯出並重新匯入連結,因此一開始就選擇 Postgres 可省去遷移工作。
我忘記複製 API key,可以找回嗎?
不行。Shlink 僅儲存 key 的雜湊值,因此 api-key:list 僅顯示名稱與狀態,不會顯示原始數值。請使用 shlink api-key:generate 產生新的 key,貼入網頁客戶端,並使用 shlink api-key:disable 停用舊的 key 以使其失效。
為什麼訪問統計中的國家欄位是空的?
地理位置功能需要 GeoLite2 資料庫,Shlink 僅在您提供 GEOLITE_LICENSE_KEY 時才會下載。此金鑰可從 MaxMind 免費取得。請將其加入環境變數區塊並重建容器,新的訪問記錄即可顯示地理位置。在此之前記錄的訪問資料將保持空白,除非您執行 shlink visit:locate。
如何將 Shlink 遷移至另一台伺服器?
保留網域名稱並遷移資料。使用 pg_dump 匯出資料庫,將匯出檔與 compose 檔案複製到新伺服器,啟動服務堆疊,並在實際流量進入前將資料匯入空的資料庫。最後再變更 DNS 紀錄。由於所有資料皆儲存在資料庫中,短代碼及其訪問歷史將會完整保留。