fix(compose): exec-form healthcheck; enable embedded DERP (tw)

- healthcheck 改 exec 形式(distroless 映像無 /bin/sh,CMD-SHELL 永遠 unhealthy)
- 補 command: serve、對外開 3478/udp(DERP STUN)
- config.yaml 內建 DERP 設為 enabled(region tw),與線上實跑一致

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
timmy
2026-07-13 21:28:47 +08:00
parent 0e35a3d164
commit a20835c371
2 changed files with 138 additions and 122 deletions

View File

@@ -1,233 +1,249 @@
---
# headscale will look for a configuration file named `config.yaml` (or `config.json`) in the following order:
#
# - `/etc/headscale`
# - `~/.headscale`
# - current working directory
# ============================================================================
# Headscale 設定檔
# 檔案搜尋順序:/etc/headscale → ~/.headscale → 當前工作目錄
# ============================================================================
# ----------------------------------------------------------------------------
# 伺服器與監聽位址
# ----------------------------------------------------------------------------
# server_url: 給客戶端連線用的網址
# server_url: http://127.0.0.1:8080
server_url: https://headscale.lotimmy.com
# listen_addr: Headscale 綁定在哪個 IP:Port 等待連線
# listen_addr: 127.0.0.1:8080
# listen_addr: Headscale HTTP API 綁定的 IP:Port
listen_addr: 0.0.0.0:8080
# metrics_listen_addr: 提供 Prometheus 或其他監控系統抓取 metrics 的位址
# metrics_listen_addr: 127.0.0.1:9090
# metrics_listen_addr: Prometheus/監控系統抓取 metrics 的位址
metrics_listen_addr: 0.0.0.0:9090
# grpc_listen_addr: Headscale 提供 gRPC API 服務位址,通常給內部或管理工具使用
# grpc_listen_addr: gRPC API 服務位址給內部或管理工具使用
grpc_listen_addr: 0.0.0.0:50443
# grpc_listen_addr: 127.0.0.1:50443
# grpc_allow_insecure: 是否允許 gRPC 使用未加密連線(建議保持 false 確保安全
# grpc_allow_insecure: 是否允許 gRPC 未加密連線(建議保持 false
grpc_allow_insecure: false
# noise: Headscale 使用 Noise 協議加密與客戶端的流量
# unix_socket: 本地 UNIX Socket 路徑
unix_socket: /var/run/headscale/headscale.sock
# unix_socket_permission: UNIX Socket 權限設定
unix_socket_permission: "0770"
# ----------------------------------------------------------------------------
# Noise 加密協議
# ----------------------------------------------------------------------------
noise:
# private_key_path: Noise 私鑰路徑,缺少時會自動生成
private_key_path: /var/lib/headscale/noise_private.key
# prefixes: 設定分配給節點的 IP 範圍(必須在 Tailscale 支援的範圍內)
# ----------------------------------------------------------------------------
# IP 位址分配(必須位於 Tailscale 支援的範圍內)
# ----------------------------------------------------------------------------
prefixes:
# v4: IPv4 分配範圍(預設 100.64.0.0/10
v4: 100.64.0.0/10
# v6: IPv6 分配範圍(預設 fd7a:115c:a1e0::/48
v6: fd7a:115c:a1e0::/48
# allocation: IP 分配策略sequential 依序分配random 隨機分配)
# allocation: 分配策略sequential 依序 / random 隨機)
allocation: sequential
# derp: DERP 是 Tailscale 用來在無法直連時的中繼伺服器設定
# ----------------------------------------------------------------------------
# DERP 中繼伺服器(直連失敗時的備援通道)
# ----------------------------------------------------------------------------
derp:
# server: 內建的 DERP 伺服器設定
server:
# enabled: 是否啟用內建 DERP需要 https/TLS
enabled: false
enabled: true
# region_id: 內建 DERP 區域 ID避免與其他 DERP 衝突)
# region_id: 內建 DERP 區域 ID避免與其他 DERP 衝突)
region_id: 999
# region_code: 區域代碼(顯示於 Tailscale UI
region_code: "tw"
# region_name: 區域名稱(顯示於 Tailscale UI
region_name: "Headscale TW"
# region_code: 區域代碼(會顯示在 Tailscale UI
region_code: "headscale"
# region_name: 區域名稱(會顯示在 Tailscale UI
region_name: "Headscale Embedded DERP"
# stun_listen_addr: STUN 服務的監聽位址(協助 NAT 穿透)
# stun_listen_addr: STUN 服務監聽位址(協助 NAT 穿透
stun_listen_addr: "0.0.0.0:3478"
# private_key_path: 內建 DERP 使用的私鑰路徑,沒有會自動生成
# private_key_path: 內建 DERP 私鑰路徑,缺少時自動生成
private_key_path: /var/lib/headscale/derp_server_private.key
# automatically_add_embedded_derp_region: 是否自動把內建 DERP 加進 DERP map
# automatically_add_embedded_derp_region: 自動把內建 DERP 加進 DERP map
automatically_add_embedded_derp_region: true
# ipv4: 可選,將公開 IPv4 加入 DERP map(改善 Exit-Node 或 DNS 問題時的連線穩定性)
ipv4: 1.2.3.4
# ipv6: 可選,將公開 IPv6 加入 DERP map
ipv6: 2001:db8::1
# ipv4 / ipv6: 公開 IP(改善 Exit-Node 或 DNS 問題時的連線穩定性)
ipv4: 125.229.110.50
ipv6: 2001:b011:3012:373a:be24:11ff:fe40:a31a
# urls: 外部可用的 DERP map JSON 清單
urls:
- https://controlplane.tailscale.com/derpmap/default
# paths: 本地 DERP map YAML 檔路徑(通常用在自建 DERP 伺服器)
# paths: 本地 DERP map YAML 檔路徑(自建 DERP 伺服器使用
paths: []
# auto_update_enabled: 是否自動定期更新 DERP map
# auto_update_enabled: 自動定期更新 DERP map
auto_update_enabled: true
# update_frequency: DERP map 更新檢查頻率
update_frequency: 24h
# disable_check_updates: 是否停用 Headscale 啟動時自動檢查更新
disable_check_updates: false
# ephemeral_node_inactivity_timeout: 臨時節點ephemeral node多久沒活動就自動刪除
# ⚠️ 此鍵已於新版 Headscale 移除,改名為 node.ephemeral.inactivity_timeout預設仍為 30m
# 保留 30m 預設行為,故直接停用本鍵;如需自訂請改用新的 node.ephemeral.inactivity_timeout 區塊。
# ephemeral_node_inactivity_timeout: 30m
# ----------------------------------------------------------------------------
# 資料庫
# ----------------------------------------------------------------------------
database:
# type: 資料庫類型(可選 sqlite postgres建議 sqlite
# type: 資料庫類型sqlite / postgres建議 sqlite
type: sqlite
# debug: 是否啟用資料庫偵錯(需搭配 log.level=debug/trace
# debug: 啟用資料庫偵錯(需搭配 log.level=debug/trace
debug: false
gorm:
# prepare_stmt: 是否啟用 prepared statements
# prepare_stmt: 啟用 prepared statements
prepare_stmt: true
# parameterized_queries: 是否啟用參數化查詢(提升安全性與效能)
# parameterized_queries: 啟用參數化查詢(安全性與效能)
parameterized_queries: true
# skip_err_record_not_found: 是否略過「record not found」錯誤訊息
# skip_err_record_not_found: 略過「record not found」錯誤訊息
skip_err_record_not_found: true
# slow_threshold: 慢查詢閾值(毫秒)
slow_threshold: 1000
sqlite:
# path: SQLite 資料庫檔案的存放路徑
# path: SQLite 資料庫檔案路徑
path: /var/lib/headscale/db.sqlite
# write_ahead_log: 是否啟用 WAL 模式(建議正式環境開啟,效能與穩定性更好)
# write_ahead_log: 啟用 WAL 模式(正式環境建議開啟)
write_ahead_log: true
# wal_autocheckpoint: WAL 自動 checkpoint 閾值(單位 frame0 代表停用自動 checkpoint
# wal_autocheckpoint: WAL 自動 checkpoint 閾值frame0 代表停用)
wal_autocheckpoint: 1000
# acme_url: ACME 伺服器 URL預設使用 Let's Encrypt
acme_url: https://acme-v02.api.letsencrypt.org/directory
# acme_email: 註冊 ACME 憑證時的聯絡 email
# ----------------------------------------------------------------------------
# TLS / ACME 憑證
# ----------------------------------------------------------------------------
# acme_url: ACME 伺服器 URL預設 Let's Encrypt
acme_url: https://acme-v02.api.letsencrypt.org/directory
# acme_email: 註冊 ACME 憑證的聯絡 email
acme_email: ""
# tls_letsencrypt_hostname: 要申請憑證的網域名稱
tls_letsencrypt_hostname: ""
# tls_letsencrypt_cache_dir: 存放憑證與 metadata 的路徑
# tls_letsencrypt_cache_dir: 憑證與 metadata 存放路徑
tls_letsencrypt_cache_dir: /var/lib/headscale/cache
# tls_letsencrypt_challenge_type: ACME 驗證方式(支援 HTTP-01 或 TLS-ALPN-01
# tls_letsencrypt_challenge_type: ACME 驗證方式HTTP-01 或 TLS-ALPN-01
tls_letsencrypt_challenge_type: HTTP-01
# tls_letsencrypt_listen: HTTP-01 驗證時使用的監聽位址(預設 80 port
# tls_letsencrypt_listen: HTTP-01 驗證監聽位址
tls_letsencrypt_listen: ":http"
# tls_cert_path: 有憑證的檔案路徑(選填,不用 Let's Encrypt 時使用
# tls_cert_path / tls_key_path: 有憑證路徑(不使用 Let's Encrypt 時填寫
tls_cert_path: ""
# tls_key_path: 已有私鑰的檔案路徑(選填,不用 Let's Encrypt 時使用)
tls_key_path: ""
# ----------------------------------------------------------------------------
# 日誌
# ----------------------------------------------------------------------------
log:
# format: 日誌輸出格式text json
# format: 輸出格式text / json
format: text
# level: 日誌等級(常見有 debuginfowarnerror
# level: 日誌等級debug / info / warn / error
level: info
# logtail: 是否把 client 日誌送到 Tailscale 官方伺服器(預設關閉)
logtail:
enabled: false
# ----------------------------------------------------------------------------
# ACL 政策
# ----------------------------------------------------------------------------
policy:
# mode: ACL 政策儲存方式(可選 file database
# mode: ACL 儲存方式file / database
mode: file
# path: mode=file 時ACL HuJSON 檔案路徑
# path: mode=file 時 HuJSON 檔案路徑
path: /etc/headscale/policy.hujson
# ----------------------------------------------------------------------------
# DNS 與 MagicDNS
# ----------------------------------------------------------------------------
dns:
# 啟用 MagicDNS讓節點自動 hostname
# magic_dns: 啟用 MagicDNS讓節點自動取得 hostname
magic_dns: true
# 要確定和 headscale server_url 不同網域,例如:
# base_domain: example.com
# base_domain: MagicDNS 基底網域(須與 server_url 網域不同)
base_domain: internal.lotimmy.com
# override_local_dns: 是否強制使用 Headscale 的 DNS覆蓋本地設定
# 如果希望 tailnet 裡所有節點都「強制走這組 DNS,建議改成 true
# 若你還想保留各節點自己的 DNS例如公司 VPN、家路由器內網),就維持 false
# override_local_dns: 是否強制 tailnet 節點使用以下 DNS覆蓋本地設定
# true → 所有節點強制走這組 DNS
# false → 保留各節點自己的 DNS例如公司 VPN、家路由器內網)
override_local_dns: true
# nameservers.global: 全域 DNS 伺服器清單
nameservers:
global:
# NextDNS個人 DoH Profile含封鎖清單與過濾規則
- https://dns.nextdns.io/9c7b19
# NextDNS(你的專屬 DoH Profile
- https://dns.nextdns.io/9c7b19 # NextDNS DoH完整支援你的個人設定、封鎖清單、過濾規則
# NextDNS 傳統 IPv4fallback
- 45.90.28.37
- 45.90.30.37
# NextDNS IPv4 傳統 DNS可作 fallback
- 45.90.28.37 # NextDNS IPv4 主 DNS
- 45.90.30.37 # NextDNS IPv4 副 DNS
# Cloudflare DNS快速、穩定
- 1.1.1.1
- 1.0.0.1
- 2606:4700:4700::1111
- 2606:4700:4700::1001
- https://cloudflare-dns.com/dns-query
# Google DNS高穩定度 fallback
- https://dns.google/dns-query
# Cloudflare DNS快速穩定
- 1.1.1.1 # Cloudflare IPv4 主 DNSfallback
- 1.0.0.1 # Cloudflare IPv4 副 DNS
- 2606:4700:4700::1111 # Cloudflare IPv6 主 DNS
- 2606:4700:4700::1001 # Cloudflare IPv6 副 DNS
- https://cloudflare-dns.com/dns-query # Cloudflare DoH快速加密
# Quad9 DNS側重惡意網站過濾
- https://dns.quad9.net/dns-query
# Google DNS可靠但不做安全過濾
- https://dns.google/dns-query # Google DoH高穩定度 fallback
# nameservers.split: Split DNS針對特定網域指定 DNS
split: {}
# 範例:
# foo.bar.com:
# - 1.1.1.1
# darp.headscale.net:
# - 1.1.1.1
# - 8.8.8.8
# Quad9 DNS帶惡意網站安全過濾
- https://dns.quad9.net/dns-query # Quad9 DoH側重惡意網站與安全防護
# nameservers.split: Split DNS每個網域指定不同 DNS 伺服器
split:
{}
# foo.bar.com:
# - 1.1.1.1
# darp.headscale.net:
# - 1.1.1.1
# - 8.8.8.8
# search_domains: 自訂搜尋網域(啟用 MagicDNS 時base_domain 會自動加在最前面)
# search_domains: 自訂搜尋網域(啟用 MagicDNS 時會自動加入 base_domain
search_domains: []
# Extra DNS records
# so far only A and AAAA records are supported (on the tailscale side)
# See: docs/ref/dns.md
# extra_records: 額外的 DNS 紀錄(支援 A 與 AAAA可用檔案或直接寫在這裡
# extra_records: 額外 DNS 紀錄(目前僅支援 A 與 AAAA
# 詳見 docs/ref/dns.md
extra_records: []
# - name: "grafana.myvpn.example.com"
# type: "A"
# value: "100.64.0.3"
# - { name: "prometheus.myvpn.example.com", type: "A", value: "100.64.0.3" }
# extra_records_path: 額外 DNS 紀錄 JSON 檔路徑(檔案變更時自動載入)
# extra_records_path: 額外 DNS 紀錄 JSON 檔路徑(檔案變更時自動載入)
# extra_records_path: /var/lib/headscale/extra-records.json
# unix_socket: Headscale 提供本地 UNIX Socket 的路徑
unix_socket: /var/run/headscale/headscale.sock
# unix_socket_permission: UNIX Socket 的權限設定(例如 0770
unix_socket_permission: "0770"
logtail:
# enabled: 是否啟用 Logtail把 client 日誌送到 Tailscale 官方伺服器(預設關閉)
enabled: false
# ----------------------------------------------------------------------------
# 其他雜項
# ----------------------------------------------------------------------------
# randomize_client_port: 是否隨機使用 WireGuard port預設 41641開啟後會隨機挑 port主要解決防火牆相容問題
# ⚠️ 此頂層鍵已於新版 Headscale 移除,啟動會直接 FATAL導致容器無限重啟
# 新版改為在 policy 檔頂層設定 "randomizeClientPort": true見 policy.path / policy.mode
# 或用 nodeAttrs 針對個別節點授權。一般家用情境不需要,維持預設即可。
# randomize_client_port: true
# disable_check_updates: 停用 Headscale 啟動時自動檢查更新
disable_check_updates: false
# ephemeral_node_inactivity_timeout: 臨時節點閒置多久後自動刪除
ephemeral_node_inactivity_timeout: 30m
# randomize_client_port: 隨機選擇 WireGuard port預設 41641有助穿越嚴格防火牆
# randomize_client_port: # removed in new headscale; moved to policy file true

View File

@@ -1,9 +1,8 @@
services:
headscale:
# 釘版本避免 watchtower 自動套用破壞性更新(見 OPERATIONS.md → 故障排除)。
# 升級時手動改 tag 並先看 CHANGELOG 的 BREAKING 段落。
image: headscale/headscale:v0.29.0
container_name: headscale
command: serve
restart: unless-stopped
networks:
- shared-net
@@ -14,7 +13,7 @@ services:
# 健康檢查
healthcheck:
test: ["CMD-SHELL", "headscale version >/dev/null 2>&1 || exit 1"]
test: ["CMD", "headscale", "version"]
interval: 30s
timeout: 10s
retries: 3
@@ -38,6 +37,7 @@ services:
- "8080:8080" # HTTP API
- "9090:9090" # Metrics
- "50443:50443" # gRPC
- "3478:3478/udp" # DERP STUN
# 標籤配置
labels: