Dify离线安装沙箱服务的Python依赖包

背景

业务在使用Dify开发AI应用,AI应用会使用Python代码调用一些接口,涉及到安全认证,需要使用额外的Python包,但Dify沙箱模块的Python依赖包源码中只支持在线安装,在网络隔离情况下,将会无法离线安装,本文给出一些安装方案。

沙箱安装机制

Dify在部署时会部署一个sandbox模块,用于Python代码的执行。

依赖包安装

如果沙箱环境能够访问互联网,安装python包会非常简单,修改docker/volumes/sandbox/dependencies/python-requirements.txt文件,添加相关依赖及版本即可,比如:

Shell 复制代码
$ cat python-requirements.txt 
pycryptodome==3.18.0

修改完成后,重启Dify的Docker服务,这时SandBox服务在启动时就会执行依赖安装,运行日志如下,会从远程下载pycryptodome-3.18.0包。

Shell 复制代码
$ docker logs dify-sandbox-1
2025/10/13 14:42:55 setup.go:29: [INFO]initializing nodejs runner environment...
2025/10/13 14:42:55 setup.go:85: [INFO]nodejs runner environment initialized
2025/10/13 14:42:55 setup.go:33: [INFO]initializing python runner environment...
2025/10/13 14:42:55 config.go:129: [INFO]network has been enabled
2025/10/13 14:42:55 config.go:145: [INFO]using https proxy: http://ssrf_proxy:3128
2025/10/13 14:42:55 config.go:154: [INFO]using http proxy: http://ssrf_proxy:3128
2025/10/13 14:42:55 server.go:20: [INFO]config init success
2025/10/13 14:42:55 server.go:26: [INFO]runner dependencies init success
2025/10/13 14:42:55 cocrrent.go:31: [INFO]setting max requests to 50
2025/10/13 14:42:55 cocrrent.go:13: [INFO]setting max workers to 4
2025/10/13 14:42:55 server.go:47: [INFO]installing python dependencies...
2025/10/13 14:42:56 setup.go:140: [INFO]Collecting pycryptodome==3.18.0
2025/10/13 14:42:57 setup.go:140: [INFO]  Downloading pycryptodome-3.18.0-cp35-abi3-manylinux2014_aarch64.whl (2.1 MB)
2025/10/13 14:42:57 setup.go:140: [INFO]     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 4.0 MB/s eta 0:00:00
2025/10/13 14:42:57 setup.go:140: [INFO]Installing collected packages: pycryptodome
2025/10/13 14:42:58 setup.go:140: [INFO]Successfully installed pycryptodome-3.18.0
2025/10/13 14:42:58 setup.go:161: [INFO]Python dependency installed: pycryptodome 3.18.0
2025/10/13 14:42:58 server.go:53: [INFO]python dependencies installed
2025/10/13 14:42:58 server.go:55: [INFO]initializing python dependencies sandbox...
2025/10/13 14:43:20 server.go:60: [INFO]python dependencies sandbox initialized

源码分析

Dify的Python依赖包安装实现在dify-sandbox仓库的internal/core/runner/python/setup.go文件中,关键源码如下:

Go 复制代码
func InstallDependencies(requirements string) error {
  if requirements == "" {
   return nil
  }

  runner := runner.TempDirRunner{}
  return runner.WithTempDir("/", []string{}, func(root_path string) error {
   defer os.RemoveAll(root_path)
   // create a requirements file
   err := os.WriteFile(path.Join(root_path, "requirements.txt"), []byte(requirements), 0644)
   if err != nil {
    log.Error("failed to create requirements.txt")
    return nil
   }

   // install dependencies
   pipMirrorURL := static.GetDifySandboxGlobalConfigurations().PythonPipMirrorURL

   // Create the base command
   args := []string{"install", "-r", "requirements.txt"}
   if pipMirrorURL != "" {
    // If a mirror URL is provided, include it in the command arguments
    args = append(args, "-i", pipMirrorURL)
   }
   cmd := exec.Command("pip3", args...)
   reader, err := cmd.StdoutPipe()
   if err != nil {
    log.Error("failed to get stdout pipe of pip3")
    return err
   }
   ...
   return nil
  })
}

从源码中可以得到下面关键信息:

  1. 如果docker/volumes/sandbox/dependencies/python-requirements.txt文件内容为空,则不会触发下面安装。
  2. 最终采用pip3 install -r requirements.txt命令来安装。
  3. 源码预留了扩展机制,允许配置依赖包镜像地址,可以在docker/volumes/sandbox/conf/config.yaml配置文件中增加python_pip_mirror_url实现。比如,使用阿里云源。
