文章摘要
猿_AI

需求背景

因为运营商对于 IPv4 及 公网IP 的政策收窄,现在的普通家宽所获得的 IP 都是 NAT 过后的私有 IP。人在外已经无法通过 DDNS 等方式直连到家里的 NAS 等服务了。为了能够从外部访问到家里的 NAS 等设备,需要通过 Tailscale 的 VPN 服务来实现外部访问。

前期准备

  • VPS / 云服务器
  • 域名
  • Docker Compose 文件
  • Caddyfile
    VPS / 云服务器如果位于国内,那么相关的域名需要进行备案,不然无法访问。我这次使用的是 日本 的 国际版阿里云服务器,2C1G200M

网络质量可以参考下方

网络质量

整体的性能、网络测试可以参考 NodeQuality

Docker 环境安装

1
2
3
apt install curl -y && apt install wget -y
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

Docker Compose

完整 Docker Compose 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
networks:
caddynet:
external: true # 如果你已有 caddynet 网络,保留 external

services:
caddy:
image: markd3ng/caddy_custom
container_name: caddy
restart: unless-stopped
ports:
# - "80:80" # HTTP 验证用
- "443:443" # TLS/HTTPS 用
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy_data:/data
- ./caddy_config:/config
environment:
- CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN} # 从 .env 文件中读取
networks:
- caddynet

derper:
image: fredliang/derper
container_name: derper
restart: always
environment:
- DERP_DOMAIN=example.com
# - DERP_CERT_MODE=manual # derper 自签,改用 Caddy 反代
- DERP_ADDR=:8080 # 监听内部端口
- DERP_STUN=true
# - DERP_VERIFY_CLIENTS=true
- DERP_STUN_PORT=3478
# volumes:
# - /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock
ports:
- "3478:3478/udp" # 外部设备用 STUN 连接
networks:
- caddynet

Docker Compose 解析

这个 docker-compose.yml 文件定义了两个服务:caddy 和 derper,它们都连接到一个名为 caddynet 的 外部 Docker 网络。

networks 配置
1
2
3
networks:
caddynet:
external: true

PS:需要手动用 docker network create caddynet 创建 caddynet 的网络。

