mdserve使用PodmanCompose部署Markdown在线预览
mdserve 使用 Podman Compose 部署 Markdown 在线预览服务
1. 目标
使用 mdserve 将服务器上的 Markdown 文件以网页形式展示,实现:
- Markdown 文件直接通过浏览器访问
- 默认打开
README.md - 支持 Markdown、图片、代码块等内容
- 使用 Alpine Linux 作为运行环境
- 使用 Docker multi-stage build 构建镜像
- 使用 Podman / podman-compose 运行
- Markdown 文件放在宿主机目录,不打包进镜像
- 可以选择是否开启 Markdown 文件自动更新
- 可以选择关闭 mdserve 的版本更新检查
- 最终通过
http://服务器IP:9000/访问
整体结构:
宿主机
/data/docs
|
| volume,只读挂载
v
Podman 容器
/data/docs
|
v
mdserve
|
v
0.0.0.0:9000
|
v
浏览器2. Windows 上的 mdserve
之前在 Windows 上使用:
G:\ProgramFiles\mdserve-main\mdserve.exe serve \
--dir "G:\ProgramFiles\WorkBuddyfile\workbu0811\a1" \
--default-doc README.md启动后显示类似:
┌────────────────────────────────────────┐
│ mdserve · browse markdown as html │
└────────────────────────────────────────┘
v0.7.0 ● latest
G:\ProgramFiles\WorkBuddyfile\workbu0811\a1
4 markdown files · 1 directory
➜ http://127.0.0.1:8080/Windows 使用的是 8080 端口。
Linux 部署时可以使用另外一个端口,例如 9000。
3. mdserve 安装脚本
mdserve 官方提供安装脚本:
curl -fsSL https://raw.githubusercontent.com/mostafa-re/mdserve/main/scripts/install.sh | sh实际构建时发现,该脚本会自动识别当前系统架构。
例如当前服务器是 x86_64 / amd64 时:
mdserve: downloading v0.7.0 (linux/amd64)安装完成后实际路径为:
/root/.local/bin/mdserve而不是:
/usr/local/bin/mdserve这一点在制作 Dockerfile 时非常重要。
4. 最初的 Dockerfile
最开始可以直接在 Alpine 中安装:
FROM docker.1ms.run/alpine:3.22
RUN apk add --no-cache \
ca-certificates \
curl
RUN curl -fsSL https://raw.githubusercontent.com/mostafa-re/mdserve/main/scripts/install.sh | sh
RUN mkdir -p /data/docs
EXPOSE 9000
ENTRYPOINT ["/usr/local/bin/mdserve"]
CMD ["serve", \
"--dir", "/data/docs", \
"--addr", "0.0.0.0:9000", \
"--default-doc", "README.md"]但是这个版本存在一个问题:
安装脚本实际将 mdserve 安装到了:
/root/.local/bin/mdserve因此:
ENTRYPOINT ["/usr/local/bin/mdserve"]并不能直接使用安装脚本生成的文件。
另外,最终运行镜像其实不需要 curl,因此更适合使用 multi-stage build。
5. 推荐使用 Multi-stage Build
多阶段构建的基本思路:
第一阶段 builder
|
| 安装 curl
| 下载 mdserve
| 得到 mdserve 二进制文件
|
v
第二阶段 runtime
|
| 使用干净的 Alpine
| 只复制 mdserve 二进制
|
v
最终镜像这样最终运行镜像不需要包含:
curl
安装脚本
builder 阶段的其他文件最终镜像更加干净。
6. 推荐最终 Dockerfile
当前使用的推荐版本:
# ==============================
# 构建阶段
# ==============================
FROM docker.1ms.run/alpine:3.22 AS builder
RUN apk add --no-cache \
ca-certificates \
curl
# 安装 mdserve
RUN curl -fsSL https://raw.githubusercontent.com/mostafa-re/mdserve/main/scripts/install.sh | sh
# ==============================
# 运行阶段
# ==============================
FROM docker.1ms.run/alpine:3.22
RUN apk add --no-cache ca-certificates
# mdserve 实际安装位置是 /root/.local/bin/mdserve
COPY --from=builder /root/.local/bin/mdserve /usr/local/bin/mdserve
RUN mkdir -p /data/docs
EXPOSE 9000
ENTRYPOINT ["/usr/local/bin/mdserve"]
CMD ["serve", \
"--dir", "/data/docs", \
"--addr", "0.0.0.0:9000", \
"--default-doc", "README.md"]这里最关键的一行是:
COPY --from=builder /root/.local/bin/mdserve /usr/local/bin/mdserve含义:
builder:
/root/.local/bin/mdserve
|
| COPY --from=builder
v
runtime:
/usr/local/bin/mdserve然后:
ENTRYPOINT ["/usr/local/bin/mdserve"]就可以正常启动。
7. 为什么之前会出现 COPY 错误
之前使用:
COPY --from=builder /usr/local/bin/mdserve /usr/local/bin/mdserve构建时报错:
Error: building at STEP "COPY --from=builder /usr/local/bin/mdserve /usr/local/bin/mdserve":
copier: stat:
"/usr/local/bin/mdserve":
no such file or directory原因不是 Podman 或 Alpine 出问题。
而是 builder 阶段实际安装到了:
/root/.local/bin/mdserve日志已经明确显示:
mdserve: installed v0.7.0 to /root/.local/bin/mdserve所以应该使用:
COPY --from=builder /root/.local/bin/mdserve /usr/local/bin/mdserve8. 构建镜像
假设 Dockerfile 位于:
/data/docsify/zd1/Dockerfile进入目录:
cd /data/docsify/zd1使用 Podman 构建:
podman build -t mdserve:latest .如果需要完全重新构建:
podman build --no-cache -t mdserve:latest .构建完成后检查:
podman images应该能够看到:
mdserve latest9. 检查 mdserve 是否正常
构建完成后,可以直接执行:
podman run --rm mdserve:latest --version如果安装成功,应当能够看到类似:
mdserve v0.7.0这一步可以在正式创建容器之前验证镜像。
10. Markdown 文件不要放进镜像
生产环境推荐:
镜像
|
| 只负责 mdserve 程序
|
v
容器
宿主机
/data/docs
|
| volume
v
容器 /data/docs不要把 Markdown 文件写死在 Dockerfile 中。
这样以后上传新的 .md 文件时:
不需要重新 build
不需要重新制作镜像
不需要重新下载 mdserve只需要把文件放进:
/data/docs即可。
例如:
/data/docs/
├── README.md
├── guide.md
├── api.md
├── install.md
└── images/
├── logo.png
└── demo.png11. Docker volume 挂载
使用:
-v /data/docs:/data/docs:ro含义:
宿主机:
/data/docs
|
| 只读挂载
v
容器:
/data/docs其中:
/data/docs前面的是宿主机路径。
/data/docs后面的是容器内部路径。
:ro表示 Read Only,只读。
因此:
- mdserve 可以读取 Markdown
- mdserve 可以读取图片
- 容器不能修改 Markdown
- 容器不能删除 Markdown
- 宿主机仍然可以正常修改和上传文件
对于文档服务器而言,这种配置比较合适。
12. Podman run 方式
如果不使用 compose,可以直接:
podman run -d \
--name mdserve \
-p 9000:9000 \
-v /data/docs:/data/docs:ro \
--restart unless-stopped \
mdserve:latest \
serve \
--dir /data/docs \
--addr 0.0.0.0:9000 \
--default-doc README.md \
--no-update-check这里:
9000:9000表示:
宿主机 9000
|
v
容器 9000浏览器访问:
http://服务器IP:9000/13. Dockerfile 中 CMD 与 podman run 参数的关系
Dockerfile 中:
ENTRYPOINT ["/usr/local/bin/mdserve"]
CMD ["serve", \
"--dir", "/data/docs", \
"--addr", "0.0.0.0:9000", \
"--default-doc", "README.md"]最终组合起来相当于:
/usr/local/bin/mdserve \
serve \
--dir /data/docs \
--addr 0.0.0.0:9000 \
--default-doc README.md如果在 podman run 后面提供参数:
mdserve:latest \
serve \
--dir /data/docs \
--addr 0.0.0.0:9000 \
--default-doc README.md \
--no-update-check这些参数会覆盖 Dockerfile 中的 CMD。
因此:
ENTRYPOINT负责确定运行哪个程序。
而:
CMD负责提供默认参数。
14. Podman Compose 推荐配置
推荐使用 compose.yml:
services:
mdserve:
image: mdserve:latest
container_name: mdserve
restart: unless-stopped
ports:
- "9000:9000"
volumes:
- /data/docs:/data/docs:ro
command:
- serve
- --dir
- /data/docs
- --addr
- 0.0.0.0:9000
- --default-doc
- README.md
- --no-update-check如果使用已有的外部网络:
services:
mdserve:
image: mdserve:latest
container_name: mdserve
environment:
TZ: Asia/Shanghai
restart: unless-stopped
ports:
- "9000:9000"
volumes:
- /data/docs:/data/docs:ro
networks:
- znet
command:
- serve
- --dir
- /data/docs
- --addr
- 0.0.0.0:9000
- --default-doc
- README.md
- --no-update-check
networks:
znet:
external: true如果 znet 不存在,需要先创建:
podman network create znet然后:
podman-compose up -d15. 推荐让 compose 自动构建镜像
如果希望 podman-compose 同时负责构建,可以:
services:
mdserve:
build:
context: /data/docsify/zd1
dockerfile: Dockerfile
image: mdserve:latest
container_name: mdserve
environment:
TZ: Asia/Shanghai
restart: unless-stopped
ports:
- "9000:9000"
volumes:
- /data/docs:/data/docs:ro
command:
- serve
- --dir
- /data/docs
- --addr
- 0.0.0.0:9000
- --default-doc
- README.md
- --no-update-check然后:
podman-compose up -d --build以后 Dockerfile 有修改时:
podman-compose up -d --build如果只是修改:
/data/docs/*.md则不需要重新 build。
16. 相对路径和绝对路径
之前 compose 中使用:
volumes:
- ./docs:/data/docs:ro这个路径是相对于执行 podman-compose 时的 compose 项目目录。
例如如果当前目录:
/data/caddy那么:
- ./docs:/data/docs:ro实际对应:
/data/caddy/docs而不是:
/data/docs这很容易导致:
0 markdown files17. 推荐使用绝对路径
为了避免路径错误,推荐:
volumes:
- /data/docs:/data/docs:ro这样无论从哪个目录执行:
podman-compose up -d都明确表示:
宿主机 /data/docs
|
v
容器 /data/docs18. 排查 mdserve 显示 0 markdown files
如果日志出现:
/data/docs
0 markdown files · 1 directory首先检查宿主机:
ls -lah /data/docs应该能够看到:
README.md
xxx.md然后检查容器:
podman exec -it mdserve ls -lah /data/docs如果容器内部也能看到:
README.md说明挂载正常。
如果宿主机有文件,但是容器里面没有,则重点检查 compose 的 volumes。
推荐直接使用:
volumes:
- /data/docs:/data/docs:ro修改 compose 后重新创建:
podman-compose down
podman-compose up -d19. Linux 端口
Windows 原来的 mdserve 使用:
http://127.0.0.1:8080/Linux 当前使用:
--addr 0.0.0.0:9000并且 compose:
ports:
- "9000:9000"所以浏览器访问:
http://服务器IP:9000/例如:
http://1.2.3.4:9000/对应关系:
浏览器
|
| http://服务器IP:9000
v
宿主机 9000
|
v
Podman 容器 9000
|
v
mdserve20. 如果希望外部继续使用 8080
也可以不修改 mdserve 容器内部端口。
只需要:
ports:
- "8080:9000"含义:
宿主机 8080
|
v
容器 9000
|
v
mdserve此时浏览器访问:
http://服务器IP:8080/因此:
9000:9000表示外部使用 9000。
而:
8080:9000表示外部使用 8080,容器内部仍然使用 9000。
21. mdserve 的 --no-reload
默认情况下不加:
--no-reloadmdserve 会进行 live reload。
浏览器会周期性访问:
/api/poll例如:
https://zdoc.zhaopeng.site/api/poll可以在浏览器开发者工具 Network 中看到这个请求周期性出现。
这是 mdserve 的正常行为,不是异常流量。
22. 为什么上传 Markdown 后网页会自动更新
当前没有:
--no-reload所以工作流程是:
上传 README.md
|
v
/data/docs/README.md 发生变化
|
v
浏览器请求 /api/poll
|
v
mdserve 检测到文件变化
|
v
网页自动更新因此之前观察到:
上传 .md 文件
|
v
网页自动更新说明 live reload 正常工作。
23. --no-reload 的作用
如果加入:
--no-reload则关闭 live reload。
浏览器不会持续通过:
/api/poll检查 Markdown 文件变化。
效果:
上传 README.md
|
v
网页不会自动更新
|
v
手动刷新浏览器
|
v
看到最新内容24. --no-update-check 的作用
这个参数:
--no-update-check与 Markdown 自动刷新没有关系。
它用于关闭 mdserve 的版本更新检查。
生产环境一般可以使用:
--no-update-check这样可以减少不必要的更新检查请求。
25. 两个参数的区别
| 参数 | 作用 | 是否影响 Markdown 自动更新 |
|---|---|---|
--no-reload | 关闭 live reload | 是 |
--no-update-check | 关闭版本更新检查 | 否 |
如果需要实时编辑预览:
不加 --no-reload如果是生产环境,只提供已经写好的文档:
可以加 --no-reload通常生产环境可以:
--no-reload
--no-update-check26. 两种推荐运行方式
开发 / 编辑文档环境
command:
- serve
- --dir
- /data/docs
- --addr
- 0.0.0.0:9000
- --default-doc
- README.md
- --no-update-check特点:
- 支持自动检测 Markdown 文件变化
- 上传
.md后网页自动更新 - 浏览器会周期性访问
/api/poll - 适合边编辑边预览
生产 / 稳定展示环境
command:
- serve
- --dir
- /data/docs
- --addr
- 0.0.0.0:9000
- --default-doc
- README.md
- --no-reload
- --no-update-check特点:
- 不进行 live reload
- 浏览器不需要持续轮询
/api/poll - 上传 Markdown 后手动刷新
- 网络请求更少
- 更适合纯文档展示
27. 当前场景推荐配置
如果这个网站主要用于:
服务器上放 Markdown 文档,然后通过网页查看。
推荐:
services:
mdserve:
image: mdserve:latest
container_name: mdserve
environment:
TZ: Asia/Shanghai
restart: unless-stopped
ports:
- "9000:9000"
volumes:
- /data/docs:/data/docs:ro
command:
- serve
- --dir
- /data/docs
- --addr
- 0.0.0.0:9000
- --default-doc
- README.md
- --no-reload
- --no-update-check如果经常上传 Markdown,并希望上传后浏览器自动更新,则删除:
- --no-reload保留:
- --no-update-check28. Caddyfile 代码高亮警告
浏览器控制台曾出现:
highlight.min.js:107 WARN:
Could not find the language 'Caddyfile',
did you forget to load/include a language module?以及:
Falling back to no-highlight mode for this block.这不是 mdserve 服务端错误。
原因是 Markdown 中存在类似:
```Caddyfile
example.com {
reverse_proxy localhost:9000
}
```mdserve 使用的前端 highlight.js 没有加载 Caddyfile 对应的语言模块。
因此该代码块会:
不进行语法高亮但:
代码内容仍然可以正常显示所以:
- 不影响 mdserve
- 不影响 Markdown 页面
- 不影响 Caddy
- 不影响反向代理
- 不影响其他代码块
如果希望消除这个警告,可以把:
```Caddyfile改成:
```text或者直接:
```这样会以普通文本形式显示。
如果只是偶尔出现,通常可以忽略。
29. 当前完整架构
最终可以整理成:
浏览器
|
| HTTP :9000
v
+------------------+
| Caddy |
| 可选反向代理 |
+------------------+
|
| proxy
v
+------------------+
| Podman |
| |
| mdserve |
| :9000 |
| |
| /data/docs |
+------------------+
^
|
volume :ro
|
|
/data/docs
宿主机 Markdown如果使用 Caddy 域名:
https://zdoc.zhaopeng.site
|
v
Caddy
|
v
127.0.0.1:9000
|
v
mdserve
|
v
/data/docs/*.md30. 推荐目录结构
推荐服务器目录:
/data/
├── docs/
│ ├── README.md
│ ├── guide.md
│ ├── api.md
│ └── images/
│ ├── logo.png
│ └── demo.png
│
└── docsify/
└── zd1/
├── Dockerfile
└── compose.yml其中:
/data/docs负责存放实际 Markdown 文档。
/data/docsify/zd1负责 mdserve 镜像和 compose 配置。
31. 常用命令
构建镜像
podman build -t mdserve:latest .强制重新构建
podman build --no-cache -t mdserve:latest .查看镜像
podman images查看 mdserve 版本
podman run --rm mdserve:latest --version启动 compose
podman-compose up -d构建并启动
podman-compose up -d --build查看 compose 配置
podman-compose config查看容器
podman ps查看日志
podman logs mdserve实时查看日志
podman logs -f mdserve查看容器中的 Markdown
podman exec -it mdserve ls -lah /data/docs进入容器
podman exec -it mdserve sh停止 compose
podman-compose down重启
podman-compose restart删除容器后重新创建
podman-compose down
podman-compose up -d32. 最终推荐方案
Dockerfile
FROM docker.1ms.run/alpine:3.22 AS builder
RUN apk add --no-cache \
ca-certificates \
curl
RUN curl -fsSL https://raw.githubusercontent.com/mostafa-re/mdserve/main/scripts/install.sh | sh
FROM docker.1ms.run/alpine:3.22
RUN apk add --no-cache ca-certificates
COPY --from=builder /root/.local/bin/mdserve /usr/local/bin/mdserve
RUN mkdir -p /data/docs
EXPOSE 9000
ENTRYPOINT ["/usr/local/bin/mdserve"]
CMD ["serve", \
"--dir", "/data/docs", \
"--addr", "0.0.0.0:9000", \
"--default-doc", "README.md"]compose.yml
如果希望生产环境关闭自动刷新:
services:
mdserve:
image: mdserve:latest
container_name: mdserve
environment:
TZ: Asia/Shanghai
restart: unless-stopped
ports:
- "9000:9000"
volumes:
- /data/docs:/data/docs:ro
command:
- serve
- --dir
- /data/docs
- --addr
- 0.0.0.0:9000
- --default-doc
- README.md
- --no-reload
- --no-update-check如果希望上传 Markdown 后自动更新:
services:
mdserve:
image: mdserve:latest
container_name: mdserve
environment:
TZ: Asia/Shanghai
restart: unless-stopped
ports:
- "9000:9000"
volumes:
- /data/docs:/data/docs:ro
command:
- serve
- --dir
- /data/docs
- --addr
- 0.0.0.0:9000
- --default-doc
- README.md
- --no-update-check33. 一套完整部署流程
进入 Dockerfile 所在目录:
cd /data/docsify/zd1构建:
podman build -t mdserve:latest .检查:
podman images | grep mdserve确认 Markdown:
ls -lah /data/docs如果使用外部网络:
podman network create znet如果已经存在则不需要重复创建。
启动:
podman-compose up -d检查:
podman ps查看日志:
podman logs mdserve检查容器中的文件:
podman exec -it mdserve ls -lah /data/docs最后浏览器访问:
http://服务器IP:9000/如果前面配置了 Caddy 反向代理,则直接访问:
https://你的域名/34. 最终结论
当前部署方案的核心原则是:
Docker 镜像
=
mdserve 程序
宿主机 /data/docs
=
实际 Markdown 数据
Podman volume
=
连接两者
Podman Compose
=
管理容器运行参数
Caddy
=
可选的 HTTPS / 域名 / 反向代理层这种方式最大的优点是:
修改 Markdown
↓
不需要重新构建镜像
上传 Markdown
↓
不需要重新创建容器
升级 mdserve
↓
只需要重新构建镜像
修改端口或 mdserve 参数
↓
修改 compose.yml 即可对于长期运行的 Markdown 文档网站,这种架构比把 .md 文件直接 COPY 进 Docker 镜像更加方便。