yaml 复制代码
app:
  port: 8194
  debug: True
  key: dify-sandbox
max_workers: 4
max_requests: 50
worker_timeout: 5
python_path: /usr/local/bin/python3
# 指定使用阿里云镜像源
python_pip_mirror_url: https://mirrors.aliyun.com/pypi/simple/
enable_network: True # please make sure there is no network risk in your environment
allowed_syscalls: # please leave it empty if you have no idea how seccomp works
proxy:
  socks5: ''
  http: ''
  https: ''

解决方案

在网络隔离环境下,从Dify SandBox的安装源码看,并没有预留离线安装扩展机制,下面会给出2种解决方案。

使用本地镜像源

SandBox源码中预留了扩展机制,可以通过python_pip_mirror_url来指定镜像源,因此,我们可以使用devpi部署一个

本地镜像源,而后将依赖包上传到镜像服务器,这样就可以解决网络隔离的场景。镜像源的部署资料网上很多,这里不再赘述。

采用离线安装

下面给出一种更简便的方案,采用离线方式安装。从SandBox源码看,最终还是使用pip3 install -r requirements.txt命令执行安装,只要能在安装命令之后加上--no-index --find-links=/dependencies/packages即可,虽然源码没有预留扩展机制,但pip3命令是系统命令,我们包装一个pip3命令,让Go调用包装后的pip3命令。

  1. 替换掉系统的pip3命令。这一步基于系统的搜索逻辑来实现,SandBox的pip3命令的搜索路径如下。我们可以创建一个/opt/bin目录,在该目录下创建一个pip3可执行文件,然后将/opt/bin加到当前PATH的前面,这样Go在调用命令时,找到的就会是/opt/bin/pip3,而不是/usr/local/bin/pip3
Shell 复制代码
# 改造前
# echo $PATH
/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# which pip3
/usr/local/bin/pip3

# 改造后
# echo $PATH
/opt/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# which pip3
/opt/bin/pip3
  1. 包装后pip3命令实现。包装后的pip3命令需要调用原来的/usr/local/bin/pip3,在识别到install时,就在命令之后自动加上--no-index --find-links=/dependencies/packages/opt/bin/pip3内容如下:
Shell 复制代码
# cat /opt/bin/pip3
#!/bin/bash

# 检查是否包含 install 命令
has_install=false
for arg in "$@"; do
  if [ "$arg" = "install" ]; then
    has_install=true
    break
  fi
done

if [ "$has_install" = "true" ]; then
  exec /usr/local/bin/pip3 "$@" --no-index --find-links=/dependencies/packages
else
  exec /usr/local/bin/pip3 "$@"
fi
  1. Dify的docker-compose.yaml改造。

    a. volumes路径映射增加离线包映射,即./volumes/sandbox/dependencies/packages:/dependencies/packages:ro,离线包放到本地的./volumes/sandbox/dependencies/packages目录。

    b. volumes路径映射增加容器入口命令映射,即./volumes/sandbox/docker-entrypoint.sh:/docker-entrypoint.sh:rx

    c. 启动命令改造。sandbox的启动命令改造成entrypoint: [ '/docker-entrypoint.sh' ]

yaml 复制代码
sandbox:
  image: langgenius/dify-sandbox:0.2.12
  restart: always
  environment:
    # The DifySandbox configurations
    # Make sure you are changing this key for your deployment with a strong key.
    # You can generate a strong key using `openssl rand -base64 42`.
    API_KEY: ${SANDBOX_API_KEY:-dify-sandbox}
    GIN_MODE: ${SANDBOX_GIN_MODE:-release}
    WORKER_TIMEOUT: ${SANDBOX_WORKER_TIMEOUT:-15}
    ENABLE_NETWORK: ${SANDBOX_ENABLE_NETWORK:-true}
    HTTP_PROXY: ${SANDBOX_HTTP_PROXY:-http://ssrf_proxy:3128}
    HTTPS_PROXY: ${SANDBOX_HTTPS_PROXY:-http://ssrf_proxy:3128}
    SANDBOX_PORT: ${SANDBOX_PORT:-8194}
    PIP_MIRROR_URL: ${PIP_MIRROR_URL:-}
  volumes:
    - ./volumes/sandbox/dependencies:/dependencies
    - ./volumes/sandbox/conf:/conf
    - ./volumes/sandbox/dependencies/packages:/dependencies/packages:ro
    - ./volumes/sandbox/docker-entrypoint.sh:/docker-entrypoint.sh:rx
  entrypoint: [ '/docker-entrypoint.sh' ]
  healthcheck:
    test: [ 'CMD', 'curl', '-f', 'http://localhost:8194/health' ]
  networks:
    - ssrf_proxy_network
  1. docker-entrypoint.sh命令。
Shell 复制代码
#!/bin/bash

mkdir -p /opt/bin
cat > /opt/bin/pip3 << 'EOF'
#!/bin/bash

# 检查是否包含 install 命令
has_install=false
for arg in "$@"; do
  if [ "$arg" = "install" ]; then
    has_install=true
    break
  fi
done

if [ "$has_install" = "true" ]; then
  exec /usr/local/bin/pip3 "$@" --no-index --find-links=/dependencies/packages
else
  exec /usr/local/bin/pip3 "$@"
fi
EOF

chmod +x /opt/bin/pip3

echo 'export PATH="/opt/bin:$PATH"' >> /etc/profile
echo 'export PATH="/opt/bin:$PATH"' >> /etc/bash.bashrc
export PATH="/opt/bin:$PATH"

echo "Current PATH: $PATH"
echo "Which pip3: $(which pip3)"

# 执行原来的main程序
exec /main "$@"

通过上述改造后,再次启动Dify服务时,可以看到pycryptodome-3.18.0包在安装时,会从/dependencies/packages目录找到离线包pycryptodome-3.18.0-cp35-abi3-manylinux2014_aarch64.whl,然后直接安装,不再需要从外部下载。

ruby 复制代码
$ docker logs dify-sandbox-1
Current PATH: /opt/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Which pip3: /opt/bin/pip3
2025/10/13 10:46:12 setup.go:29: [INFO]initializing nodejs runner environment...
2025/10/13 10:46:12 setup.go:85: [INFO]nodejs runner environment initialized
2025/10/13 10:46:12 setup.go:33: [INFO]initializing python runner environment...
2025/10/13 10:46:12 config.go:129: [INFO]network has been enabled
2025/10/13 10:46:12 config.go:145: [INFO]using https proxy: http://ssrf_proxy:3128
2025/10/13 10:46:12 config.go:154: [INFO]using http proxy: http://ssrf_proxy:3128
2025/10/13 10:46:12 server.go:20: [INFO]config init success
2025/10/13 10:46:12 server.go:26: [INFO]runner dependencies init success
2025/10/13 10:46:12 cocrrent.go:31: [INFO]setting max requests to 50
2025/10/13 10:46:12 cocrrent.go:13: [INFO]setting max workers to 4
2025/10/13 10:46:12 server.go:47: [INFO]installing python dependencies...
2025/10/13 10:46:12 setup.go:140: [INFO]Looking in links: /dependencies/packages
2025/10/13 10:46:12 setup.go:140: [INFO]Processing /dependencies/packages/pycryptodome-3.18.0-cp35-abi3-manylinux2014_aarch64.whl
2025/10/13 10:46:12 setup.go:140: [INFO]Installing collected packages: pycryptodome
2025/10/13 10:46:13 setup.go:140: [INFO]Successfully installed pycryptodome-3.18.0
2025/10/13 10:46:13 setup.go:161: [INFO]Python dependency installed: pycryptodome 3.18.0
2025/10/13 10:46:13 server.go:53: [INFO]python dependencies installed
2025/10/13 10:46:13 server.go:55: [INFO]initializing python dependencies sandbox...
2025/10/13 14:43:20 server.go:60: [INFO]python dependencies sandbox initialized
相关推荐
cr7xin3 小时前
go语言结构体内存对齐
后端·golang
代码匠心3 小时前
从零开始学Flink:流批一体的执行模式
java·大数据·后端·flink·大数据处理
渣哥3 小时前
事务崩了别怪数据库!三大核心要素没掌握才是根本原因
javascript·后端·面试
it技术4 小时前
[百度网盘] Java互联网高级系统班【尚学堂】
后端
it技术4 小时前
尚学堂-Java互联网高级系统班
后端
渣哥4 小时前
你以为自动开启?Spring 事务支持其实还需要这几步!
javascript·后端·面试
Ray664 小时前
AOP
后端
初见0014 小时前
HashMap深度解析:不只是存取键值对那么简单
后端
拳打南山敬老院4 小时前
🚀 为什么 LangChain 不做可视化工作流?从“工作流”到“智能体”的边界与融合
前端·人工智能·后端