感觉飞书云文档很好用,但是用那个写文档总是附属在某个组织下。我想要一个类似飞书云文档的好用的浏览器写文服务,不过也不需要那么复杂,在我的构想中是这样的:
- 在一台个人服务器上部署服务
- 通过浏览器访问,进行编辑和渲染至少支持markdown语法集合的富文本
- 服务端存储自动保存,实现方便的跨设备随时随地编写
- 身份验证,总不可能谁都能看能写
- 附加项:基础的人员管理功能,可以和亲友一起用
在和GPT大人chat了几个回合后,发现了Outline这个开源项目,和我的需求是最贴合的。仓库链接在这里。正好最近整了台阿里云ECS 99元一年的 !?穷穷?! 服务器 (饺子醋来了) 。我一看这Outline仓库的docker部署十分甚至九分的简单,这不得整一个。于是就有了后面的事情。
前置准备
在开始部署工作之前,先看看需要哪些东西吧!
- 2核2G、40GB SSD、3Mbps的阿里云ECS服务器(总之高于这个配置的肯定都可以,但这个配置基本是极限了)
- 完整的Docker、Docker Compose环境,可以参考阿里云的文档安装。docker镜像这块,阿里云分配的镜像已经不更新了,有一些image pull不下来。可以多试试这个仓库收集的源。
- 操作系统这块理论上来说是正儿八经的就行。不过我不太熟那个啥alibaba linux,用的ubuntu 24.04,后续也都建立在ubuntu 24.04的基础上。
swap
服务器内存太小了,实测下来,部署一个outline已经是燃尽了,随时有OOM风险。我也因为这个OOM导致卡死了好几次,导致配置过程中频繁重启服务器。因此这边我建议同样是用这个99元/年阿里云服务器的(或者差不多配置的)可以搞一下swap,用磁盘替代一下内存,至少不会卡死。
查看当前swap:
free -h
输出应该类似:
total used free
Mem: 2.0G 800M 1.2G
Swap: 0B 0B 0B
默认的话Swap应该是0. 我设置的是4G的swap:
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile # 设置为只有root可读
sudo mkswap /swapfile
sudo swapon /swapfile
NOTE
为什么要将swapfile设置为仅root可读? 因为 swap 文件会直接存放从内存换出的原始内存页,里面可能包含非常敏感的数据。如果普通用户可以读这个文件,就可能会有安全问题。
然后再用free -h检查一下swap是否启用,应该可以看到swap为4G。
激动人心的配置环节
我一开始尝试的是Outline官方的docker部署方式。发现不怎么开箱即用,特别是鉴权那块。说实话这块我知识盲区太多了,我折腾了好一会,把outline跑起来了,但是没鉴权,也就不能弄到公网上。随后查找各种outline部署博客,最后发现了这个仓库。这个仓库主打一个开箱即用。作者自己搓了一个私有化部署的鉴权服务器oidc,这个鉴权服务也开源了,仓库在这里。不过这个仓库还是有缺点的,就是已经没人在维护了,导致Outline版本比较低。emmm,我是懒得折腾了,现在用旧版本感觉也挺好的。
先跑起来
首先,第一步当然是把仓库clone下来:
git clone https://github.com/vicalloy/outline-docker-compose.git
然后cd进去,就可以使用make来进行一些操作:
cd ./outline-docker-compose
作者在Makefile里面写了一些基本的操作。这里列一下:
make:生成/更新配置文件make install:生成配置文件并且尝试拉取和启动docker容器,中间还会要求用户输入一些基本信息,名副其实的安装make start:启动outlinemake stop:停止outlinemake restart:先stop再start,也就是重启make clean:删除所有生成的配置文件make clean-data:删除所有数据(高危操作)
docker compose要通过env配置文件来进行配置,而这个仓库是通过scripts/config.sh中的内容来生成各个容器的env文件。一开始,scripts/下有一个config.sh.sample文件,可以在这个东西的基础上去填充具体的配置项:
cp ./scripts/config.sh.sample ./scripts/config.sh
然后在config.sh上改。再运行:
make
来生成/更新各项具体的配置文件。
在config.sh中,有以下各项比较重要的配置项:
URL=http://127.0.0.1:8888
DEFAULT_LANGUAGE=en_US
TIME_ZONE=UTC
FORCE_HTTPS=false
HTTP_IP=127.0.0.1
HTTP_PORT_IP=8888
接下来大概讲讲这些参数应该如何填。
- URL:填写outline服务的用户访问URL。例如之后配置域名了,要填具体域名,例如
https://outline.glowled.top - DEFAULT_LANGUAGE:填写outline服务的默认语言。这边我改成了简体中文
zh_CN - TIME_ZONE:时区,这个字段我填写的是中国上海
Asia/Shanghai - FORCE_HTTPS:这个暂时填false,这一块后面配置公网的时候会说一说,这块有说法的
- HTTP_IP:nginx的监听ip,保持127.0.0.1就行
- HTTP_PORT_IP:nginx的监听端口,保持8888就行(除非这个端口有服务在用了,可以换个别的)
然后直接:
make install
一开始会拉取镜像并运行。拉取docker image这个前文有提到,多尝试几个镜像源拉取。这一步可能会比较折腾。
这中间会要求你输入身份验证服务的管理员的名字(默认root)和密码。这个看心情输即可。
运行成功之后,可以用ssh端口映射8888到本机来进行访问测试,也可以用curl:
curl -I http://127.0.0.1:8888
结果是200 OK就行。如果是502,可以稍微等一会。过一会之后就会变成200.(这个很神奇,刚启动的时候总是nginx反代返回502 Bad Gateway,过几分钟就正常了)
不过建议还是用ssh端口映射,在浏览器里打开。界面应该是这样的:

