之前写了篇 frp 内网穿透的教程,当时是直接把 frps 二进制文件丢服务器上用 systemd 管理的。后来寻思这玩意儿能不能用 Docker 跑,一来方便管理不怕环境搞乱,二来换服务器的时候 docker-compose up -d 一行命令就搞定了不用重新配。折腾了一下发现还真挺好使,搞了个精简镜像才不到 30M,干脆开个仓库记录一下。

仓库地址:https://github.com/Jerry-FaGe/frps-docker

先决条件

Docker

Docker Compose

一台有公网 IP 的服务器(云服务器就行)

为什么用 Docker 跑 frps

直接跑二进制文件有啥问题呢?倒也不是不行,就是有点烦:

  1. 环境依赖 —— 虽然 frps 本身是静态编译的没啥依赖,但你想改配置、看日志、管理进程,还是得手动搞 systemd service 文件
  2. 升级麻烦 —— 每次升级得手动下载、解压、替换文件、重启服务,搞不好还得回滚
  3. 多实例不方便 —— 如果你想跑多个 frps 实例(比如测试环境和生产环境),用 systemd 管理就比较痛苦

用 Docker 的话这些问题都不存在了。镜像自带配置,升级就改个版本号重新 build,多实例直接改个端口映射就行。

项目结构

整个项目就四个文件,非常简单:

1
2
3
4
5
frps-docker/
├── Dockerfile
├── docker-compose.yml
├── frps.toml
└── LICENSE

Dockerfile 解析

直接贴代码,关键地方我都加了注释:
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
# 用 Alpine 做基础镜像,整个 build 完不到 30M
FROM amd64/alpine:3.20.1

# 改版本号就能升级 frp
ENV FRP_VERSION=0.58.1
# 设时区,不然日志时间戳是 UTC 的看着别扭
ENV TZ=Asia/Shanghai
EXPOSE 10069

