DEV Community

Cover image for Outline Wiki 自架教學(一):Docker 安裝、Gitea OIDC 與 AI 知識庫準備
Let's Write
Let's Write

Posted on • Originally published at letswrite.tw

Outline Wiki 自架教學(一):Docker 安裝、Gitea OIDC 與 AI 知識庫準備

Outline Wiki 自架教學(一):Docker 安裝、Gitea OIDC 與 AI 知識庫準備

本篇要解決的問題

公司開發團隊裡,想架一套內部的知識庫,又要能支援 AI,常見的痛點有幾個:

  • 文件資料留在自己的機器上,不對外,以免洩露商業機密。
  • 想要加上 AI?得付 $$,比如 Notion,或是也可以自架的 Docmost。
  • Outline Wiki 裝起來很難,August 失敗過一次,最後是靠 Claude Opus 4.8 的輔助才成功。

本篇筆記文,會用一份 Docker Compose 檔,把 Outline,以及需要的 PostgreSQL、Redis、Caddy 全包進去,並針對上面這些坑一次閃掉。

另外也先說明本文裡的基本設定:

  1. 本機已裝好 Docker。
  2. 對內 PORT 是 3002,對外是 3001

關於 Outline + AI 的補充說明:

自架版 Outline 沒有內建的「AI Answers」功能,網路上常看到的 OPENAI_API_KEY 對自架版是無效的。想要 Outline 加上 AI,正解是用 MCP 外接,讓 Claude / Codex 透過 Outline 的 API 來操作(作法將寫在下一篇)。


步驟 1:安裝 Gitea

第一個管理員帳號要用 Gitea 登入,所以得先有一台 Gitea。

Gitea 本身也能用 Docker 自架,這部分 August 之前寫過,直接看這篇:

安裝 Gitea

只要你的 Gitea 已經能正常登入,就可以往下走。

Outline 並不限定 Gitea,任何相容 OIDC 的供應商(Keycloak、Authentik、Google⋯)都行,差別只在 OIDC_* 那幾個網址。


步驟 2:從 Gitea 取得 Client ID 與 Secret

Outline 走 OIDC 登入,需要在 Gitea 建立一個「OAuth2 應用程式」,拿到 Client IDClient Secret

進入 Gitea 後,到 設定(Settings)→ 應用程式(Applications),找到「管理 OAuth2 應用程式」區塊。

在 Gitea 的「設定 → 應用程式」頁面,找到「管理 OAuth2 應用程式」的建立表單

在 Gitea 的「設定 → 應用程式」頁面,找到「管理 OAuth2 應用程式」的建立表單

需要填兩個欄位:

  • 應用程式名稱:填一個自己記得註是做什麼的名稱,如:Outline Wiki Login
  • 重新導向 URI:要填的 PORT 是有對外加上憑證,比如本篇用的是 3001
https://<你的IP或網域>:3001/auth/oidc.callback
Enter fullscreen mode Exit fullscreen mode

例如主機 IP 是 192.168.11.111,就填 https://192.168.11.111:3001/auth/oidc.callback。這個 IP 與埠號要跟等一下 Compose 裡的 URL= 完全一致。

Gitea OAuth2 應用程式表單,填入應用程式名稱與重新導向 URI(重點是 Redirect URI 結尾為 /auth/oidc.callback)

Gitea OAuth2 應用程式表單,填入應用程式名稱與重新導向 URI(重點是 Redirect URI 結尾為 /auth/oidc.callback)

按下「建立應用程式」後,Gitea 會顯示 Client IDClient Secret

Gitea 建立完成後顯示的 Client ID 與 Client Secret 畫面。

Gitea 建立完成後顯示的 Client ID 與 Client Secret 畫面。

要注意,Client Secret 只會完整顯示這一次,先複製存好。等一下要填進 Compose 的 OIDC_CLIENT_IDOIDC_CLIENT_SECRET


步驟 3-1:安裝 Outline Wiki:Windows 版

