云边协同:基于 Docker 与 FRP 的家庭实验室全栈内网穿透指南

摘要 :家里有一台高性能工作站(如 ThinkStation P520),如何利用一台配置普通的公网云服务器,安全、优雅地从外网访问家里的服务?本文将介绍一套基于 Docker 的全栈解决方案,实现零侵入云端现有环境 、本地 Nginx 统一网关 以及基于 URL 路径的服务分发。

1. 场景与需求

很多 HomeLab 玩家都有这样的困扰:

  1. 家庭宽带无公网 IP:高性能设备(P520, NAS)只能在局域网"自嗨"。
  2. 云服务器配置低:为了公网 IP 买的 VPS 通常只有 1核2G 或 2核4G,跑不动重型应用。
  3. 端口管理混乱 :每次加一个服务就要在防火墙开一个端口,记不住 IP:5000, IP:8081 这种地址。

本文的目标方案:

  • 云端 (Server):只做流量中转,不占用 80/443 端口(避免影响云端现有的建站业务)。
  • 边缘端 (Client) :家庭服务器通过 Docker 运行 Nginx 网关,所有服务通过 http://云IP:8080/app1/、http://云IP:8080/app2/ 的方式访问。

2. 架构设计

我们采用 FRP (Fast Reverse Proxy) 建立隧道,配合 Nginx 进行七层反向代理。

复制代码
graph TD
    User[用户] -->|访问 http://云IP:8080/test/| Cloud_Firewall
    
    subgraph Cloud_Server [云服务器 (VPS)]
        Cloud_Firewall[防火墙 TCP:8080] --> Cloud_FRPS
        Existing_Nginx[现有 Nginx (Port 80)] -.->|互不干扰| Cloud_FRPS
        Cloud_FRPS[Docker: FRP 服务端]
    end

    Cloud_FRPS <==>|FRP 加密隧道 (TCP:7000)| Home_FRPC

    subgraph Home_Server [家庭服务器 (P520)]
        Home_FRPC[Docker: FRP 客户端] -->|流量转发| Home_Nginx
        Home_Nginx[Docker: Nginx 网关 (Port 80)]
        
        Home_Nginx -->|Location /test/| Web_App_1[容器: 测试页]
        Home_Nginx -->|Location /alist/| Web_App_2[容器: 网盘]
    end

3. 云服务器端部署 (FRP Server)

策略 :为了不影响云服务器上已经运行的 Nginx(占用 80 端口),我们将 FRP 的 Web 访问端口设为 8080。

3.1 配置文件 frps.toml

复制代码
# frps.toml
bindPort = 7000           # FRP 客户端连接端口
vhostHTTPPort = 8080      # 【关键】外部访问 Web 的入口端口
auth.token = "YourStrongPassword2025" # 通信密钥

# 仪表盘配置 (可选)
webServer.addr = "0.0.0.0"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "admin"

3.2 启动容器 docker-compose.yml

复制代码
version: '3.8'
services:
  frps:
    image: snowdreamtech/frps:latest
    container_name: frps
    restart: always
    network_mode: host  # 使用 host 模式,直接监听宿主机端口
    volumes:
      - ./frps.toml:/etc/frp/frps.toml

注意 :启动后,请务必在云服务器控制台(安全组)放行 TCP 7000 和 8080 端口。


4. 家庭服务器端部署 (FRP Client + Nginx)

策略:在 P520 上,我们使用 Docker Compose 编排三个服务:FRP 客户端、Nginx 网关、以及一个测试用的 Web 服务。

4.1 目录结构

建议在 ~/homelab 下统一管理:

复制代码
homelab/
├── docker-compose.yml
├── frpc.toml
└── nginx/
    ├── conf.d/
    │   └── default.conf
    └── html/
        └── index.html

4.2 穿透配置 frpc.toml

复制代码
serverAddr = "x.x.x.x"  # 你的云服务器公网 IP
serverPort = 7000
auth.token = "YourStrongPassword2025" # 必须与服务端一致

[[proxies]]
name = "home-gateway"
type = "http"
localIP = "127.0.0.1"
localPort = 80             # 将流量转发给本地的 Nginx
customDomains = ["x.x.x.x"] # 填云服务器 IP,如果有域名则填域名

4.3 Nginx 网关配置 nginx/conf.d/default.conf

这是实现 "路径分发" 的核心配置。