# 换阿里云镜像源,国内服务器 apk update 不卡
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
apk update && \
apk add --no-cache wget tzdata && \
# 配时区
ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && \
echo $TZ > /etc/timezone && \
rm -rf /usr/local/*.apk /tmp/* /var/cache/apk/*

WORKDIR /frps

# 从 GitHub 下载 frp release,解压后只保留服务端文件
RUN wget -O frp.tar.gz https://github.com/fatedier/frp/releases/latest/download/frp_${FRP_VERSION}_linux_amd64.tar.gz && \
tar -xzf frp.tar.gz --strip-components=1 && \
rm frp.tar.gz && \
# 客户端的文件用不上,删掉减小体积
rm frpc && \
rm frpc.toml && \
rm frps.toml && \
rm LICENSE

# 把自定义的 frps.toml 配置文件拷进去
COPY . .

# 启动 frps,指定配置文件
CMD ["./frps", "-c", "frps.toml"]

几个要点:

  • 基础镜像用的 Alpine,整个镜像 build 完才不到 30M,比用 Ubuntu 小太多了
  • FRP_VERSION 改一下就能升级 frp 版本,不用手动下载
  • 换了阿里云镜像源,不然在国内服务器上 apk update 能给你卡半天
  • 配了时区为 Asia/Shanghai,日志时间戳就对了
  • 下载完 frp 之后把客户端的文件(frpcfrpc.toml)都删了,服务端用不上
镜像大小只有不到 30M,Alpine 真香。

docker-compose.yml

直接贴仓库里的配置:

1
2
3
4
5
6
7
8
9
10
11
12
services:
frps:
build: . # 用当前目录的 Dockerfile 构建
image: frps # 构建后的镜像名
container_name: frps # 容器名,方便 docker logs 看日志
privileged: true # 给容器特权模式,避免某些权限问题
restart: always # 崩了自动重启,服务器重启也会拉起来
network_mode: host # 用宿主机网络,省得一个个映射端口
environment:
- TZ=Asia/Shanghai # 容器内时区
volumes:
- ./frps.toml:/frp/frps.toml # 配置文件挂载进去

这里用了 network_mode: host,意思是容器直接使用宿主机的网络栈,不需要额外做端口映射。为啥用 host 模式呢?因为 frps 需要监听的端口是动态的——你的 frpc 客户端连什么端口,frps 就要开什么端口。如果用 bridge 模式的话,每加一个代理就得在 docker-compose 里加一行端口映射,贼麻烦。

当然 host 模式也有缺点,就是容器和宿主机共用网络命名空间,隔离性差一些。但对于 frps 这种场景来说完全够用了。

查看各字段解释
  • build: . —— 用当前目录的 Dockerfile 构建镜像
  • image: frps —— 指定构建后的镜像名称,方便后续管理
  • container_name: frps —— 容器名叫 frps,docker logs frps 就能看日志
  • privileged: true —— 特权模式,给容器更高的权限,避免一些网络相关的权限报错
  • restart: always —— 容器挂了自动重启,docker-compose down 才会停
  • network_mode: host —— 直接用宿主机网络,不需要 -p 映射端口
  • environment: TZ —— 设时区,跟 Dockerfile 里的保持一致
  • volumes —— 把宿主机的 frps.toml 挂载到容器里的 /frp/frps.toml

frps.toml 配置

1
2
3
4
5
6
7
bindPort = 7000                        # frpc 连接用的端口
auth.token = "your_strong_token_here" # 客户端也要填一样的,改成强密码!

webServer.addr = "0.0.0.0" # Dashboard 监听地址
webServer.port = 7100 # Dashboard 端口,别跟其他服务冲突
webServer.user = "admin" # Dashboard 用户名
webServer.password = "your_dashboard_password" # Dashboard 密码,也要改!

auth.token 一定要改成强密码!别用 12345678 这种。同理 Dashboard 的密码也要改,不然被人扫到 Dashboard 就能看到你所有的代理配置。

使用方法

1
2
git clone https://github.com/Jerry-FaGe/frps-docker.git
cd frps-docker

先把 token 和密码改了!这是最重要的一步。

1
2
vim frps.toml
# 把 auth.token 和 webServer.password 改成你自己的强密码
1
docker-compose up -d

看到类似这样的输出就说明成功了:

1
2
3
[+] Building 12.5s (9/9) FINISHED
[+] Running 1/1
✔ Container frps Started
1
docker-compose logs -f

看到 frps tcp listen success 就说明服务端启动成功了。

升级 frp 版本

只需要改 Dockerfile 里的 FRP_VERSION

1
ENV FRP_VERSION=0.61.1  # 改成你想要的版本

然后重新 build:

1
2
docker-compose build --no-cache
docker-compose up -d

搞定。回滚也简单,把版本号改回去重新 build 就行。

跟客户端配合

服务端用 Docker 跑了之后,客户端(frpc)的配置跟之前一样不用变。参考之前写的那篇 frp 内网穿透从零搭建指南,把 serverAddr 改成你服务器的公网 IP 就行。

1
2
3
4
5
6
7
8
9
10
11
12
serverAddr = "你的服务器公网IP"
serverPort = 7000

auth.method = "token"
auth.token = "your_strong_token_here"

[[proxies]]
name = "sd-webui"
type = "tcp"
localIP = "127.0.0.1"
localPort = 7860
remotePort = 6000

一些细节

部署过程中踩过的点

自动重启

restart: always 保证了容器异常退出后会自动重启,除非你手动 docker-compose down。服务器重启后容器也会自动拉起来,不用操心。

日志管理

frps 的日志直接输出到 stdout,用 docker-compose logs -f 就能实时查看。不加任何额外配置,Docker 自己会管理日志轮转。

配置文件热更新?

frp 不支持配置文件热更新。改了 frps.toml 之后必须重启容器才能生效:docker-compose restart frps

特权模式

用了 privileged: true 是因为 frps 需要操作网络相关的系统调用。如果不用特权模式,某些情况下可能会报权限错误。当然如果你的环境不需要可以去掉这行。

总结

用 Docker 跑 frps 的好处:

部署简单,一行命令搞定

升级方便,改个版本号重新 build

镜像小,Alpine 基础镜像不到 30M

日志管理、自动重启都有了

换服务器整个目录拷过去 docker-compose up -d 就完事