Windows 用 Docker Desktop,這裡用具名 volume(由 Docker 管理)存附件,啟動最單純。

先新增一個空資料夾,接著我們要放進下面的 2 個檔案。

Caddyfile

資料夾內新增一個檔案,檔名:Caddfile

Caddyfile 負責「對外用 HTTPS、把流量轉給 Outline」。

檔案內容複製貼上以下,再把 192.168.11.111 換成我們 Windows 主機的區網 IP:

{
  default_sni 192.168.11.111
}

192.168.11.111:3001 {
  tls internal
  reverse_proxy outline:3002
}
Enter fullscreen mode Exit fullscreen mode
  • tls internal:Caddy 自己簽一張憑證。因為我們用的是區網 IP,本來就申請不到正式憑證,自簽夠用。
  • 3001 是對外埠、outline:3002 是 Outline 容器內部埠,兩者分工,注意不能寫成一樣。

docker-compose.yml

資料夾內再新增一個檔案,檔名:docker-compose.yml

檔案內容複製貼上以下:

# =============================================================================
# Outline Wiki — Docker Compose 教學範例
# =============================================================================
# 架構:
#   outline   → 主應用 (Node.js)
#   caddy     → 反向代理 + 自動 HTTPS
#   postgres  → 資料庫
#   (外部)    → Redis,負責快取 / WebSocket / 背景工作佇列
#
# ⚠️ 發佈前務必把所有 <...> 佔位符換成你自己的值。
#    範例裡的密鑰、密碼、IP 全是假的,不要直接拿去用。
# =============================================================================

