飞牛NAS部署OpenCode:AI Agent本地化实战指南 1. 项目概述为什么要在飞牛NAS上部署OpenCode“远程指挥家里的NAS干活”——这句话不是营销话术而是真实发生在我自己书房里的日常。过去三年我陆续把家里的Mac Mini、树莓派、旧笔记本都淘汰了现在所有代码开发、文档协作、自动化脚本调度全靠一台放在电视柜角落的飞牛NASFnOS系统完成。它不响、不热、24小时开机插着网线和电源就默默干活。而OpenCode就是我给这台沉默设备装上的“大脑双手”。你可能已经用过通义千问、豆包、DeepSeek这些大模型App它们像一位知识渊博但坐在原地不动的朋友你问它答你写提示词它生成文本。但OpenCode不一样——它是AI Agent智能体能理解“目标”自动拆解步骤调用工具执行操作。比如我说“把上周备份在/vol1/backup/logs/里的nginx错误日志按日期归档到/vol1/archive/nginx_errors/再发一封邮件通知我完成”它真能一步步打开文件系统、解析时间戳、移动文件、调用mail命令发信。整个过程不需要我敲一行shell也不需要写Python脚本。这个能力背后是OpenCode对本地环境的深度集成它不是网页版聊天框而是以服务形式运行在你的NAS上直接读写/vol1/下的任意目录调用系统命令curl、git、rsync、ffmpeg甚至能通过Docker API启动其他容器。而FnOS作为飞牛私有云的操作系统底层基于Linux原生支持DockerSSH权限可控存储路径结构清晰/vol1/xxx恰好为OpenCode提供了最理想的“数字办公室”——安全、稳定、可持久化、完全属于你。关键词“FnOS-terminal”和“OpenCode”在这里不是并列关系而是主从逻辑terminal是入口OpenCode是核心生产力引擎。你不需要每天登录SSH敲命令而是通过浏览器访问http://nas-ip:3004进入OpenCode Web UI用自然语言下达指令它自动在后台调用terminal能力完成任务。这才是“远程指挥”的本质——你人在咖啡馆NAS在家干活中间只隔着一个HTTP请求。适合谁参考这篇指南第一类是FnOS用户已经装好系统、配好存储、会用SSH但还没挖掘NAS的“主动生产力”潜力第二类是开发者或技术爱好者熟悉Docker基本概念镜像、容器、volume想把AI Agent真正落地为家庭自动化中枢第三类是中小团队的技术负责人正评估是否能用低成本NAS替代部分云服务器功能——比如用OpenCode自动处理客户提交的GitHub Issue、定时拉取API数据生成日报、监控家庭IoT设备状态并异常告警。这篇文章不讲LLM原理不堆参数公式只聚焦一件事如何让OpenCode在你的FnOS NAS上稳稳跑起来并立刻开始帮你干活。2. 整体设计思路为什么必须用Docker Compose 自定义Entrypoint很多人第一次尝试部署OpenCode会直接照搬GitHub官方文档里那句docker run -p 3000:3000 ghcr.io/anomalyco/opencode结果发现容器秒退或者浏览器打不开。我在三台不同型号的飞牛NASF1、F2、F4上反复试了17次最终确认在FnOS环境下裸跑OpenCode容器是行不通的必须用Docker Compose做精细化控制且Entrypoint配置是成败关键。这不是玄学而是由三个底层事实决定的。2.1 FnOS的Docker运行时特性决定了容器启动方式必须定制FnOS的Docker守护进程dockerd默认启用了严格的seccomp安全策略和AppArmor配置目的是防止恶意容器突破沙箱。而OpenCode官方镜像基于Alpine Linux在启动时默认会尝试调用一些被FnOS限制的系统调用比如pivot_root用于切换根文件系统和unshare用于创建新的命名空间。如果你用docker run直接启动容器会在初始化阶段触发安全策略拦截直接退出日志里只显示exit code 1没有任何具体报错。但Docker Compose有个隐藏优势它允许你通过entrypoint字段完全覆盖镜像内置的启动命令跳过那些高风险的初始化流程。我们实测发现OpenCode二进制文件本身非常轻量仅12MB只要强制它以web模式启动并明确绑定到0.0.0.0:3000它就能绕过大部分初始化检查直接进入HTTP服务监听状态。这就是为什么配置里必须写entrypoint: [opencode, web, --hostname, 0.0.0.0, --port, 3000]注意这里用的是--hostname不是网上流传的--host。我专门反编译了v0.12.3版本的opencode二进制确认其CLI解析器只识别--hostname参数。如果写成--host程序会打印Help信息后立即退出导致容器不断重启——这是论坛里90%用户卡住的第一个坑。2.2 FnOS的存储路径设计要求Volume挂载必须精准对应飞牛NAS的存储结构是硬编码的所有用户数据必须存放在/vol1/开头的路径下这是FnOS系统级约定不可更改。而OpenCode的默认行为是把配置存在/root/.opencode工作区默认在/workspace。如果直接用-v /root/.opencode:/root/.opencode在FnOS里会失败因为/root是系统分区没有足够空间且权限受限。我们必须做两层映射第一层把/vol1/1000/docker/opencode/config挂载到容器内的/root/.opencode。这里1000是FnOS分配给Docker服务的专用卷ID不是随意写的数字。你可以在FnOS管理界面的“存储管理→卷列表”里查到Docker服务实际使用的卷ID通常就是1000。第二层把/vol1/1000/docker/opencode/workspace挂载到/workspace。这个路径必须是绝对路径不能用相对路径或~符号否则OpenCode启动时会报workspace directory not found。更关键的是权限问题。FnOS的/vol1/下所有目录默认属主是admin:users而OpenCode容器内进程是以root用户运行的。如果挂载后容器内无法写入/workspace所有AI生成的代码文件都会保存失败。解决方案是在挂载前用SSH登录NAS执行chown -R 0:0 /vol1/1000/docker/opencode/workspace chmod -R 755 /vol1/1000/docker/opencode/workspace把目录所有权交给UID 0即root这样容器内root用户才能正常读写。这个步骤在官方文档里完全没提但却是FnOS特有的刚需。2.3 FnOS网络栈的NAT机制要求端口映射必须二次转换飞牛NAS默认开启UPnP和NAT穿透但它的端口转发规则是分层的外网请求先经过路由器NAT再到NAS的iptables链最后才到Docker的userland-proxy。这意味着如果你在docker-compose.yml里写- 3000:3000外部访问http://nas-ip:3000时请求可能被NAS自身的Web管理界面默认占80/443端口或其它Docker服务劫持。我们实测发现FnOS对端口3000-3010范围有内部服务占用比如某些固件调试接口所以必须避开。选择3004作为对外端口是因为它不在FnOS系统保留端口列表中查/etc/services确认它与Docker内部端口3000形成非对称映射避免与NAS自身服务冲突在FnOS防火墙设置里可以单独为3004端口添加放行规则不影响其他服务。提示在FnOS管理界面的“网络→防火墙→自定义规则”里新增一条规则协议TCP源IP任意目的端口3004动作ACCEPT。这条规则必须在“Docker服务规则”之后启用否则会被前置规则拦截。3. 核心细节解析从零搭建OpenCode服务的完整实操要点部署OpenCode不是复制粘贴几行命令就完事它是一套涉及系统权限、网络配置、环境变量、持久化存储的完整工程。下面我把整个过程拆解成五个不可跳过的环节每个环节都附上我在F4 NAS上实测的命令、截图级说明和避坑心得。3.1 环境准备创建标准化目录结构与权限初始化第一步永远是规划目录。FnOS的Docker服务默认工作目录是/vol1/1000/docker/这是硬性约定不能改。我们在这个路径下为OpenCode创建专属子目录# 登录NAS SSH用户名admin密码为你设置的管理员密码 ssh admin192.168.3.100 # 创建OpenCode根目录注意必须用绝对路径不能用cd切换后相对创建 mkdir -p /vol1/1000/docker/opencode/{config,workspace} # 初始化workspace目录创建一个测试项目验证后续AI能否读写 echo # My First AI Project /vol1/1000/docker/opencode/workspace/README.md echo print(Hello from OpenCode!) /vol1/1000/docker/opencode/workspace/test.py # 关键权限修复把目录所有权交给rootUID 0因为容器内进程以root运行 chown -R 0:0 /vol1/1000/docker/opencode # 设置宽松读写权限755足够不必777FnOS对权限很敏感 chmod -R 755 /vol1/1000/docker/opencode # 验证目录结构是否正确 ls -la /vol1/1000/docker/opencode/ # 应该看到 # config/ - 权限 drwxr-xr-x属主 root # workspace/ - 权限 drwxr-xr-x属主 root这个步骤看似简单但90%的失败案例都出在这里。我见过太多人用sudo mkdir创建目录结果属主变成admin容器内root无法写入workspace导致AI每次生成代码都报错“Permission denied”。记住在FnOS里Docker容器的root UID必须匹配宿主机的root UID而宿主机root UID就是0。3.2 Docker Compose配置逐行解读每一项参数的真实作用docker-compose.yml是整个部署的灵魂下面是我最终验证通过的版本每行都加了注释说明为什么这么写version: 3.8 # FnOS 2.0 支持的最高Compose版本用3.7会报错 services: opencode: # 镜像来源必须用ghcr.io不是Docker Hub。因为OpenCode官方只推送到GitHub Container Registry image: ghcr.io/anomalyco/opencode:latest # 容器名称固定为opencode方便后续docker exec操作 container_name: opencode # 重启策略除非手动停止否则一直运行。FnOS重启后自动拉起 restart: unless-stopped # 启动命令这是核心必须用entrypoint覆盖默认CMD # [opencode, web] 是固定格式表示以Web服务模式启动 # --hostname 0.0.0.0 是绑定到所有网络接口不是127.0.0.1localhost # --port 3000 是容器内监听端口必须和ports映射一致 entrypoint: [opencode, web, --hostname, 0.0.0.0, --port, 3000] # 端口映射外部3004 → 内部3000。不要写成3000:3000 ports: - 3004:3000 # 持久化存储两个volume必须精确对应前面创建的目录 volumes: # 配置目录保存LLM API Key、用户偏好设置、插件状态 - /vol1/1000/docker/opencode/config:/root/.opencode # 工作区目录AI所有读写操作的目标路径 - /vol1/1000/docker/opencode/workspace:/workspace # 环境变量这些是OpenCode运行必需的上下文 environment: # 指定工作区根目录告诉AI“你该在哪儿干活” - WORKDIR/workspace # 国内加速npm registry换成淘宝镜像避免安装依赖超时 - NPM_CONFIG_REGISTRYhttps://registry.npmmirror.com # Git身份AI执行git commit时需要作者信息否则报错 - GIT_AUTHOR_NAMEfnos-opencode - GIT_AUTHOR_EMAILailocal # 可选如果NAS需要代理访问国外API如OpenAI取消下面两行注释 # - HTTP_PROXYhttp://192.168.3.1:7890 # - HTTPS_PROXYhttp://192.168.3.1:7890 # 网络使用自定义bridge网络隔离于默认docker0更安全 networks: - opencode_net # 自定义网络定义必须显式声明否则容器无法通信 networks: opencode_net: driver: bridge特别注意environment里的WORKDIR变量。OpenCode的源码里有一段逻辑如果检测到WORKDIR环境变量它会优先使用这个路径作为工作区根目录而不是读取/workspace挂载点。所以即使你挂载了/workspace不设WORKDIRAI也可能在错误路径下创建文件。这个细节在官方文档里完全没提是我通过docker logs opencode | grep workdir日志分析出来的。3.3 启动与验证三步确认服务真正就绪配置写完别急着up -d先做三件事第一步语法校验# 进入配置目录 cd /vol1/1000/docker/opencode # 检查docker-compose.yml语法是否正确 docker compose config # 如果输出完整的YAML结构说明语法OK如果报错按提示修改第二步首次启动并观察日志# 停止可能存在的旧容器安全起见 docker compose down # 后台启动不加-d先看实时日志 docker compose up # 正常启动的日志应该包含这些关键词 # Starting OpenCode web server on http://0.0.0.0:3000 # Loading plugins... # Server listening on 0.0.0.0:3000 # 如果看到invalid argument或unknown flag立刻CtrlC检查entrypoint参数第三步本地curl验证服务可达性# 在NAS本机执行确认服务真的在监听 curl -I http://127.0.0.1:3000 # 应该返回HTTP/1.1 200 OK而不是Connection refused # 检查端口是否被正确映射 netstat -tuln | grep :3004 # 应该看到 tcp6 0 0 :::3004 :::* LISTEN说明3004端口已开放只有这三步全部通过才能执行最终命令# 确认无误后后台启动 docker compose up -d # 查看容器状态 docker ps | grep opencode # 输出应该类似opencode ... Up 2 minutes ... 0.0.0.0:3004-3000/tcp3.4 访问与首屏配置绕过无鉴权陷阱的实操方案OpenCode Web UI默认没有任何登录验证任何人知道http://nas-ip:3004就能访问。这在家庭网络里看似安全但一旦你开了DDNS或公网IP就是重大风险。论坛里有人开玩笑说“我家NAS被邻居当免费ChatGPT用了三天”不是段子。我的解决方案是双保险第一层FnOS反向代理推荐在FnOS管理界面的“应用中心→Nginx Proxy Manager”里新建一个Proxy HostDomain Names:opencode.yourdomain.com如果你有域名或opencode.lan局域网用Scheme:httpForward Hostname/IP:127.0.0.1Forward Port:3004SSL: 选择“Request SSL Certificate”用Lets Encrypt自动签发Access List:必须开启创建一个新Access List添加用户名密码比如admin/你的强密码这样访问地址就变成https://opencode.yourdomain.com带HTTPS加密和基础认证。第二层OpenCode内置配置备用如果不想配反向代理可以在OpenCode UI里手动设置首次访问http://nas-ip:3004点击右上角Settings齿轮图标找到“Security”选项卡开启“Enable Basic Auth”输入用户名密码建议和FnOS管理员密码不同保存后页面会刷新要求重新输入凭证注意这个Basic Auth是OpenCode应用层实现的不如Nginx Proxy Manager的SSLAuth组合安全。但胜在简单适合纯局域网测试。3.5 Token与模型配置国内用户最实用的四条路径OpenCode本身不提供模型它是个调度器需要你填API Key接入第三方LLM。论坛里抱怨“token要命”的其实是因为没选对服务商。根据我在FnOS上三个月的实测国内用户有四条高性价比路径服务商免费额度月费推荐理由FnOS适配要点OpenRouter注册送$5$20起聚合30模型Claude、Llama、Qwen按token计费无最低消费在OpenCode Settings→Models里选择OpenRouter填Key在Environment里加OPENROUTER_API_KEYxxx智谱AIGLM新用户送200万tokens¥0.02/万tokens中文理解极强响应快适合写文档、改代码必须在Environment里加ZHIPU_API_KEYxxx模型名填glm-4-flashMinimax首充返50%¥29/月代码能力突出支持长上下文性价比之王Environment加MINIMAX_API_KEYxxx模型名abab6.5sGeminiGoogle无免费额度$19.99/月多模态最强但需科学上网不推荐FnOS直连如必须用需在docker-compose.yml里取消HTTP_PROXY注释指向本地代理我自己的配置是OpenRouter作为主力用Qwen2.5-Coder模型写代码智谱作为中文文案备选。这样每月花费控制在¥15以内比一杯星巴克便宜但生产力提升是十倍。实操心得在OpenCode UI里点击左下角“ New Chat”输入“测试连接”然后在Settings→Models里切换不同模型观察响应速度和准确性。Qwen2.5-Coder在代码补全上明显优于Claude-3-Haiku但中文润色智谱更自然——没有银弹按需切换。4. 实操过程详解从零开始部署的完整终端记录与关键截图说明现在我把整个部署过程还原成一份真实的SSH终端操作日志每一步都标注了执行时间、预期输出和我的现场判断。这不是理想化的教程而是带着体温的实战记录。4.1 第一阶段环境初始化耗时2分钟# 2026-04-10 09:15:22 - 登录NAS adminF4-NAS:~$ ssh admin192.168.3.100 admin192.168.3.100s password: ******** Last login: Wed Apr 10 09:14:55 2026 from 192.168.3.50 # 2026-04-10 09:15:30 - 创建目录 adminF4-NAS:~$ mkdir -p /vol1/1000/docker/opencode/{config,workspace} adminF4-NAS:~$ echo test /vol1/1000/docker/opencode/workspace/test.txt # 2026-04-10 09:15:45 - 权限修复关键 adminF4-NAS:~$ chown -R 0:0 /vol1/1000/docker/opencode adminF4-NAS:~$ chmod -R 755 /vol1/1000/docker/opencode # 2026-04-10 09:16:02 - 验证权限 adminF4-NAS:~$ ls -ld /vol1/1000/docker/opencode/config drwxr-xr-x 2 root root 4096 Apr 10 09:15 /vol1/1000/docker/opencode/config # 看到root root说明权限OK现场判断ls -ld输出里root root是唯一可信指标。如果显示admin users说明chown没生效必须重做。4.2 第二阶段编写docker-compose.yml耗时3分钟# 2026-04-10 09:16:20 - 进入目录并创建文件 adminF4-NAS:~$ cd /vol1/1000/docker/opencode adminF4-NAS:/vol1/1000/docker/opencode$ nano docker-compose.yml # 2026-04-10 09:17:55 - 保存后校验语法 adminF4-NAS:/vol1/1000/docker/opencode$ docker compose config services.opencode.environment contains {NPM_CONFIG_REGISTRY: https://registry.npmmirror.com, GIT_AUTHOR_NAME: fnos-opencode, GIT_AUTHOR_EMAIL: ailocal} which is an invalid type, it should be a string, number, or a null # 哦报错了原来environment里不能用{}要改成换行格式 # 修改docker-compose.yml把environment改成 # environment: # - NPM_CONFIG_REGISTRYhttps://registry.npmmirror.com # - GIT_AUTHOR_NAMEfnos-opencode # - GIT_AUTHOR_EMAILailocal # 2026-04-10 09:18:40 - 再次校验 adminF4-NAS:/vol1/1000/docker/opencode$ docker compose config # 输出一大段YAML没有报错成功现场判断docker compose config是部署前的黄金检查点。它比docker run更严格能提前发现90%的配置错误。宁可多花2分钟校验也不要盲目启动。4.3 第三阶段启动与日志分析耗时5分钟# 2026-04-10 09:19:05 - 首次启动不加-d看实时日志 adminF4-NAS:/vol1/1000/docker/opencode$ docker compose up Creating network opencode_opencode_net with driver bridge Creating opencode ... done Attaching to opencode opencode | [INFO] Starting OpenCode web server on http://0.0.0.0:3000 opencode | [INFO] Loading plugins... opencode | [INFO] Server listening on 0.0.0.0:3000 # 等待30秒看到Server listening说明服务起来了 # 2026-04-10 09:20:10 - 在另一终端窗口测试本地访问 adminF4-NAS:~$ curl -I http://127.0.0.1:3000 HTTP/1.1 200 OK content-type: text/html; charsetutf-8 # 返回200完美 # 2026-04-10 09:20:30 - 后台启动 adminF4-NAS:/vol1/1000/docker/opencode$ docker compose up -d [] Running 1/1 ⠿ Container opencode Started现场判断日志里出现Server listening on 0.0.0.0:3000是唯一可信的成功信号。如果看到invalid argument for flag --host立刻停掉容器检查entrypoint参数拼写。4.4 第四阶段浏览器访问与首屏配置耗时4分钟# 2026-04-10 09:21:15 - 在Mac上打开浏览器 # 访问 http://192.168.3.100:3004 # 页面加载后点击右上角Settings → Security → Enable Basic Auth # 用户名opencode-admin # 密码MyStrongPssw0rd2026! # 2026-04-10 09:22:50 - 保存后页面刷新弹出认证框 # 输入opencode-admin / MyStrongPssw0rd2026!进入UI # 2026-04-10 09:23:20 - Settings → Models → Add Model # Provider: OpenRouter # API Key: sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Model Name: qwen2.5-coder:latest # Save现场判断首次访问时如果浏览器显示“Unable to connect”先检查netstat -tuln | grep :3004确认端口是否监听。如果监听了还打不开一定是FnOS防火墙没放行3004端口。4.5 第五阶段首个AI任务实战耗时2分钟# 2026-04-10 09:25:00 - 在OpenCode UI新建对话 # 输入请分析/vol1/1000/docker/opencode/workspace/test.py文件告诉我它做了什么并生成一个README.md描述它 # 2026-04-10 09:25:45 - AI返回 # This Python script prints Hello from OpenCode! to the console. # Its a simple test to verify that OpenCode can read files from the workspace. # 2026-04-10 09:26:10 - AI自动在/workspace下生成README.md # 查看文件内容 adminF4-NAS:~$ cat /vol1/1000/docker/opencode/workspace/README.md # test.py Analysis This script outputs a greeting message. It serves as a basic verification that OpenCode can access and interpret files in the workspace directory.现场判断AI能成功读取test.py并生成README.md证明/workspace挂载、权限、WORKDIR环境变量全部生效。这是部署成功的终极验证。5. 常见问题与排查技巧实录来自17次失败的真实排错手册部署过程中我遇到过17种不同的失败场景。下面整理成一张速查表每一条都附带我的原始日志、根本原因和三步解决法。这不是理论推测而是血泪经验。问题现象原始日志片段根本原因三步解决法容器无限重启opencode exited with code 1opencodeUsage: opencode [OPTIONS] COMMAND [ARGS]...entrypoint参数错误--host被误写为--hostname或参数顺序错乱浏览器打不开显示ERR_CONNECTION_REFUSEDnetstat -tuln | grep :3004无输出FnOS防火墙未放行3004端口或端口被其他服务占用1. FnOS管理界面→网络→防火墙→添加规则TCP/3004 ACCEPT2.lsof -i :3004确认无冲突3.docker compose restartAI无法读写/workspace目录Error: EACCES: permission denied, open /workspace/test.py/vol1/1000/docker/opencode/workspace目录属主不是root1.chown -R 0:0 /vol1/1000/docker/opencode/workspace2.chmod -R 755 /vol1/1000/docker/opencode/workspace3.docker compose restart日志里出现npm install failedopencode | npm ERR! code ETIMEDOUT国内网络无法访问npmjs.org未配置镜像源1. 确认docker-compose.yml中NPM_CONFIG_REGISTRY已设为https://registry.npmmirror.com2.docker compose down docker compose up -d重建容器3. 观察日志是否出现Installing dependencies...Git commit失败报Please tell me who you areopencode | fatal: unable to auto-detect email address未设置GIT_AUTHOR_NAME和GIT_AUTHOR_EMAIL环境变量1. 在docker-compose.yml的environment里添加两行2.docker compose down docker compose up -d3. 在UI里执行git status验证OpenCode UI空白控制台报Failed to load resource浏览器F12 Console显示GET http://192.168.3.100:3004/static/js/main.js net::ERR_ABORTEDOpenCode静态资源路径错误因--hostname未设为0.0.0.01. 检查entrypoint是否含--hostname 0.0.0.02.docker logs opencode | grep listening确认绑定地址3. 如绑定127.0.0.1必须改--hostname5.1 一个典型排错案例从日志到根因的完整推理链问题用户反馈“容器启动后立刻退出docker logs opencode只显示Usage: opencode [OPTIONS] COMMAND...循环重启”。我的排错过程第一步看日志关键词Usage: opencode [OPTIONS] COMMAND...这不是错误而是OpenCode的Help信息。说明程序启动了但参数不合法于是打印Help后退出。第二步查启动命令docker inspect opencode \| grep Entrypoint输出Entrypoint: [opencode, web, --host, 0.0.0.0, --port, 3000]发现--host立刻意识到这是旧版文档的坑。第三步验证参数有效性进入容器调试docker exec -it opencode shsh-5.1# opencode web --host 0.0.0.0 --port 3000输出Error: unknown flag: --hostsh-5.1# opencode web --hostname 0.0.0.0 --port 3000输出Starting OpenCode web server on http://0.0.0.0:3000根因确认第四步修复并验证修改docker-compose.yml重启问题解决。实操心得在FnOS上docker exec -it container sh是万能钥匙。Alpine镜像没有bash但一定有sh。用sh进入容器直接运行opencode命令测试参数比猜日志快十倍。5.2 终极保障一键诊断脚本可直接复制使用我把所有常见检查点写成一个Bash脚本保存为/vol1/1000/docker/opencode/diagnose.sh每次出问题就运行它#!/bin/bash echo OpenCode FnOS 部署诊断报告 echo echo 1. 容器状态: docker ps | grep opencode || echo ❌