需求背景
因为运营商对于 IPv4 及 公网IP 的政策收窄,现在的普通家宽所获得的 IP 都是 NAT 过后的私有 IP。人在外已经无法通过 DDNS 等方式直连到家里的 NAS 等服务了。为了能够从外部访问到家里的 NAS 等设备,需要通过 Tailscale 的 VPN 服务来实现外部访问。
前期准备
- VPS / 云服务器
- 域名
- Docker Compose 文件
- Caddyfile VPS / 云服务器如果位于国内,那么相关的域名需要进行备案,不然无法访问。我这次使用的是 日本 的 国际版阿里云服务器,2C1G200M 。
网络质量可以参考下方
整体的性能、网络测试可以参考 NodeQuality
Docker 环境安装
apt install curl -y && apt install wget -y
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.shDocker Compose
完整 Docker Compose 文件如下:
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:
- caddynetDocker Compose 解析
这个 docker-compose.yml 文件定义了两个服务:caddy 和 derper,它们都连接到一个名为 caddynet 的 外部 Docker 网络。
networks 配置
networks:
caddynet:
external: truePS:需要手动用 docker network create caddynet 创建 caddynet 的网络。
服务:Caddy
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
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 服务。 |
访问流程
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
derper.example.com {
reverse_proxy derper:8080
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
}解析
- derper.example.com 这是一个 站点块(site block),表示这个配置应用于请求域名为 derper.saru.im 的 HTTPS 请求。
- 所有访问 https://derper.saru.im 的流量都会被这个配置处理。
- 默认监听端口为 443(HTTPS),Caddy 会自动监听。
- reverse_proxy derper:8080 反向代理指令。
- 意思是:将访问 https://derper.saru.im 的流量,转发给名为 derper 的容器的 8080 端口。
- derper 是 Docker Compose 中的服务名,Caddy 和 derper 都在 caddynet 网络中,因此可以通过容器名直接解析。 相当于
[Client] ---> Caddy (443) ---> http://derper:8080 (容器间通信)- tls { … } 这一块配置 HTTPS/TLS 的证书获取方式。
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}- 启用自动申请证书(Let’s Encrypt)。
- 使用 Cloudflare DNS 方式验证域名所有权,适用于:
- 无公网 80 端口
- 或在内网中运行服务时
- {env.CLOUDFLARE_API_TOKEN} 表示从环境变量中读取 Cloudflare 的 API Token。
PS:
- 确保 Cloudflare 上有一个 A 记录 derper.saru.im 指向你 Caddy 所在服务器的公网 IP;
- 环境变量 CLOUDFLARE_API_TOKEN 应至少包含 Cloudflare 的 Zone.DNS 权限。API 的申请请自行查阅教程;
- 如果你用了 .env 文件,确保 docker-compose.yml 和 .env 在同一目录下;
.env
CLOUDFLARE_API_TOKEN=type_your_own_api_here整体目录结构如下
derper-caddy/
├── Caddyfile # Caddy 的配置文件
├── docker-compose.yml # Docker Compose 编排
├── .env # 存储 Cloudflare API Token
├── caddy_data/ # Caddy 自动生成的证书等数据(映射目录)
├── caddy_config/ # Caddy 的配置状态数据- 安装 Tailscale 的 Linux 客户端,教程自行查找;
- Docker Compose 文件中的
# DERP_VERIFY_CLIENTS=true去掉#启用。
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 都准备好后在终端执行。
docker compose up -d验证
浏览器打开 https://derper.example.com 显示 DERP 字样证明成功。
Tailscale 组网
这部分可以参考 多多先生 的文章。
-
Caddy 的容器镜像是编译了第三方的插件,如果有需要编译自己的 Caddy,可以自己去编译。可以参考 3 https://github.com/caddy-dns/cloudflare https://github.com/caddyserver/forwardproxy ↩︎
-
Client verification ↩︎
-
Dockerfile ↩︎
评论