背景
业务在使用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
})
}
从源码中可以得到下面关键信息:
- 如果
docker/volumes/sandbox/dependencies/python-requirements.txt
文件内容为空,则不会触发下面安装。 - 最终采用
pip3 install -r requirements.txt
命令来安装。 - 源码预留了扩展机制,允许配置依赖包镜像地址,可以在
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
命令。
- 替换掉系统的
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
- 包装后
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
-
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
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