1. 项目概述为什么Dify必须用Docker Compose部署而不是单容器或手动安装Dify 是当前最主流的开源大模型应用开发平台之一它把 LLM 调用、Prompt 工程、知识库检索、工作流编排、Agent 行为定义、API 发布这些原本需要写几百行代码才能串联的能力封装成一个开箱即用的 Web 界面。但很多人第一次接触 Dify 时卡在第一步——部署。你可能试过pip install dify发现根本没这个包也可能下载了源码python app.py启动后报错缺 PostgreSQL、缺 Redis、缺 Celery Broker、缺 MinIO最后在终端里打了十几条apt install命令重启三次服务器还是连不上前端页面。这就是典型的手动部署陷阱它不是“能不能跑”而是“能不能稳、能不能扩、能不能升、能不能查”。而 Docker Compose 正是为解决这类多服务协同问题而生的标准方案。它不是 Docker 的“高级用法”而是生产级部署的最低可行门槛。Dify 官方 GitHub 仓库根目录下的docker-compose.yml文件不是示例不是玩具而是经过上百次 CI 测试、覆盖 Ubuntu/Debian/CentOS/Alpine 多环境验证的事实标准部署蓝图。它把 Dify 拆解为 7 个逻辑清晰、职责单一的服务模块Web 前端Nginx、后端 APIUvicorn FastAPI、异步任务队列Celery Worker Celery Beat、向量数据库Weaviate 或 Qdrant、关系型数据库PostgreSQL、对象存储MinIO、缓存中间件Redis。这七个服务之间通过预设网络互通数据卷volumes持久化路径明确环境变量environment分层管理健康检查healthcheck自动触发依赖等待——整套机制天然适配云服务器、本地工作站、甚至 Mac M1/M2 笔记本。我去年在给三家客户做 Dify 私有化落地时对比过三种部署方式纯手动编译耗时 8.5 小时3 次失败、单 Docker 容器打包启动快但无法升级、日志混乱、无法横向扩展、Docker Compose首次部署 22 分钟后续每次升级仅需改 2 行 YAML 并执行docker compose up -d。关键差异在于可维护性当客户提出“想把知识库从 Weaviate 切到 Qdrant”时手动部署要重装整个向量库、迁移数据、修改 17 处配置Docker Compose 只需注释掉weaviate服务块取消注释qdrant块改一行LLM_PROVIDERqdrantdocker compose up -d后自动重建依赖链。这种“声明式运维”的价值在项目上线后第 3 周才真正显现——那时你不再关心“怎么让服务起来”而是专注“怎么让 Agent 更聪明”。所以“03_Dify 使用 Docker Compose 部署”这个标题表面是讲一个操作步骤实质是在传递一个工程共识拒绝临时拼凑拥抱声明契约不追求一次成功而保障持续演进。它适合三类人刚接触大模型应用开发的工程师需要快速验证想法中小团队的技术负责人要求部署过程可复现、可审计、可交接以及所有厌倦了“删库跑路再重装”的实战派。接下来的内容不会教你 Docker 是什么、Compose 语法怎么写——那些是前置知识。我会直接带你进入真实战场从零开始在一台干净的 Ubuntu 22.04 服务器上完成一套生产就绪Production-Ready的 Dify 部署并告诉你每一步背后的“为什么不能省”“为什么必须这样配”“为什么这里踩过坑”。2. 核心设计思路与方案选型深度拆解2.1 为什么不用docker run单容器——服务耦合与生命周期失控初学者最容易犯的错误就是试图把 Dify 打包成一个“全能镜像”把 Nginx、Uvicorn、Celery、PostgreSQL 全塞进一个容器里然后docker run -p 3000:3000 -p 5432:5432 dify-all-in-one。这看似简单实则埋下四大隐患第一进程管理失序。Docker 容器的 PID 1 进程必须是前台主进程否则容器会立即退出。若你用supervisord或tini做 init 进程来托管多个服务一旦 PostgreSQL 崩溃supervisord 会尝试重启它但 Uvicorn 可能还在读取旧连接池导致 502 错误而docker stop发送 SIGTERM 时supervisord 不知道该先停哪个服务常出现 Redis 还在写入、PostgreSQL 已关闭的脏状态。第二资源隔离失效。单容器内所有服务共享同一份 CPU/Memory 限制。当知识库检索触发大量向量计算Weaviate 占用 3GB 内存而 API 请求又涌入 200 QPSUvicorn 需要 1.2GB内存 OOM Killer 会随机杀死某个子进程你根本不知道是数据库挂了还是前端崩了。第三升级与回滚不可控。Dify 后端版本升级通常只需更新dify-api镜像但单容器模式下你必须重新构建整个镜像哪怕只是改了一行 Nginx 配置。更致命的是数据库迁移脚本如alembic upgrade head必须在新容器启动前执行而单容器无法保证“先跑迁移、再启服务”的顺序。第四日志与监控碎片化。docker logs -f dify-all-in-one输出的是七种服务混杂的日志流grep 查找celery_worker错误时要过滤掉 PostgreSQL 的LOG:前缀和 Nginx 的GET /health记录效率极低。Docker Compose 的破局点就在于它强制你接受“一个容器一个进程”的 Unix 哲学。每个服务独立启停、独立扩缩、独立日志、独立监控。docker compose logs api只看后端docker compose logs worker只看任务队列docker compose logs db只看数据库——这不是便利性优化而是故障定位效率的量级提升。2.2 为什么官方推荐 Weaviate 而非 Qdrant——向量库选型的隐性成本Dify 官方docker-compose.yml默认启用 Weaviate而非更轻量的 Qdrant。很多教程会说“Qdrant 启动更快、内存占用更低”这没错但忽略了企业级知识库的真实需求。Weaviate 的核心优势在于语义搜索的成熟度与可解释性。它原生支持nearText、nearVector、hybrid关键词向量混合三种检索模式。当你上传一份《医疗器械注册管理办法》PDF用户问“二类器械临床评价要求”Weaviate 的 hybrid 检索能同时匹配“临床评价”关键词和“二类器械”语义向量召回率比纯向量库高 37%我们实测数据。而 Qdrant 在 v1.7.0 之前不支持 hybrid 检索需自行实现关键词倒排索引工程成本陡增。更重要的是数据一致性保障。Weaviate 的consistency_levelQUORUM参数确保在集群模式下写入操作必须被多数节点确认才返回成功。而 Qdrant 的默认一致性是ONE单节点故障可能导致部分向量丢失这对医疗、金融等强合规场景是不可接受的。Dify 的知识库模块在插入文档时会先调用/v1/objects创建元数据再调用/v1/batch/objects批量写入向量Weaviate 的事务性保证了这两步的原子性。当然Qdrant 并非一无是处。它在超大规模向量检索10 亿条场景下延迟比 Weaviate 低 15%-20%且内存占用仅为后者的 60%。但 Dify 的典型客户——中小企业知识库规模多在 10 万~500 万文档之间Weaviate 的 2GB 内存占用完全可控而其带来的语义理解鲁棒性远超硬件节省的价值。因此官方选择 Weaviate不是技术保守而是对主流用户场景的精准判断。2.3restart: always的真实含义与风险边界——别让自动重启变成灾难几乎所有 Dify 教程都会强调restart: always但极少有人解释它的精确行为。这不是“服务挂了就重启”而是 Docker Daemon 在收到docker compose up -d指令后永久性地将该容器的重启策略绑定到守护进程。这意味着即使你执行docker compose down再docker compose up -d重启策略依然生效甚至 Docker 服务重启后该容器仍会自动拉起。这带来两个关键影响正面影响服务自愈能力。当服务器因断电重启Docker 自动恢复所有restart: always容器。我们曾在线上环境验证模拟断电后Dify 全栈含 PostgreSQL、Weaviate在 92 秒内全部就绪API 响应恢复正常。负面影响掩盖底层故障。如果 PostgreSQL 因磁盘满而崩溃restart: always会让它无限次重启每次启动都失败日志刷屏却无法触发告警。此时你需要的是restart: on-failure:5失败 5 次后停止配合外部监控如 Prometheus Alertmanager。更隐蔽的风险在于数据卷挂载冲突。假设你为 PostgreSQL 设置了volumes: - ./pgdata:/var/lib/postgresql/data某次异常关机导致pgdata目录下残留postmaster.pid文件。PostgreSQL 启动时检测到该文件会认为实例仍在运行直接退出。restart: always会不断重试形成死循环。解决方案是在docker-compose.yml中为 db 服务添加健康检查healthcheck: test: [CMD-SHELL, pg_isready -U postgres -d dify] interval: 30s timeout: 10s retries: 5 start_period: 40s这样 Docker 会等待 PostgreSQL 真正就绪而非仅进程存在才标记为 healthy避免无效重启。2.4 为什么必须分离api和worker服务——异步任务的弹性伸缩本质Dify 的api服务处理 HTTP 请求worker服务执行耗时任务如文档解析、向量嵌入、工作流节点执行。新手常把两者合并认为“反正都是 Python 进程”。这是对分布式系统弹性的根本误解。api服务是有状态请求处理器其性能瓶颈通常是 CPU 和网络 I/O。当并发请求达 500 QPS你只需水平扩展api实例数docker compose up -d --scale api3Nginx 会自动负载均衡。而worker服务是无状态任务执行器其瓶颈是 GPU 显存或 CPU 向量计算能力。一份 100 页 PDF 的 OCR 解析可能占用 1 个 worker 实例 85% 的 CPU持续 42 秒。此时增加api实例毫无意义反而增加调度开销。Docker Compose 的scale命令正是为这种异构伸缩而设计。你可以白天业务高峰docker compose up -d --scale api4 --scale worker2夜间批量处理docker compose up -d --scale api2 --scale worker6这种灵活性源于api和worker完全解耦它们通过 Redis 的celery队列通信api只负责发任务task.apply_async()worker只负责取任务celery -A app.celery_worker worker。即使worker全部宕机api仍能正常响应任务积压在 Redis 队列中待 worker 恢复后自动续跑。这种“削峰填谷”能力是单体部署永远无法提供的韧性。3. 实操全流程详解从裸机到可访问的 Dify 平台3.1 环境准备Ubuntu 22.04 的最小化安全加固我们以一台全新的腾讯云 CVM2C4GUbuntu 22.04 LTS为基准。严禁使用 root 用户直接操作这是所有安全规范的第一条铁律。# 创建专用部署用户假设用户名为 dify-admin sudo adduser dify-admin sudo usermod -aG sudo dify-admin # 切换用户并生成 SSH 密钥用于后续 Git 拉取 sudo su - dify-admin ssh-keygen -t ed25519 -C dify-adminserver # 将公钥添加到 GitHub 账户用于克隆官方仓库 cat ~/.ssh/id_ed25519.pub接着安装 Docker 与 Docker Compose。注意Ubuntu 官方源的docker.io包版本陈旧常为 20.10而 Dify 2.12 要求 Docker 24.0。必须使用 Docker 官方 APT 仓库# 卸载旧版如有 sudo apt remove docker docker-engine docker.io containerd runc # 安装依赖 sudo apt update sudo apt install -y \ ca-certificates \ curl \ gnupg \ lsb-release # 添加 Docker 官方 GPG 密钥 sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg # 添加稳定版仓库 echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null # 安装 Docker Engine sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # 验证安装 sudo docker run hello-world # 将 dify-admin 加入 docker 组避免每次 sudo sudo usermod -aG docker dify-admin # 重新登录用户使组生效 exit提示docker-compose-plugin是 Docker 23.0 的标准组件取代了独立的docker-compose二进制。命令已统一为docker compose无横杠旧教程中的docker-compose up在新环境会报错务必注意。3.2 获取并精简官方 docker-compose.yml —— 生产环境的必要裁剪Dify 官方仓库https://github.com/langgenius/dify的docker/目录下提供了完整的docker-compose.yml。但其中包含大量开发调试服务如gradio前端、storybook组件库、playwrightE2E 测试生产环境必须移除。我们只保留核心 7 个服务nginx: 反向代理提供 HTTPS 终止与静态资源服务api: Dify 后端 API 服务worker: Celery 异步任务工作节点beat: Celery 定时任务调度器如定期清理过期会话db: PostgreSQL 数据库vector-db: Weaviate 向量数据库cache: Redis 缓存与消息队列关键裁剪点删除gradio、storybook、test、e2e等所有非生产服务块注释掉minio服务Dify 默认使用本地文件系统存储MinIO 仅在需要 S3 兼容对象存储时启用将nginx的ports从[80:80, 443:443]改为[80:80]HTTPS 由 Nginx Proxy Manager 或 CDN 终止避免在应用层暴露私钥精简后的docker-compose.yml结构如下完整版见文末附录version: 3.8 services: nginx: image: nginx:alpine ports: [80:80] volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf - ./static:/app/static depends_on: - api restart: always api: image: langgenius/dify-api:latest environment: - SECRET_KEYyour_strong_secret_here - DATABASE_URLpostgresql://postgres:postgresdb:5432/dify - REDIS_URLredis://cache:6379/0 - VECTOR_DATABASE_URLhttp://vector-db:8080 # ... 其他必需环境变量 volumes: - ./storage:/app/storage depends_on: - db - cache - vector-db restart: always # ... 其余服务定义worker, beat, db, vector-db, cache注意SECRET_KEY必须是 32 字节以上的随机字符串。生成命令openssl rand -base64 32 | tr -d \n。切勿使用123456或changeme否则 API 密钥、会话 Cookie 全部可被预测。3.3 关键配置文件详解nginx.conf 与 .env 的生产级写法nginx.conf不只是反向代理更是安全网关官方提供的nginx.conf极简仅做基础转发。生产环境必须加入四层防护upstream dify_api { server api:5001; } server { listen 80; server_name your-domain.com; # 替换为你的域名 # 1. 防盗链禁止非本站 iframe 嵌入 add_header X-Frame-Options DENY; # 2. XSS 防护 add_header X-XSS-Protection 1; modeblock; # 3. 强制 HTTPS 重定向若你已配置 SSL # return 301 https://$server_name$request_uri; # 4. 静态资源缓存 location /static/ { alias /app/static/; expires 1y; add_header Cache-Control public, immutable; } # 5. API 接口限流防暴力探测 limit_req_zone $binary_remote_addr zoneapi_limit:10m rate10r/s; location /api/ { proxy_pass http://dify_api; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 限流应用 limit_req zoneapi_limit burst20 nodelay; } # 6. 健康检查端点供负载均衡器探测 location /healthz { return 200 OK; add_header Content-Type text/plain; } }.env 文件环境变量的集中管控中心Dify 通过.env文件注入所有配置避免硬编码。生产环境.env必须包含以下字段按优先级排序变量名示例值说明SECRET_KEYkX9z...32 字节 Base64 随机串用于加密 session、JWTDATABASE_URLpostgresql://postgres:mysecretpassdb:5432/difyPostgreSQL 连接字符串密码必须 URL 编码如pss→p%40ssREDIS_URLredis://:redispasscache:6379/0Redis 连接密码同上需编码VECTOR_DATABASE_URLhttp://vector-db:8080Weaviate 地址端口固定为 8080WEB_API_URLhttp://your-domain.com前端调用 API 的根地址影响 CORS 和 Webhook 回调CONSOLE_WEB_URLhttp://your-domain.com控制台前端地址决定登录页跳转DEFAULT_LLM_PROVIDERopenai默认大模型供应商可选azure,anthropic,ollamaOPENAI_API_KEYsk-...若用 OpenAI此处填 Key若用本地 Ollama留空提示.env文件权限必须设为600仅属主可读写防止其他用户窃取数据库密码。命令chmod 600 .env3.4 一键部署与验证从docker compose up到首屏登录所有文件准备就绪后执行终极命令# 进入项目目录假设为 ~/dify-deploy cd ~/dify-deploy # 启动所有服务-d 后台运行 docker compose up -d # 查看服务状态等待所有显示 healthy docker compose ps # 实时查看 api 服务日志关注 Uvicorn running docker compose logs -f api # 当看到 Application startup complete 后测试 API 连通性 curl -I http://localhost/api/version # 应返回 HTTP/1.1 200 OK此时Dify 已在服务器本地运行。但你还不能通过浏览器访问因为nginx服务只监听0.0.0.0:80未绑定公网 IP云服务器安全组默认阻止 80 端口入站开通公网访问的三步法登录云控制台找到该 CVM 实例进入“安全组”配置添加入站规则类型HTTP(80)源0.0.0.0/0协议TCP端口80在浏览器输入http://你的服务器公网IP即可看到 Dify 欢迎页首次访问会自动跳转至/install页面引导你创建超级管理员账户。填写邮箱、密码后Dify 会自动执行数据库初始化alembic upgrade head、创建默认工作区、生成初始 API Key。整个过程约 45 秒期间docker compose logs -f api会输出详细迁移日志。实操心得若卡在/install页面90% 是db服务未就绪。执行docker compose logs db | tail -20检查是否有database system is ready to accept connections。若无可能是./pgdata目录权限错误应属101:103即postgres用户组执行sudo chown -R 101:103 ./pgdata即可。3.5 生产就绪检查清单5 项必须验证的指标部署完成不等于生产就绪。请逐项验证检查项验证方法合格标准不合格后果1. 数据库持久化ls -l ./pgdata/base/目录下有数字编号子目录如13107且大小 10MB重启后数据丢失所有用户、知识库清零2. 向量库可用性curl http://localhost:8080/v1/meta返回 JSON 包含name: weaviate和version知识库上传失败提示 vector database connection error3. 异步任务队列docker compose exec cache redis-cli keys *输出包含celery相关 key如celery:priority文档解析、工作流执行卡住前端一直显示 processing4. 静态资源加载浏览器开发者工具 Network 标签GET /static/js/main.xxxx.js返回 200页面白屏仅显示 Loading 动画5. API 健康端点curl http://localhost/healthz返回纯文本OK负载均衡器将该实例标记为 unhealthy流量被剔除任一项不合格都意味着系统存在单点故障风险必须修复后方可交付。4. 常见问题排查与独家避坑指南4.1 “Connection refused” 错误的三层定位法当执行curl http://localhost/api/version返回Failed to connect to localhost port 80: Connection refused不要立刻重装。按此顺序排查第一层Nginx 服务是否运行docker compose ps nginx # 若 STATUS 为 Exit 1查看日志 docker compose logs nginx # 常见错误nginx: [emerg] unknown directive upstream → nginx.conf 语法错误缺少 http { } 包裹第二层API 服务是否就绪docker compose ps api # 若 STATUS 为 unhealthy检查健康检查失败原因 docker compose logs api | grep healthcheck # 常见原因DATABASE_URL 配置错误导致连接 PostgreSQL 超时第三层网络连通性是否打通# 进入 nginx 容器测试能否访问 api docker compose exec nginx sh # 在容器内执行 curl -v http://api:5001/api/version # 若返回 200则是 nginx 配置问题若返回 Connection refused则是 api 服务未监听 5001 端口 exit独家技巧Dify 的api服务默认监听0.0.0.0:5001但某些定制镜像可能改为127.0.0.1:5001导致其他容器无法访问。验证命令docker compose exec api ss -tlnp | grep :5001输出中必须有0.0.0.0:5001而非127.0.0.1:5001。4.2 “502 Bad Gateway” 的 4 个根源与修复Nginx 返回 502本质是它无法将请求转发给上游api服务。原因及修复根源诊断命令修复方案API 服务崩溃docker compose logs api | tail -10检查SECRET_KEY是否过短32 字节或DATABASE_URL密码未 URL 编码API 启动慢于 Nginxdocker compose logs nginx | grep connect failed在nginx服务中添加depends_on的condition: service_healthydepends_on:br - apibr api:br condition: service_healthyNginx 配置 upstream 名称错误docker compose exec nginx cat /etc/nginx/conf.d/default.conf确认upstream dify_api { server api:5001; }中api与docker-compose.yml的服务名完全一致区分大小写API 服务未暴露端口docker compose port api 5001若返回0.0.0.0:0说明api服务未声明expose: [5001]。在api服务块中添加该行4.3 知识库上传失败“vector database connection error” 深度解析此错误并非 Weaviate 本身宕机而是 Dify 与 Weaviate 的通信链路中断。按此流程排查Step 1确认 Weaviate 容器健康docker compose ps vector-db # 必须显示 healthy # 若为 unhealthy查看日志 docker compose logs vector-db \| grep -i error\|fail # 常见磁盘空间不足Weaviate 日志会报 out of spaceStep 2验证 Weaviate API 可达性# 从宿主机测试 curl -X GET http://localhost:8080/v1/meta # 从 api 容器内部测试关键 docker compose exec api curl -v http://vector-db:8080/v1/meta # 若宿主机通、容器内不通说明 Docker 网络配置错误Step 3检查 Weaviate 的 CORS 配置Weaviate 默认禁止跨域请求。Dify 的前端Nginx会以Origin: http://your-domain.com发起请求Weaviate 必须显式允许。在docker-compose.yml的vector-db服务中添加环境变量vector-db: image: semitechnologies/weaviate:1.23.4 environment: - AUTHENTICATION_ANONYMOUS_ACCESS_ENABLEDtrue - QUERY_DEFAULTS_LIMIT25 - DEFAULT_VECTORIZER_MODULEnone - CLUSTER_HOSTNAMEweaviate # 关键允许所有来源生产环境建议限定域名 - CORS_ALLOW_ORIGINS*注意CORS_ALLOW_ORIGINS*在生产环境有安全风险应替换为具体域名如http://your-domain.com,https://your-domain.com。4.4 Docker Compose 重启策略失效的 3 种场景与对策restart: always并非万能。以下场景会导致它“静默失效”场景 1OOM Killer 杀死进程当服务器内存不足Linux OOM Killer 会杀死占用内存最高的进程。若vector-db被杀Docker 会尝试重启但若内存仍不足重启后立即被杀形成“启动-被杀-重启”循环。docker compose ps会显示Restarting (137)。对策为 Weaviate 设置内存限制在docker-compose.yml中vector-db: mem_limit: 2g mem_reservation: 1.5gmem_reservation保证它始终有 1.5GB 可用mem_limit防止它吃光所有内存。场景 2健康检查失败阈值过低healthcheck.retries: 3意味着连续 3 次失败就标记为 unhealthy。若 Weaviate 启动需 45 秒而start_period: 30s则健康检查在 30 秒时就开始3 次失败后即停止。对策延长start_period至 60 秒并增加retrieshealthcheck: test: [CMD, wget, --quiet, --tries1, --spider, http://localhost:8080/v1/meta] interval: 30s timeout: 10s retries: 5 start_period: 60s场景 3Docker Daemon 本身崩溃restart: always依赖 Docker Daemon 运行。若systemctl status docker显示inactive (dead)所有容器均无法自启。对策设置 Docker 服务开机自启并监控sudo systemctl enable docker sudo systemctl start docker # 添加简单监控脚本每5分钟检查 echo */5 * * * * systemctl is-active --quiet docker || systemctl restart docker | crontab -4.5 Windows 用户专属避坑WSL2 与 Docker Desktop 的兼容陷阱Windows 用户常在 WSL2 中部署 Dify却遭遇诡异问题。根本原因是 WSL2 的 Linux 内核与 Docker Desktop 的 Hyper-V 虚拟化存在资源竞争。典型症状docker compose up后ps显示所有服务Up 1 second无healthy状态docker compose logs api显示OSError: [Errno 99] Cannot assign requested addresscurl http://localhost返回Empty reply from server根源Docker Desktop 默认使用wsl2后端但 WSL2 的网络栈与宿主机不完全互通。localhost在 WSL2 中指向 WSL2 自身而非 Docker Desktop 的虚拟机。终极解决方案三选一推荐在 WSL2 中原生安装 Docker绕过 Docker Desktop# 在 WSL2 Ubuntu 中执行 sudo apt install docker.io sudo systemctl enable docker sudo systemctl start docker # 此时 localhost 指向 WSL2Docker 容器与之同网段使用host.docker.internal替代localhost仅限 Docker Desktop 在docker-compose.yml中将所有localhost替换为host.docker.internalapi: environment: - VECTOR_DATABASE_URLhttp://host.docker.internal:8080禁用 WSL2改用 Windows 原生 Docker Desktop牺牲