services:
  # ---------------------------------------------------------------------------
  # Outline 主應用
  # ---------------------------------------------------------------------------
  outline:
    image: outlinewiki/outline:latest
    container_name: outline
    restart: unless-stopped
    environment:
      - NODE_ENV=production

      # --- 安全密鑰 (必填) ---------------------------------------------------
      # 兩把都要用 32-byte 隨機 hex 字串。產生方式:
      #   openssl rand -hex 32
      # ⚠️ 換掉後不要再改,改了會讓既有 session / 加密資料失效。
      - SECRET_KEY=<openssl rand -hex 32 產生>
      - UTILS_SECRET=<openssl rand -hex 32 產生>

      # --- 資料庫連線 (必填) -------------------------------------------------
      # 格式:postgres://帳號:密碼@主機:埠/資料庫名
      # 主機名 postgres = 下面那個 service 名稱,Docker 內部 DNS 解析。
      - DATABASE_URL=postgres://outline_user:<DB_密碼>@postgres:5432/outline
      - PGSSLMODE=disable   # 同一台 Docker 內部連線,不需 SSL

      # --- Redis 連線 (必填) -------------------------------------------------
      # 格式:redis://:密碼@主機:埠/DB編號
      # 這裡指向下面內建的 redis service,主機名 redis = service 名。
      - REDIS_URL=redis://redis:6379/1

      # --- 對外網址 (必填) ---------------------------------------------------
      # 使用者瀏覽器看到的完整網址,要跟 Caddy / 反代對外位置一致。
      - URL=https://<你的網域或IP>:8443
      - PORT=3019            # 容器內部監聽埠 (給 Caddy 反代用,不直接對外)
      - FORCE_HTTPS=false    # HTTPS 由 Caddy 處理,這裡關掉避免重導迴圈

      # --- 檔案儲存 (必填) ---------------------------------------------------
      # local = 存本機磁碟 (搭配下面 volumes)。也可改 s3 接物件儲存。
      - FILE_STORAGE=local
      - FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data
      # 上傳/匯入大小上限 (bytes)。預設約 1MB,匯入大 zip (如從
      # Docmost/Notion 匯出) 會跳「file too large」。調大解決。
      # 算法: MB × 1048576。下面 = 250MB。
      - FILE_STORAGE_UPLOAD_MAX_SIZE=262144000            # 附件上限
      - FILE_STORAGE_IMPORT_MAX_SIZE=262144000            # 單篇匯入
      - FILE_STORAGE_WORKSPACE_IMPORT_MAX_SIZE=262144000  # zip 整包匯入 (關鍵)

      # --- 登入: Gitea OIDC (選填) -------------------------------------------
      # 任何相容 OIDC 的供應商都行 (Gitea / Keycloak / Authentik / Google...)。
      # client id/secret 從你的 OIDC 供應商後台拿。
      - OIDC_CLIENT_ID=<OIDC_CLIENT_ID>
      - OIDC_CLIENT_SECRET=<OIDC_CLIENT_SECRET>
      - OIDC_AUTH_URI=http://<OIDC主機>/login/oauth/authorize
      - OIDC_TOKEN_URI=http://<OIDC主機>/login/oauth/access_token
      - OIDC_USERINFO_URI=http://<OIDC主機>/login/oauth/userinfo
      - OIDC_USERNAME_CLAIM=preferred_username   # 用哪個欄位當帳號
      - OIDC_DISPLAY_NAME=Gitea                   # 登入按鈕顯示文字
      - OIDC_SCOPES=openid profile email

      # --- SMTP 寄信 (選填) --------------------------------------------------
      # 用途:magic-link 登入信 + 通知信。
      # Gmail 範例:密碼要用「應用程式密碼」,不是帳號密碼。
      # (Google 帳號 → 安全性 → 兩步驟驗證 → 應用程式密碼)
      - SMTP_HOST=smtp.gmail.com
      - SMTP_PORT=587
      - SMTP_SECURE=false    # 587 用 STARTTLS,所以這裡 false
      - SMTP_USERNAME=<你的gmail@gmail.com>
      - SMTP_PASSWORD=<Gmail 應用程式密碼>
      - SMTP_FROM_EMAIL=<你的gmail@gmail.com>
      - SMTP_REPLY_EMAIL=<你的gmail@gmail.com>

    volumes:
      # 用具名 volume,由 Docker 管理。注意:這裡存的是「附件/上傳檔」
      # (圖片、PDF...),不是文章本體。文章內容存在 PostgreSQL,不是 .md 檔。
      - outline_data:/var/lib/outline/data
    depends_on:
      postgres:
        condition: service_healthy   # 等 DB 健康檢查通過才啟動 outline
      redis:
        condition: service_healthy   # 等 Redis 就緒

  # ---------------------------------------------------------------------------
  # Redis — 快取 / WebSocket / 背景工作佇列 (內建獨立 service)
  # ---------------------------------------------------------------------------
  redis:
    image: redis:7-alpine
    container_name: outline_redis
    restart: unless-stopped
    volumes:
      - redis_data:/data   # 持久化 (選用,Outline 主要當快取,掉了也能重建)
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]   # 回 PONG 才算就緒
      interval: 10s
      timeout: 5s
      retries: 5

  # ---------------------------------------------------------------------------
  # Caddy — 反向代理,自動處理 HTTPS 憑證
  # ---------------------------------------------------------------------------
  caddy:
    image: caddy:2
    container_name: outline_caddy
    restart: unless-stopped
    ports:
      - "8443:8443"   # 對外開的埠 (主機:容器)。實際反代規則寫在 Caddyfile。
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro   # :ro = 唯讀掛載
      - caddy_data:/data       # 存 TLS 憑證,別刪,刪了要重新申請
      - caddy_config:/config
    depends_on:
      - outline

  # ---------------------------------------------------------------------------
  # PostgreSQL
  # ---------------------------------------------------------------------------
  postgres:
    image: pgvector/pgvector:pg16
    container_name: outline_postgres
    restart: unless-stopped
    environment:
      # 這三個要跟上面 DATABASE_URL 裡的帳號/密碼/DB名一致。
      - POSTGRES_USER=outline_user
      - POSTGRES_PASSWORD=<DB_密碼>
      - POSTGRES_DB=outline
    volumes:
      - pg_data:/var/lib/postgresql/data   # 資料庫實體資料,千萬別誤刪
    ports:
      # 只綁 127.0.0.1 = 僅本機可連,不對外曝露 DB。安全做法。
      - "127.0.0.1:5432:5432"
    healthcheck:
      # 確認 DB 真的可接受連線,outline 才會啟動 (見上面 depends_on)。
      test: ["CMD-SHELL", "pg_isready -U outline_user -d outline"]
      interval: 10s
      timeout: 5s
      retries: 5

