Docker 容器中部署 Jupyter Notebook 并通过宿主机端口访问的实践记录:使用 socat 桥接 Docker 与宿主机端口

1. 问题背景

在深度学习环境部署过程中,经常会将实验环境运行在 Docker 容器中,例如 SAM2、PyTorch 等模型环境。为了方便开发和调试,需要在宿主机浏览器中访问容器内的 Jupyter Notebook。

本次场景如下:

  • 宿主机:Ubuntu 系统
  • Docker 容器:运行深度学习环境
  • 容器内部:
    • Python 虚拟环境 .venv
    • Jupyter Notebook 服务
  • 目标:

通过宿主机 IP 和指定端口访问容器中的 Jupyter:

http://宿主机IP:9999

最终实现:

浏览器
   |
   | 10.12.54.121:9999
   |
宿主机 Ubuntu
   |
   | socat 转发
   |
Docker 容器 172.17.0.x:8888
   |
   |
Jupyter Notebook

2. 初始问题

进入 Docker 容器:

docker exec -it <container_name> bash

启动虚拟环境:

source .venv/bin/activate

然后启动 Jupyter:

jupyter notebook

发现无法启动:

Running as root is not recommended. Use --allow-root to bypass.

原因:

Docker 容器默认通常使用 root 用户运行,而 Jupyter 默认禁止 root 启动。


3. 配置 Jupyter 服务

解决方法:

指定允许 root 用户运行:

jupyter notebook --allow-root

但是默认情况下:

Jupyter 只监听:

127.0.0.1:8888

只能容器内部访问。

因此需要指定监听所有地址:

jupyter notebook \
    --ip=0.0.0.0 \
    --port=8888 \
    --allow-root

启动后:

Jupyter Server is running at:

http://0.0.0.0:8888

查看端口:

netstat -tlnp | grep 8888

正常:

tcp 0 0 0.0.0.0:8888 LISTEN

说明 Jupyter 已经允许外部连接。


4. Docker 网络访问测试

查看 Docker 容器 IP:

宿主机:

docker inspect <container_id> | grep IPAddress

例如:

172.17.0.4

测试宿主机访问容器:

curl http://172.17.0.4:8888/tree

返回:

HTTP/1.1 302 Found
Location: /login?next=%2Ftree

说明:

  • Docker 网络正常
  • Jupyter 服务正常
  • 宿主机可以访问容器

5. 使用 socat 桥接 Docker 与宿主机端口

5.1 为什么需要 socat

如果 Docker 创建容器时没有设置:

-p 9999:8888

那么容器端口不会直接映射到宿主机。

重新创建容器成本较高,因此采用 socat 做端口转发。

结构:

宿主机9999端口
        |
        |
       socat
        |
        |
Docker容器172.17.0.4:8888

5.2 安装 socat

Ubuntu:

sudo apt install socat

5.3 创建端口桥接

宿主机执行:

socat TCP-LISTEN:9999,reuseaddr,fork TCP:172.17.0.4:8888

参数说明:

参数作用
TCP-LISTEN:9999监听宿主机9999端口
reuseaddr允许端口快速复用
fork每个连接创建独立进程
TCP:172.17.0.4:8888转发到Docker Jupyter

6. 验证 socat 转发

查看监听:

ss -tlnp | grep 9999

显示:

LISTEN *:9999

测试:

curl -v http://127.0.0.1:9999/tree

返回:

HTTP/1.1 302 Found
Location: /login?next=%2Ftree

说明:

端口桥接成功。

完整链路:

127.0.0.1:9999

↓

socat

↓

172.17.0.4:8888

↓

Jupyter

7. 开放 Ubuntu 防火墙端口

如果其他机器无法访问宿主机,需要检查 Ubuntu 防火墙。

查看状态:

sudo ufw status

开放 9999 TCP:

sudo ufw allow 9999/tcp

查看:

sudo ufw status

结果:

9999/tcp ALLOW

此时其他机器可以访问:

http://10.12.54.121:9999

8. 删除防火墙规则

如果不再需要开放:

sudo ufw delete allow 9999/tcp

查看规则:

sudo ufw status numbered

也可以根据编号删除:

sudo ufw delete <编号>

9. 最终方案总结

最终采用方案:

Docker 内

启动 Jupyter:

jupyter notebook \
    --ip=0.0.0.0 \
    --port=8888 \
    --allow-root

宿主机

获取容器 IP:

docker inspect <container>

例如:

172.17.0.4

启动 socat:

socat TCP-LISTEN:9999,reuseaddr,fork TCP:172.17.0.4:8888

防火墙

开放端口:

sudo ufw allow 9999/tcp

浏览器访问

http://宿主机IP:9999

例如:

http://10.12.54.121:9999

即可访问 Docker 内的 Jupyter Notebook。


10. 总结

本次问题的核心不是 Jupyter 本身,而是 Docker 容器网络与宿主机端口之间缺少映射关系

解决方案由三部分组成:

  1. Jupyter 配置外部监听
--ip=0.0.0.0

确保容器内部服务可以被访问。

  1. socat 实现 Docker 与宿主机端口桥接
TCP-LISTEN:9999 → TCP:container_ip:8888

实现宿主机端口转发。

  1. Ubuntu 防火墙开放访问端口
ufw allow 9999/tcp

最终实现:

外部浏览器
      |
      |
宿主机:9999
      |
      |
socat
      |
      |
Docker:8888
      |
      |
Jupyter Notebook

该方案适用于:

  • 已经运行的 Docker 容器
  • 不方便重新创建容器
  • 需要快速暴露开发服务(Jupyter、TensorBoard、Web 服务等)的场景。

更多推荐