复制代码
server {
    listen       80;
    server_name  localhost;
    charset utf-8;  # 防止中文乱码

    # 1. 默认首页
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    # 2. 业务示例:测试服务
    # 外部访问: http://云IP:8080/test/
    # 内部转发: http://127.0.0.1:8081/
    location /test/ {
        # 【重点】proxy_pass 结尾加 / 表示截断路径
        # 即把 /test/ 后面的内容传给后端,去掉 /test 本身
        proxy_pass http://127.0.0.1:8081/; 
        
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

4.4 整体编排 docker-compose.yml

复制代码
version: '3.8'

services:
  # 服务 1: 穿透客户端
  frpc:
    image: snowdreamtech/frpc:latest
    container_name: frpc
    restart: always
    depends_on:
      - nginx-gateway
    volumes:
      - ./frpc.toml:/etc/frp/frpc.toml
    network_mode: "host" # 使用 Host 模式,方便访问本机 Localhost

  # 服务 2: 本地 Nginx 网关
  nginx-gateway:
    image: nginx:latest
    container_name: nginx-gateway
    restart: always
    ports:
      - "80:80" # 占据 P520 的 80 端口
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/html:/usr/share/nginx/html

  # 服务 3: 模拟业务应用 (用于测试)
  web-demo:
    image: nginx:alpine
    container_name: web-demo
    restart: always
    ports:
      - "8081:80" # 实际运行在 8081,通过网关转发
    # 简单映射个页面进去方便看效果
    volumes:
      - ./nginx/html:/usr/share/nginx/html 

5. 部署与验证

5.1 启动服务

在 P520 的 homelab 目录下执行:

复制代码
docker compose up -d

5.2 验证效果

  1. 内网验证 : 打开浏览器访问 P520 的内网 IP:http://192.168.x.x/test/,应该能看到测试页面。
  2. 公网验证 : 打开手机 4G,访问:http://云服务器IP:8080/test/。

如果一切顺利,你应该能看到同样的页面。这意味着:请求 -> 云服务器 -> FRP 隧道 -> 家里 P520 -> Nginx -> 具体容器 的链路已经打通。


6. 技术避坑指南 (必读)

虽然"路径转发"(如 /app/)看起来很美,但在实际部署复杂应用时,你可能会遇到 静态资源 404 的问题。

  • 原因 : 比如你访问 /alist/,页面加载了一个 CSS 文件 /style.css。浏览器会去请求 http://云IP:8080/style.css,Nginx 会发现根目录下没有这个文件,因为这个文件实际在 /alist/ 下。
  • 解决方案 :
    1. 首选 :在应用本身的设置里,寻找 Base URL 或 Web Root 选项,将其设置为 /alist/。
    2. 备选 :如果应用不支持 Base URL,建议放弃路径转发,改用 子域名 方案(如 alist.yourdomain.com),这需要你有自己的域名。

7. 总结

通过这套方案,我们成功地将家庭服务器纳入了云端网络架构中。未来如果想增加新服务(比如 HomeAssistant),只需:

  1. 在 Docker Compose 里添加服务容器。
  2. 在 nginx/conf.d/default.conf 里增加一段 location /ha/ { ... }。
  3. 重启 Nginx,无需修改云服务器和防火墙设置。

Happy Hacking!

相关推荐
分布式存储与RustFS5 天前
MinIO 官方 Docker 镜像被移除:依赖它的项目该怎么办
docker·云原生·devops·对象存储·minio·分布式存储
codeejun5 天前
每日一Go·MySQL-5、锁机制全解析
云原生·golang
张洛闻Eren6 天前
k8s云原生【第十课】:水平 Pod 自动扩缩容
运维·数据库·云原生·kubernetes·github
不吃香菜kkk、6 天前
CI/CD(GitOps)学习与部署手册
运维·云原生·容器·kubernetes·云计算·jenkins·argocd
ChaITSimpleLove6 天前
云原生性能对决:JDK21虚拟线程+GraalVM vs .NET10/11 技术栈深度对比
微服务·云原生·serverless·graalvm native·native aot·runtime async·.net11/java21
春天花会开1316 天前
Supabase 自部署 Edge Functions 超时从 60s 延长到 360s 完整复盘
运维·云原生
ysu_03147 天前
【2026】K8s GPU调度空占怎么解?Kueue与Volcano Gang配置
云原生·kubernetes·gpu算力·volcano·ai基础设施·kueue·gang scheduling
天天喝旺仔8 天前
分布式服务容错实战:用 Sentinel 实现限流、熔断与降级
分布式·微服务·云原生·sentinel
逐流人8 天前
Containerd容器管理实战:从架构原理到nerdctlcrictl工具链
linux·运维·云原生·容器·云计算·containerd
运维开发王义杰8 天前
跨项目直连数据库:是架构反模式,还是现实的工程妥协?
云原生