用 Docker + redsocks 实现「一容器一出口 IP」:透明 SOCKS5 代理网关
很多时候,给容器设置 HTTP_PROXY/ALL_PROXY 并不可靠:有的程序不读环境变量,有的仅支持 HTTP 代理不支持 SOCKS5,还有的会把一部分流量绕过代理。更稳的做法是系统级透明代理——把容器里的所有 TCP 流量统一“劫持”并转发到指定的 SOCKS5,由此实现一个容器一个出口 IP。
思路与架构
我们做一个单独的“网关容器”,在这个容器里运行:
- redsocks:把透明转发的 TCP 连接封装成 SOCKS5 请求;
- iptables (NAT/OUTPUT):在该网络命名空间内重定向所有 TCP 出站流量到 redsocks 监听口。
随后,让业务容器通过 --network container:<网关容器名> 复用网关容器的网络栈。这样业务容器无感知地全量走代理。
业务容器 → (共享网络栈) → iptables(OUTPUT) → redsocks:12345 → SOCKS5 → 目标网站优点:
- 应用容器无需更改镜像或配置;
- 真正“全局”代理(TCP 层),不依赖应用是否支持代理环境变量;
- 多个网关容器 = 多个出口 IP;业务容器按需绑定对应网关。
环境准备
- 宿主机:Debian/Ubuntu(其他 Linux 发行版亦可)
- 已安装 Docker,能拉取镜像、启动容器
- 具备一个或多个可用 SOCKS5 代理(可匿名或带用户名/密码)
实操步骤(逐步 + 每步目的)
以网关容器名
proxy1、示例 SOCKS560.188.121.252:20002为例。
1. 创建“网关容器”
docker run -d --name proxy1 --cap-add=NET_ADMIN --restart unless-stopped debian:bookworm-slim sleep infinity目的:
--cap-add=NET_ADMIN让容器内能设置 iptables;- 独立网络命名空间,用作“透明代理网关”;
sleep infinity占位常驻;重启策略保证容器随宿主开机恢复。
2. 安装工具(redsocks / iptables / curl 等)
docker exec -it proxy1 bash -lc 'apt update && apt install -y redsocks iptables curl procps net-tools ca-certificates'目的:
redsocks做透明转发至 SOCKS5;iptables写 NAT/OUTPUT 规则;curl用于探测代理可用与验证出口 IP;procps/net-tools用于排错(ps/netstat);ca-certificates避免 HTTPS 验证问题。
3. 先直接用 curl 测 SOCKS5 是否可用(绕过 redsocks)
docker exec -it proxy1 bash -lc 'curl -v --socks5 60.188.121.252:20002 https://ipinfo.io/ip'目的:
- 在启用透明代理前,确认 SOCKS5 本身能正常出外网;
- 如果这里就超时/失败,后续即使配置再完美也没用。
4. 写入 redsocks.conf
docker exec -i proxy1 bash <<'EOF'
cat > /etc/redsocks.conf <<'EOC'
base {
log_debug = off;
log_info = on;
log = "stderr";
daemon = on;
redirector = iptables;
}
redsocks {
local_ip = 127.0.0.1;
local_port = 12345;
ip = 60.188.121.252; # SOCKS5 服务器 IP
port = 20002; # SOCKS5 端口
type = socks5;
# 若需认证,取消注释:
# login = "username";
# password = "password";
}
EOC
EOF目的与要点:
- 配置 redsocks 监听
127.0.0.1:12345; - 指向你的 SOCKS5;
- 配置文件必须是纯 ASCII,避免中文/奇怪字符导致解析失败。
5. 启动 redsocks
docker exec -d proxy1 bash -lc 'redsocks -c /etc/redsocks.conf'目的:
- 让 redsocks 常驻监听;如需诊断可用
-t前台模式查看日志。
6. 下发 iptables 规则(透明转发)
docker exec -it proxy1 bash -lc '
iptables -t nat -F
iptables -t nat -A OUTPUT -p tcp -d 127.0.0.1 -j RETURN
iptables -t nat -A OUTPUT -p tcp -d 60.188.121.252 -j RETURN
iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports 12345
'目的与要点:
- 清空旧规则,避免重复叠加;
- 放行本机回环与代理服务器本身(否则会套娃死循环);
- 其余 TCP 全部重定向到
12345→ 由 redsocks 送往 SOCKS5。
说明:这是在 proxy1 的网络命名空间 内生效,不影响宿主机和其他容器。
7. 验证出口 IP(确认透明代理生效)
docker exec -it proxy1 bash -lc 'curl -s https://ipinfo.io/ip'期望:输出为 SOCKS5 的公网 IP(例如 114.86.2.30)。
意义:证明透明代理链路 容器 → iptables → redsocks → SOCKS5 正常。
8. 让业务容器复用网关网络(“一容器一出口 IP”)
docker run -d \
--restart unless-stopped \
--network container:proxy1 \
packetshare/packetshare \
-accept-tos \
-email=<你的邮箱> \
-password=<你的密码>目的:
- 业务容器与
proxy1共用网络栈,因此自动走上一步配置好的透明代理; - 适用于任何镜像,不需要在应用层配置代理变量。
切换/替换 SOCKS5 的正确姿势
当旧代理失效,需要切换新代理时:
# 1) 恢复直连(避免“断网时无法 apt/curl”)
docker exec -it proxy1 bash -lc 'iptables -t nat -F'
# 2) 重启或重启 redsocks(如需)
docker restart proxy1 # 简洁做法,顺带清理僵尸进程
# 3) 先直接用 curl 测新 SOCKS5 是否通
docker exec -it proxy1 bash -lc 'curl -v --socks5 <NEW_IP:PORT> https://ipinfo.io/ip'
# 4) 改 /etc/redsocks.conf,启动 redsocks
# 5) 重新下发三条 iptables 规则(放行 127.0.0.1 / 新代理 IP / 其余全转发)常见问题 & 排错建议
redsocks报“unexpected char / unclosed section” → 配置文件含中文或${VAR}这类 shell 变量占位符(redsocks 不会展开),改为硬编码纯 ASCII。curl -s https://ipinfo.io/ip无输出 → 常见于 SOCKS5 仅“能连上端口”但不能完整转发 HTTPS(公开代理很多这种)。先用curl -v --socks5 <IP:PORT> https://ipinfo.io/ip直连测试;查代理质量。[redsocks] <defunct>僵尸进程 → 代理或配置异常导致短启即死,重启容器最干净:docker restart proxy1。 重新按「先清规则 → 测代理 → 写配置 → 启动 → 下规则」顺序来。DNS/UDP 泄漏 → 透明代理本方案主要拦截 TCP。对 DNS 建议:应用侧启用 DoH/DoT 或使用
redudp/dns2socks进一步处理。 (如果你的业务全是 HTTPS/TLS,通常已足够。)IPv6 → 如需代理 IPv6,另配一组
ip6tables -t nat ...规则(思路同 IPv4)。
一键脚本(先测 SOCKS5,再自动建“网关容器”)
把下面内容保存为 create_proxy_container.sh,赋予执行权限 chmod +x create_proxy_container.sh。
用法:./create_proxy_container.sh <容器名> <SOCKS_IP> <SOCKS_PORT>
#!/bin/bash
# ===============================
# 一键创建 SOCKS5 透明代理“网关容器”
# 用法示例:
# ./create_proxy_container.sh proxy1 60.188.121.252 20002
# ===============================
set -e
NAME=$1
SOCKS_IP=$2
SOCKS_PORT=$3
TARGET_URL="https://ipinfo.io/ip"
if [[ -z "$NAME" || -z "$SOCKS_IP" || -z "$SOCKS_PORT" ]]; then
echo "用法: $0 <容器名> <SOCKS_IP> <SOCKS_PORT>"
exit 1
fi
# 1) 准备容器
if docker ps -a --format '{{.Names}}' | grep -q "^${NAME}$"; then
echo "容器 ${NAME} 已存在,直接使用。"
else
echo "创建容器 ${NAME}..."
docker run -d --name "$NAME" --cap-add=NET_ADMIN --restart unless-stopped debian:bookworm-slim sleep infinity
fi
# 2) 安装依赖
echo "安装 redsocks 和依赖..."
docker exec "$NAME" bash -lc 'apt update -qq && apt install -y -qq redsocks iptables curl procps net-tools ca-certificates >/dev/null'
# 3) 预检 SOCKS5 可用性(绕过透明代理)
echo "测试 SOCKS5 代理是否可用 (${SOCKS_IP}:${SOCKS_PORT}) ..."
if docker exec "$NAME" bash -lc "curl -s --max-time 6 --socks5 ${SOCKS_IP}:${SOCKS_PORT} ${TARGET_URL}" >/dev/null 2>&1; then
echo "✅ SOCKS5 测试通过"
else
echo "❌ SOCKS5 无法连接或超时,请检查地址/端口/可用性"
exit 1
fi
# 4) 写入 redsocks 配置
echo "写入 /etc/redsocks.conf..."
docker exec -i "$NAME" bash <<EOF
cat > /etc/redsocks.conf <<EOC
base {
log_debug = off;
log_info = on;
log = "stderr";
daemon = on;
redirector = iptables;
}
redsocks {
local_ip = 127.0.0.1;
local_port = 12345;
ip = ${SOCKS_IP};
port = ${SOCKS_PORT};
type = socks5;
}
EOC
EOF
# 5) 启动 redsocks
echo "启动 redsocks..."
docker exec -d "$NAME" bash -lc 'redsocks -c /etc/redsocks.conf'
# 6) 配置 iptables(先清空再下发)
echo "配置 iptables 规则..."
docker exec "$NAME" bash -lc "
iptables -t nat -F
iptables -t nat -A OUTPUT -p tcp -d 127.0.0.1 -j RETURN
iptables -t nat -A OUTPUT -p tcp -d ${SOCKS_IP} -j RETURN
iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports 12345
"
# 7) 验证出口 IP(透明代理是否生效)
echo "验证透明代理出口 IP..."
IP_OUT=$(docker exec "$NAME" bash -lc "curl -s --max-time 8 ${TARGET_URL}" || true)
if [[ -n "$IP_OUT" ]]; then
echo "✅ 代理工作正常,出口 IP: $IP_OUT"
echo "👉 可通过 '--network container:${NAME}' 让其他容器共用此代理。"
exit 0
else
echo "⚠️ 未能获取出口 IP,请手动检查 redsocks/iptables/代理可用性。"
exit 2
fi使用例子:
./create_proxy_container.sh proxy1 60.188.121.252 20002
# 让业务容器共享代理网络
docker run -d --network container:proxy1 packetshare/packetshare \
-accept-tos -email=<你的邮箱> -password=<你的密码>