然后点击使用 OpenID 继续,输入之前make install的时候弄的管理员账号密码,就可以登陆进去了。登陆进去的页面(这里有一些已有的东西,刚进去应该只有welcome的):

能看到这个基本就没啥问题了。
开放到公网
接下来就是把我们的服务配置到公网。
首先第一步我们需要安装一个叫做caddy的东西,用于根据域名将流量反代到具体的服务。不过真正的用途是用caddy自动处理https加密和安全性相关的东西(证书,之类的)。
首先是安装caddy:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy
安装时caddy会自动注册为systemctl的一个systemd服务。之后caddy就会是一个daemon,每次开机会自启动。
安装后检查:
caddy version
systemctl status caddy
配置caddy,打开caddy的配置文件:
sudo vim /etc/caddy/Caddyfile
写入以下内容:
outline.glowled.top {
reverse_proxy 127.0.0.1:8888
}
注意域名替换成你的域名。如果前面改过nginx监听端口,这里也要改。
在配置完成caddy之后,我们需要修改一下outline-docker-compose的配置文件config.sh。把其中的字段改成这样:
URL=https://outline.glowled.top #注意用https
DEFAULT_LANGUAGE=zh_CN
TIME_ZONE=Asia/Shanghai
FORCE_HTTPS=false # 注意保持false
HTTP_IP=127.0.0.1
HTTP_PORT_IP=8888
然后运行:
make && make restart
等待服务重启即可。重新运行之后,还是无法通过这个域名访问到outline服务。原因有两个:
- 域名解析DNS还没配
- 服务器端口没放行
这两个和具体的服务商有关,但是操作逻辑都差不多。因为我使用的是阿里云服务器,并且我的域名也是在阿里云购买的,因此这两个都使用阿里云举例子。
配置域名解析DNS:进入域名的控制台,点击“解析”,然后添加新的记录,记录类型为A(映射到ipv4),主机记录为你想要用的子域名名称,我这里是outline。解析请求来源选择默认即可。记录值输入你的服务器公网IPv4地址。在阿里云上,服务器的IPv4地址是可以在云服务器的控制台查看和复制的。随后点击确定即可。过几分钟之后域名就会被缓存到各个DNS服务器中。
配置服务器端口放行:进入云服务器控制台,点击网络与安全组,查看防火墙规则的入方向规则,然后添加入方向规则,选择允许、Web HTTP流量访问、ip地址选择0.0.0.0、端口选择80和443,随后点击确定即可。
配置完成后,直接从公网进行访问,应该就可以正常使用了。
添加新用户
添加新用户,可以访问https://address/uc/admin这个url。address替换成outline服务的域名或ip:port(例如这里是outline.glowled.top)。使用在make install时候的管理员名称和密码就可以登陆进去,这里就可以管理身份认证相关的设置。点击Users,在Users中就可以添加新用户了。
这块引导比较明显,看到应该就会了,因此不再赘述如何添加。但是这里提一个我遇到的问题。就是身份验证服务(oidc)中的User和outline中的账号不完全是同一个东西。outline本质通过邮箱来区分账号,所以两个User如果用同一个邮箱会被识别成一个账号。同时身份验证服务器中管理的权限,只是针对这个账户能不能管理身份验证服务器,而不是outline中的权限。outline中的权限需要管理员账号(oidc的管理员和outline的管理员账号是一致的)登陆outline,进行分配,默认是成员这个级别的。
总之oidc中的账户和outline中的账户有对应关系但是不完全相同。使用时要注意这一点。
后记
这里用来统计一些后续在使用过程中发现的问题,以及解决方案。
无法上传图片
上传图片进文章里,会报错upload failed。原因似乎是容器内的outline权限不足的问题。在上传图片失败后,立刻查看日志:
docker compose logs --tail=100 wk-outline
可以看到类似这样的东西:
wk-outline-1 | [Error: EACCES: permission denied, mkdir '/var/lib/outline/data/uploads'] {
wk-outline-1 | errno: -13,
wk-outline-1 | code: 'EACCES',
wk-outline-1 | syscall: 'mkdir',
wk-outline-1 | path: '/var/lib/outline/data/uploads',
wk-outline-1 | status: 500
wk-outline-1 | }
这个意思就是说想要创建/var/lib/outline/data/uploads这个目录,但是没有写权限。
先用这个命令查看uid与gid:
docker compose exec wk-outline id
输出类似:
uid=1001(nodejs) gid=65533(nogroup) groups=65533(nogroup)
还需要找到文件存储目录在哪:
docker inspect outline-docker-compose-wk-outline-1 \
--format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}'
输出应该类似:
/root/outline-docker-compose/data/outline -> /var/lib/outline/data
这里的/root/outline-docker-compose/data/outline就是宿主机的实际数据目录。
然后运行下面的命令来修改对应目录的权限(注意一下宿主机上的目录要跟着刚才那个命令的输出来改):
sudo mkdir -p /root/outline-docker-compose/data/outline/uploads
sudo chown -R {uid}:{gid} /root/outline-docker-compose/data/outline # {uid}与{gid}替换成实际数字
sudo chmod -R u+rwX /root/outline-docker-compose/data/outline
然后重启容器:
docker compose restart wk-outline
重启完成后,测试一下能否正常写入:
docker compose exec wk-outline sh -c '
mkdir -p /var/lib/outline/data/uploads &&
touch /var/lib/outline/data/uploads/test-file &&
rm /var/lib/outline/data/uploads/test-file
'
如果没有报错,就是成功了。

欢迎友好讨论~