簡介
Memos 是一款具備高度隱私與開源特性的輕量級筆記服務。隨著筆記與附件數量的增加,傳統將資料庫與附件統一存放在高容量但低速的傳統硬碟 (HDD) 架構,容易導致 SQLite 發生嚴重的 I/O 瓶頸與 RPC 逾時錯誤。
本指南將帶領您透過 Docker Compose 與 Portainer,部署一套結合 Memos 與 Memogram (Telegram 整合機器人) 的系統。透過「混合儲存架構」(資料庫存放於高效能 SSD,大型附件存放於大容量 HDD),徹底解決讀取卡頓問題,並結合自動化備份腳本與 Traefik 進行安全連線管理,打造兼具效能與安全的個人知識庫。
先決條件
在開始部署之前,請確保您的伺服器具備以下環境與資源:
- 一台運行 Linux (如 Ubuntu/Debian) 的伺服器,且使用者具有
sudo權限。 - 伺服器已安裝 Docker 與 Docker Compose。
- 伺服器已安裝並設定好 Traefik 作為反向代理。
- 伺服器具備雙硬碟架構:系統碟 (SSD,本例為 256GB) 與資料碟 (HDD,本例為 4TB)。
- 已在 Telegram 透過 BotFather 取得專屬的 Bot Token。
- 伺服器已安裝
sqlite3命令列工具。
環境參數總覽
本指南所使用的所有關鍵路徑、網路設定與環境變數如下表所示。若您過去曾進行過多次測試,請以本表的最終確認版本為準。
| 參數類型 | 參數名稱 / 路徑 | 說明 |
|---|---|---|
| 儲存路徑 (SSD) | /home/YOUR_USERNAME/memos | 存放 memos_prod.db 資料庫,提供極速查詢效能。 |
| 儲存路徑 (HDD) | /mnt/cloud1/dockerData/memos/memosdb/assets | 存放圖片、影片等大型附件資源。 |
| 備份路徑 (HDD) | /mnt/cloud1/dockerData/memos/backups | 存放每日自動產生的資料庫備份檔。 |
| 內部網路 | default (bridge) | 供 Memos 與 Memogram 內部通訊使用的 Docker 橋接網路。 |
| 外部網路 | traefik_network | 供 Traefik 進行外部流量路由的網路。 |
| 連線變數 | SERVER_ADDR=dns:memos:5230 | Memogram 連線至 Memos 的 gRPC 內部解析位址。 |
| 授權變數 | ALLOWED_USERNAMES | 限制可使用 Telegram 機器人的使用者名稱清單 (半形逗號分隔,不含 @)。 |
逐步教學
步驟一:建立混合儲存目錄架構
為了將資料庫的 I/O 負載與大型檔案的儲存負載分離,我們需要分別在 SSD 與 HDD 上建立對應的目錄。請在終端機執行以下指令:
# 建立 SSD 資料庫目錄
mkdir -p /home/YOUR_USERNAME/memos
# 建立 HDD 附件與備份目錄
mkdir -p /mnt/cloud1/dockerData/memos/memosdb/assets
mkdir -p /mnt/cloud1/dockerData/memos/backups
# 確保目錄擁有者正確 (避免 Docker 權限衝突)
sudo chown -R YOUR_USERNAME:YOUR_USERNAME /home/YOUR_USERNAME/memos
sudo chown -R YOUR_USERNAME:YOUR_USERNAME /mnt/cloud1/dockerData/memos
步驟二:撰寫與部署 Docker Compose
我們將使用 Portainer 的 Stack 功能來進行部署。請建立一個新的 Stack,並填入以下 docker-compose.yml 內容。此設定檔已將隱私資料抽離為環境變數,並妥善設定了內部網路。
version: "3.8"
networks:
# 確保所有服務都接入 Traefik 的共用網路
traefik_network:
external: true
# 明確宣告內部預設網路,確保同設定檔內跨網路服務 (如 App 與 DB) 可以互相溝通
default:
driver: bridge
services:
memos:
image: neosmemo/memos:stable
container_name: memos
environment:
- MEMOS_PUBLIC=${MEMOS_PUBLIC_MODE}
- TZ=Asia/Taipei
volumes:
# (1) 資料庫本體:掛載到 SSD 以獲取極速的 I/O 查詢
- /home/YOUR_USERNAME/memos:/var/opt/memos
# (2) 附件目錄:掛載到 4TB HDD 以利用大儲存空間
- /mnt/cloud1/dockerData/memos/memosdb/assets:/var/opt/memos/assets
restart: unless-stopped
networks:
- traefik_network
- default
labels:
- "traefik.enable=true"
# 網址設定:使用 .env 中的 APP_DOMAIN 變數來設定外部連線網址
- "traefik.http.routers.${ROUTER_NAME}.rule=Host(`${APP_DOMAIN}`)"
# 進入點:指定只接收 443 通訊埠 (HTTPS) 的安全連線
- "traefik.http.routers.${ROUTER_NAME}.entrypoints=websecure"
# 憑證解析器:呼叫 Traefik 主程式中的 myresolver 來處理 SSL 憑證
- "traefik.http.routers.${ROUTER_NAME}.tls.certresolver=myresolver"
# 內部通訊埠:使用 .env 中的 INTERNAL_PORT 告訴 Traefik 容器的真實通訊埠
- "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=${INTERNAL_PORT}"
# 嚴格安全防護 (包含 HSTS,適合多數對外服務,預設開啟)
- "traefik.http.routers.${ROUTER_NAME}.middlewares=sec-strict@file"
# 指定 Traefik 路由時使用的網路,避免多網路環境下抓錯 IP 導致 500 錯誤
- "traefik.docker.network=traefik_network"
memogram:
# 官方專案目前並未提供打包好的 Docker 映像檔,直接指定官方 GitHub 專案網址進行建置
build: https://github.com/usememos/telegram-integration.git
container_name: memogram
environment:
# Memos 正在運行的 gRPC 伺服器位址,使用內網連線直接指向 memos 容器名稱
- SERVER_ADDR=${SERVER_ADDR}
# 你的 Telegram 機器人 Token
- BOT_TOKEN=${BOT_TOKEN}
# (選用) Telegram API 的代理伺服器位址,若不需要請留空
- BOT_PROXY_ADDR=${BOT_PROXY_ADDR}
# (選用) 允許使用的 Telegram 使用者名稱列表,使用半形逗號分隔且不含 @ 符號
- ALLOWED_USERNAMES=${ALLOWED_USERNAMES}
restart: unless-stopped
depends_on:
- memos
networks:
# 只需要加入 default 網路即可與 memos 容器互相通訊,不需要對外暴露給 Traefik
- default
在 Portainer 部署介面下方,請新增對應的 Environment variables (環境變數),例如 APP_DOMAIN, SERVER_ADDR (設定為 dns:memos:5230), BOT_TOKEN 等。
步驟三:設定 Telegram 機器人與儲存機制
容器啟動後,請進行以下關鍵設定:
- 設定 Memos 儲存路徑:登入 Memos 網頁介面,進入「設定」>「儲存」,將儲存服務改為「Local」,並確保路徑為
assets/{filename}。這能防止未來的附件被塞入 SQLite 資料庫中。 - 綁定 Telegram:在 Memos 系統中取得您的 Access Token。開啟 Telegram 並向您的機器人發送
/start <您的 Access Token>完成綁定。
進階優化
自動化備份腳本與 Cron 排程
為防範系統碟 (SSD) 故障,我們將實作「熱備份」機制,將資料庫每日備份至 HDD。
- 建立腳本檔案:
nano /home/YOUR_USERNAME/backup_memos.sh
- 填入以下內容:
#!/bin/bash
# --- 變數設定 ---
DB_SOURCE="/home/YOUR_USERNAME/memos/memos_prod.db"
BACKUP_DEST="/mnt/cloud1/dockerData/memos/backups"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DEST
if [ ! -f "$DB_SOURCE" ]; then
echo "錯誤:找不到來源資料庫 $DB_SOURCE"
exit 1
fi
# 使用 sqlite3 的 .backup 指令進行安全熱備份
sqlite3 $DB_SOURCE ".backup $BACKUP_DEST/memos_$DATE.db"
# 刪除超過 30 天的舊備份
find $BACKUP_DEST -mtime +30 -type f -name "*.db" -delete
echo "Memos 備份完成於 $DATE"
- 賦予執行權限並加入 Crontab 排程:
chmod +x /home/YOUR_USERNAME/backup_memos.sh
crontab -e
在檔案末端加入此行 (每日凌晨 03:00 執行):
00 03 * * * /bin/bash /home/YOUR_USERNAME/backup_memos.sh
常見問題與故障排除
1. Memos 讀取清單時發生 failed to list attachments (RPC 逾時錯誤)
- 現象:Memos 網頁載入極慢,日誌顯示
internal: rpc error: code = Internal desc = failed to list attachments。 - 原因:SQLite 資料庫放置於傳統 HDD 上,當附件紀錄眾多且檔案碎片化時,磁碟的隨機讀寫速度無法負荷,導致 gRPC 連線逾時。
- 最終解決邏輯:將資料庫檔案 (
memos_prod.db) 移至 SSD 掛載路徑,並透過指令開啟 SQLite 的 WAL (Write-Ahead Logging) 模式以提升併發效能:
sudo sqlite3 /home/YOUR_USERNAME/memos/memos_prod.db "PRAGMA journal_mode=WAL;"
2. 資料庫檔案異常肥大 (數百 MB)
- 現象:純文字筆記的資料庫高達 200MB 以上。
- 原因:舊版設定將大型檔案 (如 MP4, PDF) 的實體二進位資料存入了資料庫的資料表中,而非外部的
assets資料夾。 - 最終解決邏輯:查詢並刪除大於 1MB 的異常紀錄,隨後執行
VACUUM重組資料庫。 (注意:在 Memos v0.26.x 中,資源表已從resource更名為attachment。)
# 刪除大於 1MB 的附件紀錄
sudo sqlite3 /home/YOUR_USERNAME/memos/memos_prod.db "DELETE FROM attachment WHERE size > 1000000;"
# 回收與重組空間
sudo sqlite3 /home/YOUR_USERNAME/memos/memos_prod.db "VACUUM;"
3. 執行 VACUUM 時出現 readonly database (8)
- 現象:嘗試優化資料庫時報錯。
- 原因:由 Docker 建立的資料庫檔案擁有者通常為容器內的使用者 (如 UID 10001),主機端的一般使用者缺乏寫入與建立暫存檔的權限。
- 最終解決邏輯:在
sqlite3指令前方加上sudo提升權限執行。
4. 終端機顯示 zsh: corrupt history file
- 現象:操作終端機時出現歷史紀錄損毀警告。
- 原因:系統意外斷電或硬碟 I/O 阻塞導致
.zsh_history寫入不完整,產生亂碼。 - 最終解決邏輯:過濾出正常字元並覆蓋原檔案。
cd ~
mv .zsh_history .zsh_history_bad
strings .zsh_history_bad > .zsh_history
fc -R .zsh_history
5. 清理無效的系統排程
- 現象:系統持續每 5 分鐘執行已廢棄的 DuckDNS 更新腳本。
- 最終解決邏輯:透過
crontab -e刪除對應的排程行,並使用rm -rf /home/YOUR_USERNAME/duckdns徹底移除相關資料夾。
結論
透過上述指南,我們成功解決了 Memos 在傳統硬碟上的效能瓶頸。藉由將輕量級的資料庫遷移至 SSD,並將大容量附件卸載至 HDD,我們在極小的 SSD 空間消耗下獲得了顯著的效能提升。配合 Memogram 的 Docker 源碼建置部署、Traefik 的安全路由,以及每日自動化的資料庫備份與雲端同步規劃,您的知識庫系統現在已具備企業級的穩定度與災難復原能力。
