每次饭前,我都要经历一场漫长的内心挣扎。手机里外卖App来回切换,冰箱里的食材看了一遍又一遍,最后往往还是点开了熟悉的那家店。这种“选择困难”,其实不是因为选择太少,而是因为我们没有一个称手的工具,把自己的私房菜谱管理起来。后来我在GitHub上看到YunYouJun cook这个开源项目,又配合cpolar把服务映射到公网,才算真正解决了这个问题——不管是在家里、在办公室,还是出差在外,打开手机浏览器就能查自家的菜谱,随查随用。
这篇文章就是我从零开始,把YunYouJun cook部署到自己的服务器上,再用cpolar做内网穿透,让私房菜谱“走到哪用到哪”的完整记录。适合喜欢做饭、想建立家庭菜谱库、又愿意折腾一点小技术的朋友参考。整个过程不需要买独立服务器,一台闲置电脑甚至树莓派就够了。
1. 项目拆解与定位:这个“菜谱系统”到底解决了什么
1.1 从“今天吃什么”到“我家的菜谱库”
先聊一下痛点。我们日常做饭,信息来源其实很乱:朋友圈里收藏的文章、公众号推送的食谱、妈妈电话里口述的家传做法、B站视频里暂停截图的步骤……这些内容散落在各个App里,真要找的时候,要么过期失效,要么翻半天找不到,要么根本没有记录可以回看。
YunYouJun cook这个项目,简单说就是一个可以自己部署、自己管理的私人菜谱系统。它把菜谱当作结构化的数据来管理,一道菜包含食材、步骤、标签、分类、封面图、备注等信息,全部存在自己的电脑或服务器上。配合一套漂亮的前端界面,你可以像逛一个私人美食网站一样,浏览自己收藏的所有菜谱。
对我来说,它最大的价值不是“记录”本身,而是把“记录”变成了“决策支持”。当你把家常菜、拿手菜、宵夜、宴客菜全部整理进一个库里,再按季节、场合、食材打上标签,打开页面就不再是面对一片空白,而是一整个属于你自己的备选菜单,“今天吃什么”这个问题瞬间被简化成了几次点击。
1.2 cook项目技术概览:Markdown驱动的菜谱管理
从技术角度看,YunYouJun cook的定位非常清晰:一个以Markdown文件为数据源、静态生成加动态渲染结合的菜谱管理系统。项目核心托管在GitHub上,代码结构很规整,前端基于Vue/Nuxt生态,界面走的是清爽简约风,不花哨但很耐看。
最让我喜欢的设计,是它支持用Markdown文件来维护菜谱内容。食材、步骤、要点,都是用纯文本写成的。这意味着你可以用最简单的文本编辑器就能改菜谱,改完刷新页面就能看到效果。数据本质上就是一堆.md文件,不依赖重型数据库,备份、同步、版本管理都非常方便,哪怕哪天整个系统挂了,这些Markdown文件依然还在,内容不会丢。
从部署形态上说,cook同时支持直接以Node服务方式运行,也提供Docker镜像。这意味着不管你手里是闲置笔记本、NUC小主机、NAS,还是一台云服务器,基本都能跑起来。我自己的环境是一台闲置的迷你主机,装的是Ubuntu Server,属于比较典型的自托管场景。
1.3 到底哪些人适合这套方案
先说结论:这不是一个给所有人用的产品,但它非常适合三类人。
第一类,是像我一样对“吃什么”有严重选择困难、且家庭烹饪频率较高的人。菜谱库一旦建立起来,每次打开界面都是熟悉的味道,不用再刷半小时外卖App。
第二类,是家里有“传承菜谱”需求的人。老人家口述的做法、逢年过节才做的硬菜、包含家族记忆的独门配方,用Markdown记录成文档,配合照片保存下来,比手写小本本更不容易丢。
第三类,是喜欢自托管、对数据隐私比较敏感的技术爱好者。所有数据都在自己的设备上,不经过第三方平台,不会有“作者删了文章”“平台下架了收藏”这种问题。
当然,如果你完全不懂命令行、也不想碰任何配置文件,那这套方案可能不太适合,直接用现成的下厨房、豆果美食之类的App会轻松得多。但如果愿意花一个下午折腾一下,回报还是很值得的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 方案选型思考:为什么不是现成App,为什么需要cpolar
2.1 自部署菜谱系统与商业App的核心差异
很多人会问:市面上免费菜谱App那么多,为什么要费劲自部署一个?
我的答案分三层。第一层是内容归属权。商业App里的收藏、笔记,本质上都存在别人的服务器上,平台规则一变、社区一冷清,你的收藏可能就没了。自部署的意思是,所有内容都以文件形式躺在你自己的硬盘上,谁也拿不走。
第二层是定制自由。YunYouJun cook是开源的,你可以改界面、加字段、换主题,甚至自己写脚本批量导入数据。我在使用中给它加了一个“食材别名”的字段,比如“青蒜”和“蒜苗”其实指同一种东西,在搜索时就能互相匹配。这种事在封闭App里很难实现。
第三层是离线可用和长期性。部署在内网的服务,哪怕断网也能在家庭局域网内访问。商用的菜谱平台哪天停止运营,你的菜谱库不会跟着消失。
但自部署有一个天然短板:离开了家庭局域网,外面就访问不了。这个时候就需要内网穿透工具来搭一座桥,把家里的服务暴露到公网。这也是我把cpolar拉进来的原因。
2.2 cpolar在内网穿透方案里的位置
内网穿透工具其实不少,经典的ngrok、frp、nps、Tailscale、ZeroTier都能实现类似的目标。为什么我最终选了cpolar?
一个很现实的原因是:cpolar面向国内用户做了大量优化。它的官网、文档、控制台全是中文,隧道管理有图形化界面,比ngrok的英文终端体验友好太多。而且cpolar提供免费套餐,对个人菜谱这种低流量场景完全够用,不需要为“日常自己看几眼菜谱”这种需求额外花钱。
另一个原因是它的隧道配置非常直观。cpolar的基本模型是:你在本地跑一个客户端,客户端和cpolar云端建立一条安全通道;云端分配给你一个公网域名,所有访问这个域名的流量,都会被转发到你指定的本地端口。也就是说,我只要把cook跑在本地比如3000端口,再用cpolar把公网域名映射到这个端口,访问域名就等于访问我家的菜谱系统。
| 对比项 | ngrok | frp | cpolar | Tailscale/ZeroTier |
|---|---|---|---|---|
| 中文文档 | 一般 | 一般 | 好 | 一般 |
| 配置难度 | 中 | 较高 | 低 | 低 |
| 免费额度 | 有限 | 自建无限制 | 免费套餐可用 | 免费 |
| 国内访问速度 | 不稳 | 取决于自建服务器 | 较快 | 取决于中转/直连 |
| 适用场景 | 临时演示 | 重度自托管 | 轻量家庭服务 | 虚拟组网 |
2.3 选型背后的三个关键逻辑
第一,安全边界要清晰。内网穿透的本质是把家里的端口暴露到公网,所以必须设置访问控制。我使用的是cpolar的访问令牌功能,再配合cook本身的登录鉴权,双重保障。如果没有这些限制,等于把自己的菜谱库完全敞开在互联网上,虽然菜谱不是什么机密,但没必要冒这个风险。
第二,稳定大于速度。菜谱系统是低频访问场景,峰值流量很小,但对稳定性有要求。cpolar的隧道基于长连接,家庭宽带的IP变动不影响域名访问,这一点比很多人自己用动态DNS方案要省心。
第三,贪图简单。端到端的整个过程,我只安装了cpolar客户端、拉起了cook的Docker容器、配置了两三个参数,没有写任何复杂的Nginx反代规则,也没有买域名、没有申请证书。cpolar自动处理了HTTPS证书,对个人用户来说非常省事。
3. 部署实操:把cook跑起来,构建家庭菜谱库
3.1 前置准备与两种部署路径对比
在正式动手之前,先列一下我用到的东西:
- 一台Ubuntu Server 22.04的迷你主机(配置不重要,双核2G内存就能跑)
- Node.js 16以上版本(如果走Node直接跑的方式)
- Docker和Docker Compose(如果走容器化方式)
- 一个cpolar账号(后面映射公网用)
cook的部署路径有两种:一种是直接克隆源码后yarn install + yarn dev,另一种是拉取现成的Docker镜像。两个办法我都试过,说下实际感受。
直接用Node跑的好处是调试方便,改代码即时生效,适合想二次开发的人。坏处是环境依赖多,Node版本不对、依赖装不上,各种小问题会花掉不少时间。
用Docker跑的好处是一行命令搞定,隔离性好,不会污染系统环境,升级也方便。坏处是改内部文件稍微绕一点,但你只是日常用菜谱库的话,根本不需要改内部文件。
我自己最终选了Docker方案,原因很简单:少踩坑。以下都是基于Docker路径的完整流程。
3.2 Docker部署cook的完整步骤
第一步,先把项目源码和镜像拉下来。打开终端执行:
bash复制git clone https://github.com/YunYouJun/cook.git
cd cook
其实源码主要是为了看配置文件,如果你不需要改配置,直接用docker命令就行。项目根目录下有Dockerfile和docker-compose.yml示例,里面的端口和挂载路径写得很清楚。
第二步,启动容器。我用的docker-compose方式,示例配置大致如下:
yaml复制services:
cook:
image: yunyoujun/cook:latest
container_name: cook
restart: always
ports:
- "9239:9239"
volumes:
- ./data:/cook/data
有一个细节要注意:cook默认的监听端口是9239。第一次我顺手映射到3000端口,结果界面打不开,后来看GitHub的README才发现默认端口是9239。这个细节在文档里确实写了,但很容易被忽略。
启动命令:
bash复制docker compose up -d
等十几秒让容器完全起来,浏览器访问 http://localhost:9239,如果能看到界面,说明cook已经跑起来了。
第三步,准备菜谱数据。cook支持从GitHub仓库拉取菜谱,也支持本地Markdown文件。我的做法是在挂载的./data目录下手动建一个菜谱目录,把自己整理的菜谱按“分类/菜名.md”的结构放进去,然后在cook的后台设置里指定数据源路径。比如:
bash复制mkdir -p data/my-recipes/家常菜
mkdir -p data/my-recipes/烘焙
vim data/my-recipes/家常菜/红烧肉.md
Markdown文件里的格式并不复杂,核心是YAML头部加正文。头部写上菜名、标签、分类、准备时间、烹饪时间这些元数据,正文用普通Markdown写食材清单和步骤。cook会解析这些内容,渲染成漂亮的菜谱卡片。
一个markdown菜谱示例:
markdown复制---
name: 红烧肉
tags: [肉类, 下饭]
category: 家常菜
time: 90分钟
ingredients:
- 五花肉 500g
- 冰糖 30g
- 生抽 3勺
- 料酒 2勺
---
## 步骤
1. 五花肉切块,冷水下锅焯水。
2. 炒糖色,下肉块翻炒上色。
3. 加入料酒、生抽,加热水没过肉面。
4. 小火炖60分钟,最后大火收汁。
写完保存,回cook界面刷新,这道菜就出现了。整个过程很像写博客文章,但渲染出来的是一个精致的菜谱。
3.3 数据同步:Git仓库作为菜谱库的妙用
既然数据都是Markdown文件,最自然的同步方式就是用Git。我把data/my-recipes整个文件夹做成了一个Git仓库,推送到GitHub私有仓库。这样有三层好处:
本地改完文件后可以随时提交,历史版本都能回溯。如果以后换机器部署,直接clone仓库就能把整个菜谱库迁过去。GitHub本身可以作为备份,就算家里的硬盘坏了,菜谱库也不丢。
我还试着在笔记软件里写菜谱、保存成Markdown文件,再通过同步盘放到服务器目录下,cook自动就能识别新菜谱。这种“随处编辑、一处展示”的模式,用起来非常顺手。
3.4 初始化的常见问题与调优
整个部署过程中,我遇到过三个比较典型的坑。
第一,端口冲突。如果9239端口已经被其他服务占用,容器会起不来。排查方法是docker compose logs查看日志,然后改映射端口,比如改成9230:9239,注意右边的端口是容器内部端口,不能改,左边是宿主机对外端口,可以随意。
第二,数据目录权限。挂载目录如果权限不对,容器内写不进去,界面会出现只读或保存失败的情况。直接chmod -R 755 data就能解决大部分权限问题。
第三,Markdown的YAML头部格式错误。有一次我少写了一个冒号,导致整个菜谱无法显示。YAML对缩进和标点极其敏感,如果解析失败,建议先在本地用任意YAML校验工具检查一遍格式。
4. cpolar接入:让私房菜谱走出家门
4.1 为什么给cook配cpolar而不是公网服务器
在cook跑通本地之后,最关键的决策来了:如何从外面访问它。
最直观的方案是把cook部署到一台有公网IP的云服务器上。但这样每个月至少多花几十块服务器费用,菜谱这种低流量应用,专门租一台服务器实在有点浪费。而且数据放在云上,隐私性也不如放在自己家里。
另一种方案是家庭宽带的公网IP加动态DNS。但现在很多家庭宽带根本没有独立的公网IPv4地址,运营商给的都是大内网IP,路由器上端口映射根本无从谈起。折腾一圈往往无功而返。
cpolar这类内网穿透工具的价值就在这里:它不要求你有公网IP,不要求你办专线,只要你能让cpolar客户端主动连接cpolar的云端服务器,就等于在云端和家里之间建立了一条专属隧道。外面的访问请求先到cpolar云端,再由云端通过隧道转发到家里的cook服务。整个过程对用户来说,就是得到一个公网可访问的域名而已。
4.2 cpolar安装与隧道配置实录
cpolar的安装很简单,在Ubuntu上执行官方提供的一键脚本:
bash复制curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash
装完之后先做一次基础认证。注册cpolar账号之后,在用户后台能看到一串authtoken,把本机和账号关联起来:
bash复制cpolar authtoken <你的token>
然后启动一个指向cook的隧道。cook跑在9239端口,所以我用的命令是:
bash复制cpolar http 9239
这个命令的意思是:在cpolar云端创建一个HTTP隧道,把所有到这个隧道域名的请求,转发到本机的9239端口。执行之后,终端会输出一个公网地址,形如https://xxxx.cpolar.top。用浏览器打开这个地址,看到的界面和本地访问一模一样。
如果只是想临时用一用,到这一步就已经完成了。但我建议你继续做两件事:配置开机自启,以及申请一个固定的子域名。
4.3 固定域名与访问控制的配置细节
默认情况下,免费套餐的隧道域名是随机生成的,每次重启服务域名可能会变。对私人菜谱来说,域名一变,手机上的书签就得改,非常麻烦。
cpolar支持给免费用户绑定一个固定的二级域名,这个二级域名是基于你账号的专属随机前缀,比如cook-xxxx.cpolar.top。配置方法有两种:一种是在cpolar的Web管理界面(默认监听本地的9200端口)里操作,另一种是直接改配置文件。
我的操作是在Web管理界面里完成的。打开http://localhost:9200,进入“隧道管理”,添加一条新隧道,填如下信息:
- 协议:HTTP
- 本地地址:127.0.0.1:9239
- 域名类型:固定域名
- 自定义域名:选择一个你喜欢的二级域名前缀
保存之后,重启隧道,固定域名就生效了。以后不管客户端怎么重连,对外访问地址都不会变。
访问控制方面,cpolar提供基本认证(用户名密码),可以在隧道设置里开启。我在cook的服务外层又套了一层基础认证,这样即使域名被扫描到,也必须先过登录这关,才可能进入后面的cook界面。
4.4 开机自启:让菜谱服务永不掉线
我的服务器不是24小时守着的,家里断电重启之后,如果cook和cpolar没有自动启动,得手动连接一下才恢复,那就很不省心了。
Docker容器因为有restart: always策略,Docker服务随系统启动时会自动拉起cook容器,这部分不用额外操心。cpolar则需要手动配置为系统服务。
cpolar安装之后自带systemd服务文件,执行以下命令就能启用:
bash复制systemctl enable cpolar
systemctl start cpolar
要注意的是,cpolar的systemd服务默认读取配置文件/etc/cpolar/cpolar.yml。你在Web管理界面里配置的隧道会自动写入这个文件,所以只要配置过一次,服务起来之后隧道就自动存在了。
另外再补充一点:如果系统主动重启后,cook和cpolar都起来正常,但域名还是打不开,先检查cpolar进程是否已经连上云端:
bash复制systemctl status cpolar
看到Online状态,说明隧道已经建立。如果显示Offline,多半是网络问题,家里宽带拨号后DNS没通,等一会儿重试即可。
5. 常见问题与排查技巧实录
5.1 本地访问正常、公网访问超时
这是内网穿透场景最常遇到的问题。排查思路从外到内逐层收敛:先用手机流量访问cpolar域名,如果打不开,再检查cpolar客户端状态;客户端在线的话,再用本地浏览器访问http://localhost:9239,确认cook服务本身是好的。
如果本地正常、客户端在线、但公网依然不通,问题大概率出在cpolar隧道的“本地地址”配置上。有些机器有多个网卡,如果隧道里写的是192.168.x.x而实际服务只监听了127.0.0.1,对不上就会超时。安全起见,本地地址统一写127.0.0.1:9239最稳妥。
5.2 Markdown菜谱不显示或解析失败
cook对Markdown的解析比较严格,尤其是YAML头部。常见的坑有下面几种:
---开头后忘了结束符---- 字段名和冒号之间不能用中文冒号
ingredients列表的每一项必须以-开头且对齐- 多级分类的
category字段如果填了不存在的分类名,菜谱会被归到“未分类”里
我的经验是:每新建一个菜谱文件,先在本地用调试工具把YAML解析一遍,确认无错再放到服务器上。虽然多了一步,但能省掉很多“为什么菜谱不显示”的排查时间。
5.3 如何在移动端获得更好的浏览体验
菜谱系统最常用的场景其实是厨房里看手机,所以移动端体验非常关键。cpolar分配的域名浏览器访问后,cook自带响应式设计,竖屏显示效果不错,食材清单和步骤都能正常阅读。
但有几个体验细节我花时间调过:字体大小。如果觉得小了,可以直接在cook的界面设置里调整。存放菜谱的Markdown里,步骤一定要用有序列表。cook在移动端会把有序列表渲染成自动编号的分步卡片,比写成一整段文本清晰得多。为每一道菜准备一张封面图,没有实拍图也可以用简单的食材拼图,因为列表页的视觉冲击力主要靠封面图。
5.4 安全自查清单
任何人做内网穿透,第一反应都应该是安全。我的自查清单供参考:
- 是否开启了cpolar基础认证
- cook服务是否设置了登录密码
- cpolar账号是否启用了二次验证
- 是否长期依赖临时随机域名
- 数据目录是否有定期备份脚本
关于最后一条,我的做法是配置了一个简单的cron任务,每天凌晨把data目录打包上传到云端对象存储:
bash复制0 3 * * * tar -czf /backup/cook-$(date +%Y%m%d).tar.gz /data/cook
菜谱数据不涉及隐私敏感信息,但这里面存的是日积月累的家庭味道,丢了想找补回来几乎不可能。有一个自动备份兜底,心里踏实很多。
6. 真实使用体验与后续扩展方向
这套系统实际用了一段时间之后,我最明显的感受是:做饭这件事的决策成本确实被拉低了。过去晚饭前要在各种App之间来回切,现在打开手机书签里的cpolar域名,按“本周想吃的”标签翻一遍,不到半分钟就能定下来。周末想尝试新菜,就到素材库里翻收藏的菜谱,按难度排序挑一个。
还有一个意外收获:家里的菜谱终于不再是“某个人脑子里的经验”了。我以前总觉得爸妈的拿手菜很难复制,因为“适量”“少许”这种模糊描述太多。通过cook整理成结构化菜谱后,把模糊的“适量”改成具体克数,把“小火”标注到具体温度范围,做出来虽然和长辈的味道还有差距,但至少每一次都在逼近那个标准。
后续我还打算做几件事。一是把cook部署到一个低功率的ARM开发板上,进一步降低整机功耗,让这台服务器真正实现7x24小时运行。二是研究一下cook有没有开放API,如果有,就可以写一个小脚本:周末自动从备选菜谱里帮你挑三道菜,生成一份采购清单。三是给菜谱库引入季节性标签,比如夏天的凉菜、冬天的炖菜,用于换季时快速切换菜单。
最后再分享一个我自己很受用的习惯:每周花十分钟,把这周做过的新菜、改过的配方、家人说好吃的菜,随手更新到cook里。这个系统最好的用法,不是一次导入几百道菜,而是把它作为长期的“家庭味道账本”,慢慢沉淀。积累到一定量,你就有了一部完全属于自己的、带温度的活菜谱,而且走到哪儿都能带着它。
