解決WSL2 Docker執行Miniflux出現「socket: address family not supported」錯誤

該問題點很好重現(跟架設環境有關):登入到Miniflux網頁 → Feed → 點一下「在背景更新所有Feed」或是等Miniflux自動更新Feed,在Feed頁面會出現很多網站的RSS都顯示socket: address family not supported by protocol,但是手動更新單一網站的RSS卻又是好的。在Docker Desktop和Rancher Desktop沒遇過,第一次在WSL2安裝Miniflux後遇到這個問題。一直不停詢問Gemini和反覆測試,最後才得以解決。

注意

本文由 Gemini 3 Pro 語言模型產生。

這是一份整理好的技術筆記,特別針對 WSL 2 + Docker 環境下遇到的這個棘手網路問題,詳細記錄了錯誤現象、深層原因以及最終的解決方案。

📍 環境與症狀

環境:

  • OS:Windows 10/11 with WSL 2(Ubuntu 24.04)
  • Platform:Docker running on WSL 2
  • App:Miniflux(Go 語言編寫的 RSS 閱讀器,基於 Alpine Linux Image)

症狀:

  1. Miniflux 無法抓取 RSS Feed,日誌中大量出現以下錯誤:

    dial tcp [2606:4700:3036::ac43:83b7]:443: socket: address family not supported by protocol
    
  2. 在 WSL 主機上使用 curl 測試該網址完全正常(因為 curl 自動走 IPv4)。

  3. 使用 docker exec 進入容器 ping 外部網站,也能解析出 IPv4。

  4. 特殊背景:因 ISP 或路由器 IPv6 連線不穩,已在 Windows 的 %UserProfile%/.wslconfig 中設定 kernelCommandLine=ipv6.disable=1 強制在核心層級關閉 IPv6。

🔍 問題根源分析(Root Cause)

這是一個由 WSL 2 特殊網路架構 + Go 語言解析機制 + Linux 核心設定 三者衝突導致的「殭屍 IPv6」現象。

由於 Miniflux 官方 Docker Image 強制使用 Alpine Linux 作為基底,且未提供 Debian/Ubuntu 版本,因此我們無法透過簡單的「更換 Image Tag」(例如換成 miniflux:debian)來避開 Alpine 與 Go 語言在 IPv6 解析上的相容性問題。

這迫使我們必須採用更底層的手段:掛載 gai.conf 設定檔,直接從 OS 層級修正 Alpine 內部的 DNS 解析優先權。

具體衝突層級如下:

  1. 核心層(Kernel):
    我們透過 .wslconfig 物理關閉了 IPv6 支援。Linux 核心完全不懂 IPv6,任何 IPv6 連線請求都會被直接拒絕(address family not supported)。
  2. DNS 層(The Conflict):
    雖然核心關了 IPv6,但 Docker 容器內的 DNS 解析器(無論是 Docker 內建的 127.0.0.11 還是 WSL 的 DNS Proxy)在查詢網域時,依然會回傳 AAAA(IPv6)紀錄。
  3. 應用層(Miniflux/Go):
    Miniflux 使用 Go 語言撰寫。Go 的 DNS Resolver 在拿到 IPv4 和 IPv6 兩種地址時,預設行為(Happy Eyeballs 演算法)可能會優先或並行嘗試 IPv6。
  4. 崩潰點:
    Go 程式拿到 IPv6 地址 → 嘗試連線 → 核心瞬間報錯「不支援此協定」。
    在高併發(一次更新數百個 Feed)的情況下,這種瞬間錯誤會導致 Go 的連線邏輯崩潰,來不及 Fallback 回 IPv4 就直接判定失敗。

結論:必須從系統層級「強迫」應用程式忽略 IPv6,只使用 IPv4。

🛠️ 解決方案

傳統的 Docker Bridge 網路難以完全過濾 IPv6 DNS 回應。最終極的解法是:使用 Host Network 模式,並掛載自訂的網路設定檔。

步驟 1:準備設定檔

docker-compose.yml 同級目錄下,建立兩個檔案。

  1. resolv.conf(強制指定穩定 DNS)
    WSL 2 預設的 DNS Proxy 在高負載下容易 Timeout(no such host),我們直接指定 Google DNS。

    nameserver 8.8.8.8
    nameserver 1.1.1.1
    
  2. gai.conf(強制 IPv4 優先)
    這是 Linux 的 getaddrinfo 設定檔。我們要將 IPv4 的優先權調到最高(100),讓系統在解析 DNS 後,永遠把 IPv4 排在第一位。

    # ::ffff:0:0/96 is the IPv4 mapped prefix.
    # We set its precedence to 100 (highest), making it preferred over IPv6 (default 40).
    precedence ::ffff:0:0/96  100
    

步驟 2:修改 docker-compose.yml

請參照以下範例修改。重點在於切換成 network_mode: host 並掛載上述兩個設定檔。

services:
  miniflux:
    image: miniflux/miniflux:latest
    container_name: miniflux
    restart: unless-stopped
    # [關鍵 1] 改用 Host 模式,直接繼承 WSL 主機的純淨網路環境
    network_mode: host
    # Host 模式下不需要 ports 映射,預設會監聽 8080
    depends_on:
      - db
    environment:
      # [關鍵 2] 因為改用 Host 模式,資料庫在 "本機",所以要連線到 127.0.0.1
      - DATABASE_URL=postgres://miniflux:secret@127.0.0.1/miniflux?sslmode=disable
      - RUN_MIGRATIONS=1
      # 其他環境變數...

    # [關鍵 3] 掛載設定檔,從系統層級覆寫 DNS 行為
    volumes:
      - ./resolv.conf:/etc/resolv.conf:ro  # 解決 DNS Timeout
      - ./gai.conf:/etc/gai.conf:ro        # 解決 IPv6 優先問題 (address family error)

  db:
    image: postgres:16
    container_name: postgresql
    restart: unless-stopped
    environment:
      - POSTGRES_USER=miniflux
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=miniflux
    volumes:
      - miniflux-db:/var/lib/postgresql/data
    # [關鍵 4] 必須開放 Port,因為 Miniflux 是透過 127.0.0.1 (TCP) 連進來的
    ports:
      - "5432:5432"

volumes:
  miniflux-db:

步驟 3:特別注意 Cloudflare Tunnel(如果有使用)

如果你有搭配 Cloudflare Tunnel,因為改成了 Host Network,Tunnel 容器無法再透過 container_name(如 miniflux)找到對方。

請至 Cloudflare Zero Trust DashboardTunnelsConfigurePublic Hostname
將 Service URL 修改為:

  • localhost:8080(或 127.0.0.1:8080

步驟 4:重建容器

執行以下指令套用變更:

docker compose down
docker compose up -d

✅ 驗證結果

修改後,觀察 Miniflux 的 Log:

docker compose logs -f --tail=10 miniflux
  1. 錯誤消失:socket: address family not supported 不再出現。
  2. 連線行為:如果遇到網路問題,錯誤訊息會變成標準的 dial tcp 1.2.3.4:80: i/o timeout(顯示的是 IPv4 地址),證明系統已經成功強制走 IPv4 通道。

這組設定能確保在 WSL 2 這種特殊的網路環境下,Go 語言程式也能乖乖聽話,不再被 IPv6 的幽靈干擾。