服务:Caddy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
services:
caddy:
image: markd3ng/caddy_custom
container_name: caddy
restart: unless-stopped
ports:
- "443:443" # 监听外部 HTTPS 流量(可选开启 80)
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy_data:/data
- ./caddy_config:/config
environment:
- CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
networks:
- caddynet
项目 说明
image 使用自定义构建的 caddy 镜像(可能包含额外插件)。[^1]见解释。
ports 仅开放 443(HTTPS),若要自动申请证书,建议开启 80(被注释掉)。
volumes 绑定本地配置:
- Caddyfile 是主配置文件
- caddy_data 存储证书等数据
- caddy_config 存储配置状态
environment 使用环境变量 CLOUDFLARE_API_TOKEN,供 Caddy 使用 DNS 方式申请证书(Cloudflare 提供 DNS 验证)。
networks 使用共享网络 caddynet,以便跟其他服务(如 derper)互通。
服务:Derper
1
2
3
4
5
6
7
8
9
10
11
12
13
derper:
image: fredliang/derper
container_name: derper
restart: always
environment:
- DERP_DOMAIN=example.com
- DERP_ADDR=:8080
- DERP_STUN=true
- DERP_STUN_PORT=3478
ports:
- "3478:3478/udp"
networks:
- caddynet
项目 说明
image 使用 fredliang/derper 镜像,运行 Tailscale 的中继服务器。 如果需要启用 Client verification,见[^2]
DERP_DOMAIN 设置公开使用的域名(需与 Caddy 配置反向代理对应)。
DERP_ADDR=:8080 监听容器内部的 8080 端口(不直接对外开放,走反向代理)。
DERP_STUN=true 启用 STUN(用于打洞)
DERP_STUN_PORT=3478 STUN 使用的端口,映射到主机 3478/udp
ports 仅暴露 3478/udp,用于 STUN 功能,8080 没有暴露(仅被 Caddy 内部反代)。
networks caddy 共享 caddynet 网络,Caddy 可以通过容器名访问 derper 服务。
访问流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
graph TD
subgraph "Internet"
A[Client]
D[Cloudflare API]
E[Let's Encrypt]
end

subgraph "Your Server / Docker"
subgraph "caddynet Network"
direction LR
B(Caddy - Reverse Proxy)
C(Derper - DERP Server)
B -- Reverse Proxy to :8080 --> C
end
end

A -- DERP over HTTPS (derper.saru.im:443) --> B
A -- STUN Request (UDP:3478) --> C
C -- STUN Response --> A

B -- Step 1: Request TLS Certificate --> E
E -- Step 2: Issue DNS-01 Challenge --> B
B -- Step 3: Update TXT Record --> D
D -- Step 4: Confirm TXT Record --> E
E -- Step 5: Issue TLS Certificate --> B

Caddyfile

1
2
3
4
5
6
derper.example.com {
reverse_proxy derper:8080
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
}

解析

  1. derper.example.com
    这是一个 站点块(site block),表示这个配置应用于请求域名为 derper.saru.im 的 HTTPS 请求。
  • 所有访问 https://derper.saru.im 的流量都会被这个配置处理。
  • 默认监听端口为 443(HTTPS),Caddy 会自动监听。
  1. reverse_proxy derper:8080
    反向代理指令。
  • 意思是:将访问 https://derper.saru.im 的流量,转发给名为 derper 的容器的 8080 端口。
  • derper 是 Docker Compose 中的服务名,Caddy 和 derper 都在 caddynet 网络中,因此可以通过容器名直接解析。
    相当于
1
[Client] ---> Caddy (443) ---> http://derper:8080 (容器间通信)
  1. tls { … }
    这一块配置 HTTPS/TLS 的证书获取方式。
1
2
3
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
  • 启用自动申请证书(Let’s Encrypt)。
  • 使用 Cloudflare DNS 方式验证域名所有权,适用于:
    • 无公网 80 端口
    • 或在内网中运行服务时
  • {env.CLOUDFLARE_API_TOKEN} 表示从环境变量中读取 Cloudflare 的 API Token。

PS:

  1. 确保 Cloudflare 上有一个 A 记录 derper.saru.im 指向你 Caddy 所在服务器的公网 IP;
  2. 环境变量 CLOUDFLARE_API_TOKEN 应至少包含 Cloudflare 的 Zone.DNS 权限。API 的申请请自行查阅教程;
  3. 如果你用了 .env 文件,确保 docker-compose.yml 和 .env 在同一目录下;

.env

1
CLOUDFLARE_API_TOKEN=type_your_own_api_here

整体目录结构如下

1
2
3
4
5
6
derper-caddy/
├── Caddyfile # Caddy 的配置文件
├── docker-compose.yml # Docker Compose 编排
├── .env # 存储 Cloudflare API Token
├── caddy_data/ # Caddy 自动生成的证书等数据(映射目录)
├── caddy_config/ # Caddy 的配置状态数据

[^1]:Caddy 的容器镜像是编译了第三方的插件,如果有需要编译自己的 Caddy,可以自己去编译。可以参考 ^caddy_custom
https://github.com/caddy-dns/cloudflare
https://github.com/caddyserver/forwardproxy

[^2]: Client verification

  1. 安装 Tailscale 的 Linux 客户端,教程自行查找;
  2. Docker Compose 文件中的 # DERP_VERIFY_CLIENTS=true 去掉 # 启用。
1
2
3
4
5
6
7
8
9
FROM caddy:builder AS builder

RUN xcaddy build \
--with github.com/caddy-dns/cloudflare \
--with github.com/caddyserver/forwardproxy

FROM caddy:latest

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

运行

Caddyfile,Docker Compose File,.ENV 都准备好后在终端执行。

1
docker compose up -d

验证

浏览器打开 https://derper.example.com 显示 DERP 字样证明成功。

Tailscale 组网

这部分可以参考 多多先生 的文章。