# -----------------------------------------------------------------------------
# 具名 volume — 由 Docker 管理,容器刪掉資料還在。
# -----------------------------------------------------------------------------
volumes:
  outline_data:
  pg_data:
  redis_data:
  caddy_data:
  caddy_config:
Enter fullscreen mode Exit fullscreen mode

接著要把所有 <...> 的部份換成自己的值,要改這幾項:

  • SECRET_KEYUTILS_SECRET:開啟終端機,執行兩次 openssl rand -hex 32 產生 32-byte 隨機字串。沒有 openssl 的話,那就用 Claude、ChatGPT 等 AI,對話中讓它們生成 2 組使用。
  • DATABASE_URLPOSTGRES_PASSWORD:兩邊的密碼要一致。
  • URL:填 https://<你的IP>:3001,IP 要跟 Caddyfile 一樣。
  • OIDC_CLIENT_IDOIDC_CLIENT_SECRET:填上一段從 Gitea 拿到的值。
  • OIDC_AUTH_URI / OIDC_TOKEN_URI / OIDC_USERINFO_URI:把 <OIDC主機> 換成 Gitea 網址。
  • (選填)SMTP:要寄邀請信 / magic-link 才需要填。

啟動

docker-compose.yml 所在資料夾開終端機:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

第一次啟動 Outline 會自動跑資料庫遷移(migration),會需要多等個十幾秒。

看到 outline 正常起來後,瀏覽器打開 https://<你的IP>:3001

瀏覽器會跳「您的連線不是私人連線 / 不安全」警告。

這是因為 tls internal 用的是自簽憑證,屬於正常現象。點「進階 → 繼續前往」就能進到 Outline。

進站後點 Continue with Gitea 用 Gitea 帳號登入,第一個登入的人就是管理員。

之後的成員,在 Outline 後台用 Email 邀請即可(這也是為什麼上面要設定 SMTP)。


步驟 3-2:安裝 Outline Wiki:Ubuntu 版

