Docker Sidecar VPN 更Dockerize的透明代理

大类
Util
Env
技术标签
环境增强
优先级
High
状态
Maintaining
开始日期
Aug 17, 2025
最后更新
Aug 17, 2025
Public
Public

更新202601 - 通过替换init免去一大串command

原来的写法有几个问题:
  1. 需要装ip命令
  1. 会盖掉原来的command
  1. 太长了,不好看
 
Q:如果我们又不想换entrypoint,又不想换command,怎么才能把我们注入进去呢?
A:有个好办法,通过docker的--init选项可以让都docker在entrypoint和command的外面再套一层init,我们只要直接换掉init进程就可以注入自定义逻辑。
 
docker的init选项会注入/sbin/docker-init,这个进程不开源(
docker-init ("docker init" command) sources?
Github
docker-init ("docker init" command) sources?
Updated
Feb 7, 2024
),所以我们图省事直接造一个wrapper,执行完后再execve过去。
用AI把我原来的bash脚本转换了一下:
docker_tun_helper
NyaMistyUpdated Mar 7, 2026
新的模板:
version: '3.8' services: clash: image: metacubex/mihomo:v1.18.3 restart: always devices: - /dev/net/tun networks: vpn-net: volumes: - ./clash-conf:/root/.config/mihomo cap_add: - NET_ADMIN s: depends_on: - clash init: true networks: default: vpn-net: cap_add: - NET_ADMIN volumes: - ./tun_helper/docker_tun_helper:/sbin/docker-init - ./tun_helper/docker-init_ori:/sbin/docker-init_ori sysctls: - "net.ipv6.conf.all.disable_ipv6=1" # Service custom properties image: XXXXXX restart: always networks: default: vpn-net: driver: bridge

TL;DR:

自定义下面的配置即可,注意:
  • 需要填入./clash-conf,并开启tun
  • 注意clash容器的名称不可修改,否则需同步修改下面的脚本
  • 修改command属性中的最后一条命令为相应image的CMD
  • 应用的image中需要带有ip命令(或许也可以放一个静态编译的iproute2)
  • 有可能需要配置sysctl -w net.ipv4.conf.all.rp_filter=2
version: '3.8' services: clash: image: metacubex/mihomo:v1.18.3 restart: always devices: - /dev/net/tun networks: vpn-net: volumes: - ./clash-conf:/root/.config/mihomo cap_add: - NET_ADMIN s: depends_on: - clash init: true networks: lan-net: vpn-net: cap_add: - NET_ADMIN command: | sh -euxc ' sleep 2 gw=$(getent hosts clash | awk "{ print \$1 }" || true) if [ -z "$$gw" ]; then echo "ERROR: failed to resolve clash" >&2 exit 1 fi echo "Resolved clash to $$gw" ip a ip r ip route del default || true ip route add default via $$gw exec bash -x /root/start.sh' sysctls: - "net.ipv6.conf.all.disable_ipv6=1" # Service custom properties image: XXXXXX restart: always networks: vpn-net: driver: bridge lan-net: driver: bridge
 

现有方案的缺点:

  • 方案1:在宿主机上开tun
    • 不共享netns,导致宿主机无法区分流量来源
    • 配合fwmark可以实现区分,但是由于docker没法自己自动打fwmark,维护代价太高
    • 配合固定ip也可以区分,但是这样需要自主管理docker的ip池分配,维护代价也不低
  • 方案2:network-mode: container:XXX
    • 使用这个方法共享netns,只能在XXX上设置expose
    • 多个容器共享ip地址,无法多次bind同一个端口
  • 方案3:在容器内开vpn
    • 增加镜像大小和复杂度,难以长期维护
  • 方案4:Custom Network Plugin (Network Driver)
    • yassine/soxy-driver、vincent-163/docker-transparent-socks5
    • 长期未更新(7年)、无大规模应用
    • 需要自定义network driver,可能与docker compose兼容不好
    • 使用的是redsocks,基于iptables而不是tun,在复杂网络环境下效果可能不如clash/sing-box的tun方案

原理:

  • 同时连接到2个network,一个起到compose default network的作用(lan-net),一个专门用于与vpn连接(vpn-net)
    • (或许可以只有1个network,但这样不容易debug)
  • clash启动tun,应用的service则通过command中的小脚本根据hostname解析clash并自动设置gateway
  • 这样无需配置任何固定ip段、固定ip地址,且仍然可以正常使用expose等功能