Ubuntu 版跟 Windows 版幾乎一樣,只有兩個差別:

  1. 附件改用 bind mount(./data:附件直接落在專案資料夾,搬家 / 備份複製整個資料夾就帶走,不必像具名 volume 還要 tar 撈。
  2. OIDC 的 Gitea 可以在另一台機器,只要這台 Ubuntu 連得到即可。

檔案

一樣是兩個檔案:docker-compose.ymlCaddyfile

Caddyfile 內容(把 IP 換成你的 Ubuntu 主機 IP):

{
  default_sni 192.168.11.111
}

192.168.11.111:3001 {
  tls internal
  reverse_proxy outline:3002
}
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml 的內容複製貼上以下:

# =============================================================================
# Outline Wiki — Ubuntu 單主機部署版 (教學範例)
# =============================================================================
# 跟 Windows 版的差異:
#   1. 內建 Redis service — 不依賴外部 Redis 主機,一台 Ubuntu 全包。
#   2. 全部服務同一台主機,對外只露 Caddy 的 8443。
#   3. OIDC 仍可指向「另一台」Gitea/OIDC 主機,只要這台網路連得到。
#
# 架構:
#   outline   → 主應用 (Node.js)
#   redis     → 快取 / WebSocket / 背景佇列
#   caddy     → 反向代理 + 自動 HTTPS
#   postgres  → 資料庫
#
# ⚠️ 發佈前把所有 <...> 佔位符換成你自己的值。範例的密鑰/密碼/IP 全是假的。
#
# 部署步驟:
#   1. 整個資料夾複製到 Ubuntu (含 Caddyfile)。
#   2. 全域搜尋取代 <你的Ubuntu主機IP> → 實際 IP。
#   3. docker compose up -d
# =============================================================================

services:
  # ---------------------------------------------------------------------------
  # Outline 主應用
  # ---------------------------------------------------------------------------
  outline:
    image: outlinewiki/outline:latest
    container_name: outline
    restart: unless-stopped
    environment:
      - NODE_ENV=production

      # --- 安全密鑰 (必填) ---------------------------------------------------
      # 兩把都用 32-byte 隨機 hex。產生: openssl rand -hex 32
      # ⚠️ 換掉後別再改,改了會讓既有 session / 加密資料失效。
      - SECRET_KEY=<openssl rand -hex 32 產生>
      - UTILS_SECRET=<openssl rand -hex 32 產生>

      # --- 資料庫連線 (必填) -------------------------------------------------
      # 格式:postgres://帳號:密碼@主機:埠/資料庫名
      # 主機名 postgres = 下面的 service 名,Docker 內部 DNS 解析。
      - DATABASE_URL=postgres://outline_user:<DB_密碼>@postgres:5432/outline
      - PGSSLMODE=disable   # 同台 Docker 內部連線,不需 SSL

      # --- Redis 連線 (必填) -------------------------------------------------
      # ★ Ubuntu 版重點:指向「內建」redis service,主機名 redis = service 名。
      #   不像基本版要填外部 Redis IP/密碼。
      - REDIS_URL=redis://redis:6379/1

      # --- 對外網址 (必填) ---------------------------------------------------
      # 使用者瀏覽器看到的完整網址,要跟 Caddy 對外位置一致。
      - URL=https://<你的Ubuntu主機IP>:8443
      - PORT=3019            # 容器內部監聽埠 (給 Caddy 反向代理,不直接對外)
      - FORCE_HTTPS=false    # HTTPS 由 Caddy 處理,這裡關掉避免重導迴圈

      # --- 檔案儲存 (必填) ---------------------------------------------------
      - FILE_STORAGE=local
      - FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data
      # 上傳/匯入大小上限 (bytes)。預設約 1MB,匯入大 zip 會跳「file too large」。
      # 算法: MB × 1048576。下面 = 250MB。
      - FILE_STORAGE_UPLOAD_MAX_SIZE=262144000            # 附件上限
      - FILE_STORAGE_IMPORT_MAX_SIZE=262144000            # 單篇匯入
      - FILE_STORAGE_WORKSPACE_IMPORT_MAX_SIZE=262144000  # zip 整包匯入 (關鍵)

      # --- 登入: Gitea OIDC (選填) -------------------------------------------
      # 任何相容 OIDC 供應商皆可 (Gitea / Keycloak / Authentik / Google...)。
      # ★ Ubuntu 版注意:OIDC 主機可在「另一台」機器,確認這台 Ubuntu 連得到。
      - OIDC_CLIENT_ID=<OIDC_CLIENT_ID>
      - OIDC_CLIENT_SECRET=<OIDC_CLIENT_SECRET>
      - OIDC_AUTH_URI=http://<OIDC主機>/login/oauth/authorize
      - OIDC_TOKEN_URI=http://<OIDC主機>/login/oauth/access_token
      - OIDC_USERINFO_URI=http://<OIDC主機>/login/oauth/userinfo
      - OIDC_USERNAME_CLAIM=preferred_username   # 用哪個欄位當帳號
      - OIDC_DISPLAY_NAME=Gitea                   # 登入按鈕顯示文字
      - OIDC_SCOPES=openid profile email

      # --- SMTP 寄信 (選填) --------------------------------------------------
      # 用途:magic-link 登入信 + 通知信。
      # Gmail 範例:密碼要用「應用程式密碼」,不是帳號密碼。
      # (Google 帳號 → 安全性 → 兩步驟驗證 → 應用程式密碼)
      - SMTP_HOST=smtp.gmail.com
      - SMTP_PORT=587
      - SMTP_SECURE=false    # 587 用 STARTTLS,所以 false
      - SMTP_USERNAME=<你的gmail@gmail.com>
      - SMTP_PASSWORD=<Gmail 應用程式密碼>
      - SMTP_FROM_EMAIL=<你的gmail@gmail.com>
      - SMTP_REPLY_EMAIL=<你的gmail@gmail.com>

    volumes:
      # 存的是「附件/上傳檔」(圖片、PDF...),不是文章本體 (文章在 PostgreSQL)。
      # ★ 用 bind mount: 附件直接落在專案資料夾 ./data,搬家複製即帶走。
      #   對比 named volume (outline_data:) 要 tar 才能撈出,bind 好搬很多。
      #   注意:DB 不要 bind,DB 檔不能直接 copy,一律用 pg_dump 備份。
      - ./data:/var/lib/outline/data
    depends_on:
      postgres:
        condition: service_healthy   # 等 DB 健康檢查通過才啟動
      redis:
        condition: service_healthy   # 等 Redis 就緒

  # ---------------------------------------------------------------------------
  # Redis — 內建獨立 service (★ Ubuntu 版才有,基本版用外部 Redis)
  # ---------------------------------------------------------------------------
  redis:
    image: redis:7-alpine
    container_name: outline_redis
    restart: unless-stopped
    volumes:
      - redis_data:/data   # 持久化 (選用,Outline 主要當快取,掉了也能重建)
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]   # 回 PONG 才算就緒
      interval: 10s
      timeout: 5s
      retries: 5

  # ---------------------------------------------------------------------------
  # Caddy — 反向代理,自動處理 HTTPS 憑證
  # ---------------------------------------------------------------------------
  caddy:
    image: caddy:2
    container_name: outline_caddy
    restart: unless-stopped
    ports:
      - "8443:8443"   # 對外唯一開的埠 (主機:容器)。反代規則寫在 Caddyfile。
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro   # :ro = 唯讀掛載
      - caddy_data:/data       # 存 TLS 憑證,別刪,刪了要重新申請
      - caddy_config:/config
    depends_on:
      - outline

  # ---------------------------------------------------------------------------
  # PostgreSQL — pgvector 映像 (支援向量搜尋)
  # ---------------------------------------------------------------------------
  postgres:
    image: pgvector/pgvector:pg16
    container_name: outline_postgres
    restart: unless-stopped
    environment:
      # 這三個要跟上面 DATABASE_URL 的帳號/密碼/DB名一致。
      - POSTGRES_USER=outline_user
      - POSTGRES_PASSWORD=<DB_密碼>
      - POSTGRES_DB=outline
    volumes:
      - pg_data:/var/lib/postgresql/data   # 資料庫實體資料,千萬別誤刪
    ports:
      # 只綁 127.0.0.1 = 僅本機可連,不對外曝露 DB。安全做法。
      - "127.0.0.1:5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U outline_user -d outline"]
      interval: 10s
      timeout: 5s
      retries: 5

# -----------------------------------------------------------------------------
# 具名 volume — 由 Docker 管理,容器刪掉資料還在。
# -----------------------------------------------------------------------------
volumes:
  # outline_data 改用 bind mount (./data),不在這裡宣告。
  pg_data:
  redis_data:
  caddy_data:
  caddy_config:
Enter fullscreen mode Exit fullscreen mode

要修改的部份,如:密鑰、DB、OIDC、SMTP,都與 Windows 版填法完全相同。

啟動

docker-compose.yml 所在資料夾開終端機:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

確認與登入流程跟 Windows 版一樣:瀏覽器打開 https://<你的IP>:3001,遇到憑證警告點「繼續前往」,再用 Gitea 登入。


結語

照著上面的步驟做,我們就會有一套 完全自架 + 免費 + 可以用 MCP 接上 AI 的 Outline Wiki。

怎麼用 MCP 接上 Claude、Codex,下一篇會說明。

Top comments (0)