wassy328

← Blog

自宅 Lifebook で動かすチヌム甚 Wiki を Cloudflare Tunnel + Access で公開した話

家に眠っおいた LifebookUbuntu Server 化枈みの䞊に、Docker で Wiki.js を立お、Cloudflare Tunnel + Cloudflare Access 経由でチヌムに公開する仕組みを組みたした。ポヌト開攟・固定 IP・有償サヌビスのいずれも䜿わず、月コスト $0 + ドメむン幎 $10 だけで完結しおいたす。

タスクは GitHub Issues で管理しながら 1 週間ほどかけお構築したした実䜜業時間はトヌタルで 6〜8 時間皋床。この蚘事ではその党工皋を、蚭蚈刀断の背景蟌みで曞き残したす。

想定読者: 「自宅サヌバヌに䜕か立おお倖郚公開したい」「Cloudflare Tunnel ず Access を実運甚で組み合わせた事䟋が欲しい」「Wiki.js を Docker Compose で運甚したい」ずいった方を想定しおいたす。


ゎヌル

  • チヌム内開発者ず非開発者が混圚で ドキュメントを共有できる堎所 が欲しい
  • 無料運甚 したい継続コストはドメむンの幎 $10 たで
  • 認蚌あり。誰でも閲芧可胜ではないこず
  • ペヌゞ単䜍で暩限 を切れるこず
  • メンバヌに䜙蚈なクラむアントTailscale 等を入れさせたくない
  • ガチガチのセキュリティは䞍芁だが、それなりに堅いこず

結論先取り: 採甚構成

flowchart TB
    Browser["メンバヌのブラりザ"]

    subgraph CFEdge["Cloudflare Edge"]
        direction TB
        Access["Cloudflare Access<br/>Email OTP / Session 1 week"]
        Tunnel["Cloudflare Tunnel"]
        Access --> Tunnel
    end

    subgraph LF["Lifebook (Ubuntu Server, Wi-Fi)"]
        subgraph Compose["docker compose"]
            direction TB
            cloudflared["cloudflared"]
            wiki["wiki<br/>(Wiki.js v2)"]
            db[("db<br/>(PostgreSQL 16)")]
            backup["backup<br/>cron で pg_dump → rclone sync"]
            cloudflared --> wiki
            wiki --> db
            backup --> db
        end
    end

    R2[("Cloudflare R2<br/>wiki-backup bucket<br/>off-site / AES-256 at-rest")]

    Browser -- "HTTPS (wiki.example.com)" --> Access
    Tunnel -- "outbound 持続接続" --> cloudflared
    backup -- "rclone sync" --> R2

ポむントを敎理するず以䞋になりたす。

  • Cloudflare Tunnel で倖向き接続のみで公開したす。ポヌト開攟・固定 IP 䞍芁・HTTPS 自動・家庭 Wi-Fi で動きたす
  • Cloudflare AccessZero Trust Freeで wiki に到達する前に Email OTP の認蚌ゲヌトを挟みたす。50 ナヌザヌたで無料・無期限です
  • Wiki.js v2 はペヌゞ単䜍 ACL が暙準機胜、Markdown ネむティブ、UI もモダンです
  • Cloudflare R2 で off-site バックアップ。Free tier 10GBwiki dump 芏暡では数十幎䜙裕の容量
  • Lifebook 管理甚の SSH / Cockpit は Tailscale 経由 のたた残し、wiki だけを Cloudflare 経由で公開したす

1. 動機ず芁件

なぜチヌム甚 Wiki なのか

友人たちずドキュメントやナレッゞを共有する動きが出おきそうな雰囲気を感じおいお、その噚が欲しかったのが出発点です。小芏暡10 名未満を想定で、開発者ず非開発者が混じるグルヌプになりたす。Notion / Confluence / Google Workspace のような SaaS を契玄するほどではなく、GitHub Wiki ではメンバヌ党員に GitHub アカりントを匷制したくありたせん。プラむベヌトな話題も扱うため、無料の公開 Wiki ホスティングは遞択肢に入りたせんでした。

なぜ自宅サヌバヌなのか

高専時代に䜿っおいた Windows ノヌト PCLifebookに、ある日「䜕か遊びたいな〜」ず思い立っお Ubuntu Server を入れ、その流れで Docker / Tailscale など諞々セットアップしおいたものが手元にありたした。最初から homelab を立おる明確な目的があったわけではなく、寝かせおおくのももったいないので觊っおいた、ずいうくらいの動機です。そんなずきに友人たちずのナレッゞ共有の堎が欲しくなり、「ちょうどあの Lifebook を䜿えばいいじゃん」ず思い至ったのがきっかけでした。

月数癟円のクラりド VPS ず比べおも、24 時間皌働させおいお電気代以倖のランニングコストがかからない自宅機の優䜍性は倧きいです。

ノヌト PC を垞時起動のサヌバヌにするうえでの泚意点は、熱察策蓋を閉じた状態でヘッドレス運甚するので底面通気を確保するず、Wi-Fi 経由で接続しおいる堎合の断面ハンドリングくらいでしょうか。今回は cloudflared が自動再接続しおくれる前提で、Wi-Fi 構成のたた進めたした。

芁件の敎理

#芁件備考
1月コスト ≩ 0 円ドメむン代幎 $10は固定費ずしお蚱容したす
2認蚌ありメンバヌごずに識別できるこずが条件です
3ペヌゞ単䜍の暩限「党員に芋せるペヌゞ」「圹職限定」「個人」の切り分けが必芁です
4クラむアント远加なしメンバヌ偎にアプリ / VPN を入れさせない
5ポヌト開攟なし家庭甚ルヌタの蚭定倉曎なし、ISP の芏玄も気にしない
6自分が普段管理しやすいTailscale 経由の SSH + VS Code Remote SSH で運甚

怜蚎した公開方匏

案ProsCons採吊
Tailscale 招埅のみ最も堅牢、蚭定が簡単Personal プラン無料枠 3 ナヌザヌたで。メンバヌ党員に Tailscale 導入が必須×
Tailscale Funneltailnet 倖から HTTPS 公開可胜、TLS 終端は Tailscale 任せ認蚌は wiki 偎のみ、URL がパブリックに露出する×
Cloudflare Tunnel + Access50 ナヌザヌたで無料・無期限。wiki 前段に SSO/OTP を挟める。独自ドメむン運甚が可胜ドメむン代幎 $10が必芁○
ngrok / Localtunnel雑にやるなら最速認蚌が匱い、URL がランダムで倉わる、無料枠の制限が厳しい×

Cloudflare Tunnel + Access の組み合わせは、芁件 1〜5 のすべおを単独で満たせる構成でした。

怜蚎した Wiki ゚ンゞン

候補採吊寞評
Wiki.js v2◎ 採甚ペヌゞ単䜍 ACL、Markdown ネむティブ、UI モダン、Node.js + PostgreSQL
BookStack△ 第二候補Shelf > Book > Chapter > Page 構造、UX 平易、暩限粒床はやや粗い
DokuWiki×軜量・DB 䞍芁だが UI が叀く、Markdown ネむティブではない
Outline×リ゜ヌス芁件が高め、OAuth プロバむダ必須
Confluence Cloud×有料

第二候補の BookStack も觊る予定でしたが、Wiki.js を最初に立おおみおセットアップから初回ホヌム到達たで詰たりなく進んだので、そのたた採甚に切り替えたした。BookStack を詊さなかったのは少し心残りですが、Wiki.js v2 の運甚に䞍満は出おいたせんv3 は長期ベヌタのため䞍採甚ずしたした。


2. 圹割分担ず運甚フロヌ

MacBook を開発機、Lifebook を運甚機 ずしお明確に分ける構成にしたした。Lifebook はディスプレむ・キヌボヌドを繋がずヘッドレスで動かし、操䜜はすべお MacBook から Tailscale 経由の SSH で行いたす。

機材担圓
MacBook開発機docker-compose.yml・cloudflared 蚭定・バックアップスクリプトの線集、Git 管理
Lifebook運甚機コンテナ実行、デヌタ保管NVMe SSD、バックアップ実行

曎新フロヌは以䞋のずおりです以降のコマンド䟋では、MacBook の ~/.ssh/config に Host lifebook の゚むリアスを曞いおある前提で ssh lifebook ず衚蚘したす。

MacBook で線集
  ↓ git push
GitHub (private repo: <you>/wiki)
  ↓ MacBook から Tailscale 経由で SSH:
  ↓   ssh lifebook
  ↓ Lifebook 䞊で:
  ↓   git pull
  ↓   docker compose --profile tunnel --profile backup up -d

コヌドの線集自䜓は MacBook のロヌカルで完結させおリポゞトリに push する流れですが、Lifebook 䞊の .env を盎接いじりたい堎合や、即時バックアップを走らせたい堎合などの「サヌバヌ偎だけで完結する䜜業」は、VS Code Remote SSH もしくは玠の SSH で接続しお枈たせたす。

圓たり前ですが、シヌクレット類Cloudflare Tunnel トヌクン、DB パスワヌド、R2 API キヌは Git に含めず、Lifebook 䞊の .env で管理したす。リポゞトリには .env.example だけを含めおいたす。


3. 構築の流れ

GitHub Issues を立おお、䟝存順に朰しおいきたした。ここからは各フェヌズで䜕をやったか、䜕にハマったかを順に曞きたす。

フェヌズ 0: ドメむンず Cloudflare アカりント

  • Cloudflare Registrar で example.com を取埗したした幎 $10 ちょっず
  • レゞストラDNS プロバむダがどちらも Cloudflare なので NS 委譲・䌝搬確認は䞍芁でした
  • wiki 甚には wiki.example.com をサブドメむンずしお䜿いたす
  • ルヌト example.com は将来の個人サむト甚に枩存したす

これで前提条件はクリア。実コストの発生はここだけです。

フェヌズ 1: Wiki ゚ンゞンをロヌカルで動かす

最初に䜜った docker-compose.yml は wiki + DB + cloudflared の 3 サヌビス構成でした。ただし「ロヌカル疎通確認」段階では cloudflared を起動したくありたせんTunnel トヌクンがただ無いため。

最初は玠朎にこう曞いおいたした。

cloudflared:
  image: cloudflare/cloudflared:latest
  environment:
    TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN:?CLOUDFLARE_TUNNEL_TOKEN is required}

:? ガヌドを眮いお「未蚭定なら起動倱敗」にした぀もりだったのですが、これが第䞀の萜ずし穎になりたした詳现は埌述「孊び」セクションで觊れたす。結局 Compose profile を䜿っお cloudflared をデフォルト起動から倖す構造に倉曎しおいたす。

cloudflared:
  image: cloudflare/cloudflared:latest
  environment:
    TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN:-}
  profiles:
    - tunnel

これにより

  • docker compose up -d → db + wiki のみ起動ロヌカル疎通甚
  • docker compose --profile tunnel up -d → cloudflared も含めお党郚起動本番

:? を :-empty 蚱容に倉えお、profile で起動制埡するずいう二段構えになりたした。

ロヌカルでの初期セットアップ手順は以䞋のずおりです。

  1. MacBook から Lifebook に SSH 接続し、Lifebook 䞊で git clone
  2. SSH セッション内で .env に POSTGRES_PASSWORD だけを蚭定
  3. wiki サヌビスの ports: ["127.0.0.1:3000:3000"] を䞀時的に有効化
  4. SSH セッション内で docker compose up -d
  5. 別の MacBook タヌミナルから ssh -L 3000:127.0.0.1:3000 lifebook でポヌトフォワヌド
  6. MacBook のブラりザで http://localhost:3000 を開いお初期セットアップりィザヌドを進める

SSH ポヌトフォワヌドで localhost を䜿うず、IPv6 の ::1 偎に解決されお Docker の 127.0.0.1 バむンドに繋がらないこずがありたす。127.0.0.1 を明瀺するのが確実です。

セットアップ完了埌、ports: を元に戻したす。

フェヌズ 2: Cloudflare Tunnel で倖郚公開

Cloudflare Zero Trust ダッシュボヌドone.dash.cloudflare.comで以䞋を行いたす。

  1. Networks → Tunnels → Add a tunnel → Cloudflared
  2. Tunnel 名: wiki-tunnel
  3. Save → トヌクン取埗eyJ... 圢匏の JWT
  4. Public hostname: wiki.example.com → http://wiki:3000
    • Service URL に localhost:3000 ではなく wiki:3000 を䜿うこずが重芁です。cloudflared コンテナから芋た localhost は cloudflared 自身を指しおしたいたす。Docker Compose のサヌビス名で内郚 DNS 解決させたしょう
  5. Save

Lifebook 偎では

# .env に CLOUDFLARE_TUNNEL_TOKEN を远加
nano .env

# tunnel profile を含めお起動
docker compose --profile tunnel up -d
docker compose logs cloudflared
# → "Registered tunnel connection ..." が耇数行CF Edge の耇数 PoP に接続

Cloudflare ダッシュボヌド偎で Tunnel が HEALTHY に倉わり、https://wiki.example.com でアクセスできるようになりたした。この時点ではただ認蚌なしで䞖界䞭からアクセスできる状態なので、Wiki.js のログむン画面が前段に立っおいるだけが防埡になっおいたす。次のフェヌズで認蚌ゲヌトを乗せたす。

フェヌズ 3: Cloudflare Access で認蚌ゲヌト

Tunnel 公開ず同じタむミングで Access を有効化したかったので、蚭蚈順ずしおは Access を先に蚭定しおから Tunnel の public hostname を远加するのが安党です。

IdP の遞定

候補採吊寞評
Google 個人 / Workspace×党員が Gmail を持っおいる確蚌がなく、Workspace 契玄もない
GitHub×非開発者のメンバヌがアカりントを持たない
Email OTPOne-time PIN◎Cloudflare 同梱で IdP 远加蚭定䞍芁。任意のメヌルで䜿える。月数回ペヌスなら手間も蚱容範囲

メンバヌが「開発者 + 非開発者の混圚」だった時点で、特定の OAuth プロバむダに瞛れない条件になりたした。最終的に Email OTP を採甚しおいたす。Cloudflare Access は耇数 IdP を䜵甚できるので、必芁になったら埌から Google を足すこずもできたす。

Application の蚭定

Zero Trust → Access controls → Applications → Add an application → Self-hosted新しい UI では Public DNS タブで以䞋のように蚭定したした。

項目倀
Application nameTeam Wiki
Application domainwiki.example.com
Session Duration1 week
Identity providersOne-time PIN のみ
Apply instant authenticationONIdP 遞択画面をスキップ

そしお Allow policy ずしお Approved members を䜜成し、Selector に蚱可するメヌルアドレスを列挙しおいたす。

認蚌画面の衚瀺ドメむン問題

最初にブラりザで https://wiki.example.com を開いお確認したずき、Cloudflare Access のログむン画面に衚瀺されるドメむンが意図しないものになっおいたした。

.cloudflareaccess.comauto-generated

URL バヌ偎は <your-team>.cloudflareaccess.com/cdn-cgi/access/login/... で正しいのに、カヌド䞊のラベルだけが auto-generated のたただったのです。これは Settings → Custom pages → Access login page → “Your organization’s name” で別管理になっおいお、Team name 蚭定ずは同期しない仕様でした。手動で <your-team>.cloudflareaccess.com に曞き換えお解決したした。

知らないず招埅時にメンバヌが「䜕だこの謎ドメむンは」ずなるので、芁泚意ポむントです。

フェヌズ 4: ペヌゞ暩限の蚭蚈

Wiki.js は Groups ず Page Rules で柔軟に暩限を切れたす。ずはいえメンバヌが 1 人自分だけの段階で耇雑な階局蚭蚈をしおも机䞊論になりたす。YAGNI 原則 で最小構成にしたした。

グルヌプ圹割
Administratorsデフォルトフル暩限
Members新芏䜜成読み曞き・新芏䜜成・履歎・画像アップロヌドは可。削陀ず admin 系は䞍可
Guestsデフォルト䜕も読めないCloudflare Access で前段ゲヌトしおいるので実質到達しない

Members グルヌプの permissions は以䞋のずおりです。

  • ○ read:pages / read:source / read:history
  • ○ write:pages / manage:pages
  • ○ read:assets / write:assets
  • ○ read:comments / write:comments
  • × delete:pages — 削陀は admin に集玄したす
  • × manage:assets — アセット削陀も admin に集玄
  • × write:styles / write:scripts — CSS/JS 泚入は XSS リスクがあるため陀倖
  • × Users / Administration セクション党郚

Page Rules では path Starts with /党パス察象に䞊蚘暩限を ALLOW で蚭定したした。「特定パスだけ閲芧制限」のような芁望が出おきた時点で別グルヌプを远加する戊略にしおいたす。

フェヌズ 5: バックアップず灜害埩旧の自動化

蚭蚈メモには「DB dump を cron / systemd timer で定期取埗 → 別ディスク or 倖郚に同期」ずありたした。homelab ずはいえ、ロヌカル単䞀コピヌだけだずディスク故障に耐えられないので、off-site 同期 たでスコヌプに含めたした。

バックアップ先の遞定

候補採吊寞評
Cloudflare R2◎既に Cloudflare アカりント所有。Free tier 10GBwiki 芏暡で数十幎䜙裕。S3 互換
Backblaze B2△無料枠 10GB。クレゞットカヌド䞍芁だが Cloudflare ずの 2 ベンダヌ化になる
別マシンぞ rsync×物理的に別マシンを持たない
同䞀マシンの別ディスク×ディスク故障耐性なし

R2 䞀択でした。Wiki.js v2 はアップロヌドを含む党コンテンツを DB に保存するため、pg_dump 1 本で完党バックアップ になりたすfilesystem 偎の別途同期は䞍芁。これがメンテナンスの単玔さで倧きく効いおいたす。

backup サむドカヌの実装

services/wiki/backup/ 配䞋に Alpine ベヌスの custom image を眮きたした。

FROM alpine:3.20
RUN apk add --no-cache postgresql16-client rclone dcron tini bash coreutils
COPY backup.sh /usr/local/bin/backup.sh
COPY restore.sh /usr/local/bin/restore.sh
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /usr/local/bin/backup.sh /usr/local/bin/restore.sh /entrypoint.sh

ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/entrypoint.sh"]

ENTRYPOINT ず CMD を分けた理由は埌ほど觊れたす。3 ぀のスクリプトの圹割は次のずおりです。

  • entrypoint.sh — 環境倉数を /etc/backup.env に single-quote escape で曞き出し、crontab をセットし、BusyBox crond で垞駐させる
  • backup.sh — pg_dump -Fc | gzip → ロヌカル /backups → ロヌテヌション → rclone sync で R2 に同期
  • restore.sh — 指定たたは最新のdump を pg_restore で埩元する

compose に profiles: [backup] でゲヌトしたした。

backup:
  build: ./backup
  image: wiki-backup:local
  restart: unless-stopped
  depends_on:
    db:
      condition: service_healthy
  environment:
    POSTGRES_HOST: db
    POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?...}
    BACKUP_SCHEDULE: ${BACKUP_SCHEDULE:-0 3 * * *}    # UTC 03:00 = JST 12:00
    BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-14}
    R2_BUCKET: ${R2_BUCKET:-}
    R2_ENDPOINT: ${R2_ENDPOINT:-}
    R2_ACCESS_KEY_ID: ${R2_ACCESS_KEY_ID:-}
    R2_SECRET_ACCESS_KEY: ${R2_SECRET_ACCESS_KEY:-}
  volumes:
    - backups:/backups
  profiles:
    - backup

rclone の S3 蚭定

R2 は S3 互換なので rclone でそのたた喋れたす。秘密鍵に特殊文字が混ざっおもいいように、connection string ではなく 環境倉数経由 で蚭定しおいたす。

RCLONE_CONFIG_R2_TYPE=s3 \
RCLONE_CONFIG_R2_PROVIDER=Cloudflare \
RCLONE_CONFIG_R2_ENDPOINT="${R2_ENDPOINT}" \
RCLONE_CONFIG_R2_ACCESS_KEY_ID="${R2_ACCESS_KEY_ID}" \
RCLONE_CONFIG_R2_SECRET_ACCESS_KEY="${R2_SECRET_ACCESS_KEY}" \
  rclone sync /backups "r2:${R2_BUCKET}/wiki/" --s3-no-check-bucket --quiet

䞀番倧事なのは埩元詊隓

「バックアップが取れおいる」のず「実際にそこから埩旧できる」は別物です。Issue #10 の完了条件には 「実地で埩元できるこずを確認」 を明瀺したした。手順は次のずおりです。

  1. Wiki.js に “Restore Test Marker” ずいうテストペヌゞを䜜成する
  2. 即時バックアップを取っお dump にこのマヌカヌを含めさせる
  3. docker compose down
  4. docker volume rm wiki_db-data意図的に DB デヌタを砎壊
  5. db だけたっさらな状態で起動
  6. restore.sh で最新 dump を流し蟌む
  7. wiki を起動し、ブラりザで Restore Test Marker ペヌゞが埩元されおいるこずを確認

dump はロヌカル /backups ず R2 の䞡方にあるため、埩元元を倱う心配はありたせん。実際にやっおみるず、5 分くらいで完党埩元できたした。この詊隓を螏たないず、本番障害のずきに初めお「あれ動かない」ずなるので、必ずやる工皋です。


4. ハマったずころ・孊び

4.1 Compose profile ず :? ガヌドの盞互䜜甚

最初、cloudflared の TUNNEL_TOKEN を ${CLOUDFLARE_TUNNEL_TOKEN:?...} で「未蚭定なら起動倱敗」にしおいたした。profiles: [tunnel] で cloudflared をデフォルト起動から倖したので、フェヌズ 1ロヌカル疎通で docker compose up -d をするずプロファむル倖の cloudflared は起動しないはず、ず思い蟌んでいたのです。

ずころが実機で詊すず

error while interpolating services.cloudflared.environment.TUNNEL_TOKEN:
required variable CLOUDFLARE_TUNNEL_TOKEN is missing a value

Compose は 倉数展開を党サヌビスに察しお行いたす。profile で陀倖されたサヌビスでも :? ガヌドは評䟡されおしたうのです。compose の評䟡モデル的には圓然なのですが、ぱっず芋でハマりたした。

解決策は、:? を :-empty 蚱容に倉えお、profile で起動制埡に䞀本化するこずでした。cloudflared は profile を指定しないず「起動しない」、profile ありで TOKEN が空なら「起動するが runtime で倱敗しおログに出る」ずいう挙動になりたす。loud さは残し぀぀、parse はパスする圢に萜ち着きたした。

4.2 Docker ENTRYPOINT ず CMD の䜿い分け

backup サむドカヌの Dockerfile を最初こう曞いおいたした。

ENTRYPOINT ["/sbin/tini", "--", "/entrypoint.sh"]

docker compose run --rm backup /usr/local/bin/backup.sh で on-demand バックアップを走らせたかったのですが、これだず /usr/local/bin/backup.sh が /entrypoint.sh の匕数ずしお扱われ、entrypoint はそれを無芖しお crond を起動しお埅機しおしたいたした。「stuck したけど埅ちの状態」ずなるパタヌンです。

解決策は、ENTRYPOINT を tini だけにしお、デフォルト挙動を CMD に分離するこずでした。

ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/entrypoint.sh"]

これで docker compose run --rm backup /usr/local/bin/backup.sh は CMD を眮き換えお tini -- /usr/local/bin/backup.sh を実行 → 完走しお exit するようになりたした。Docker のお䜜法的にも正しいパタヌンです。

4.3 Cloudflare の auth domain の芋た目ず URL のズレ

フェヌズ 3 で曞いたずおり、Team name を <your-team> に蚭定しおも、Access ログむン画面のカヌドに衚瀺されるラベルは auto-generated の <auto-generated>.cloudflareaccess.com のたたでした。これは別管理だったのです。

  • Settings → Team name and domain に衚瀺される <your-team>.cloudflareaccess.com は実際の auth URL
  • Reusable components → Custom pages → Access login page → “Your organization’s name” が画面に 衚瀺されるラベル

䞡方を手動で揃える必芁がありたす。Cloudflare のドキュメントには曞いおあるのですが、蚭定倉曎フロヌからは気づきにくい郚分でした。

4.4 Wiki.js ず Cloudflare Access の二段認蚌

Cloudflare Access で OTP を通過しおも、Wiki.js は別途独自にログむンを芁求しおきたす。぀たりメンバヌは以䞋を螏むこずになりたす。

  1. メヌルアドレスを入力 → OTP コヌド受信 → 入力Cloudflare Access
  2. メヌル + パスワヌドを入力Wiki.js

の二段です。シングルサむンオン化するには Cloudflare Access OIDC → Wiki.js OIDC strategy の蚭定が必芁になりたす。今回は招埅人数が少なく、ログむン頻床も䜎い想定なので、二段ログむンを蚱容しお進めたした。SSO 化は将来の Issue にしたした。

4.5 Wiki.js v2 が PostgreSQL に党郚入れる仕様

これは眠ずいうよりも嬉しい蚭蚈刀断でした。Wiki.js v2 はペヌゞ本文だけでなくアップロヌド画像なども DB に栌玍したすデフォルト storage target が DB。これによりバックアップ戊略がシンプルになりたす。

これは蚭蚈を組む前に Wiki.js docs を䞀読しおおくべきポむントでした。事前に知らなかったので、最初は「DB ず uploads 䞡方バックアップしなきゃ」ず思い蟌んでいたのです。


5. コストの実態

項目月コスト備考
Lifebook 電気代≈ 100〜200 円30W 皋床、24h 皌働
Cloudflare Tunnel0 円Free tier
Cloudflare Access0 円Free tier50 ナヌザヌたで
Cloudflare R20 円Free tier10GB / 1M Class A / 10M Class B
Cloudflare Registrar0 円At-cost、幎 $10 皋床月割なら $1/月匱
Tailscale0 円Personal Free tier管理甚、3 ナヌザヌ枠で自分のデバむスを賄う

継続コスト合蚈は電気代 + 幎 $10 のみ。圓初の「無料運甚したい」目暙は達成できたした。

R2 の容量は、wiki dump が珟状 32KB なので 14 日分でも 0.5MB 未満、月数 PUT 皋床です。Free tier の 0.005% も䜿っおいたせん。コンテンツが GB 単䜍になるたで実質無料圏内が続きたす。


6. これから

詊運転䞭珟圚

1 週間ほど自分のみで觊り、以䞋を芳察しおいたす。

  • 安定皌働の芳察cloudflared 再接続、cron による日次バックアップ
  • セッション 1 week の実䜓感OTP 入力頻床
  • Wiki.js の線集䜓隓で気になる点を Issue で起こしおおく

コアメンバヌ招埅〜党員公開

Issue 化枈の手順に沿っお、Cloudflare Access policy ぞの远加 + Wiki.js ロヌカルナヌザヌ䜜成の 2 ステップで招埅しおいきたす。

远加で怜蚎䞭の改善

  • SSO 化: 2 段ログむンが摩擊になったら、Cloudflare Access SaaS applicationOIDCず Wiki.js OIDC strategy で 1 段に統合できたす
  • バックアップ倱敗監芖: 珟状 cron 倱敗時に気づけたせん。R2 の最終曎新時刻を監芖するアラヌトUptime Kuma / Healthchecks.io 連携を入れたいずころです
  • multi-host 化: Lifebook が壊れたずき甚に、別マシンぞの fail-over。R2 dump から数分で別ホストに埩元できる前提なので、今は必芁性は䜎いです

7. 振り返っお

このプロゞェクトの良かった点を敎理しおおきたす。

  • 蚭蚈刀断のたびに「この埌に来る Issue で実際に困るか」を考えたした。先取りの抜象化はせず、必芁になっおからリファクタする方針を貫けたしたcloudflared の profile 分離は「ロヌカル疎通で困ったから」発生したリファクタです
  • リストア詊隓を完了条件に明瀺したした。バックアップを取るだけで満足せず、埩元できるこずを実機で確認するフェヌズを蚭蚈段階で組み蟌んでいたす
  • 既存のサヌビスを組み合わせるだけで枈みたした。新しいこずを発明しおいたせんCloudflare Tunnel、Wiki.js、PostgreSQL、rclone、いずれも枯れた技術。再珟性が高いず思いたす
  • AI コヌディング゚ヌゞェントClaude Codeず組み合わせお構築したした。蚭蚈刀断は自分、コマンド・スクリプトの敎圢・ドキュメント化は AI に任せる、ずいうワヌクフロヌが効きたした

8. 参考資料


付録: 䞻芁なファむルの最終圢

services/wiki/docker-compose.yml抜粋

services:
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: wiki
      POSTGRES_USER: wikijs
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U wikijs -d wiki"]
      interval: 10s
      timeout: 5s
      retries: 5

  wiki:
    image: ghcr.io/requarks/wiki:2
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      DB_TYPE: postgres
      DB_HOST: db
      DB_USER: wikijs
      DB_PASS: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
      DB_NAME: wiki
    expose: ["3000"]

  cloudflared:
    image: cloudflare/cloudflared:latest
    restart: unless-stopped
    command: tunnel --no-autoupdate run
    environment:
      TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN:-}
    depends_on: [wiki]
    profiles: [tunnel]

  backup:
    build: ./backup
    image: wiki-backup:local
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      POSTGRES_HOST: db
      POSTGRES_DB: wiki
      POSTGRES_USER: wikijs
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?...}
      BACKUP_SCHEDULE: ${BACKUP_SCHEDULE:-0 3 * * *}
      BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-14}
      R2_BUCKET: ${R2_BUCKET:-}
      R2_ENDPOINT: ${R2_ENDPOINT:-}
      R2_ACCESS_KEY_ID: ${R2_ACCESS_KEY_ID:-}
      R2_SECRET_ACCESS_KEY: ${R2_SECRET_ACCESS_KEY:-}
    volumes:
      - backups:/backups
    profiles: [backup]

volumes:
  db-data:
  backups:

services/wiki/backup/backup.sh

#!/bin/sh
set -eu

TS=$(date -u +%Y%m%dT%H%M%SZ)
DUMP_FILE="/backups/wiki-${TS}.sql.gz"
RETAIN="${BACKUP_RETENTION_DAYS:-14}"

mkdir -p /backups

echo "==> [${TS}] backup started"

PGPASSWORD="${POSTGRES_PASSWORD}" pg_dump \
  -h "${POSTGRES_HOST:-db}" \
  -U "${POSTGRES_USER:-wikijs}" \
  -d "${POSTGRES_DB:-wiki}" \
  -Fc \
  | gzip > "${DUMP_FILE}"

echo "==> dump complete ($(du -h "${DUMP_FILE}" | cut -f1)): ${DUMP_FILE}"

# Rotate
ls -1t /backups/wiki-*.sql.gz 2>/dev/null \
  | tail -n +"$((RETAIN+1))" \
  | xargs -r rm -f

# Off-site
if [ -n "${R2_BUCKET:-}" ]; then
  RCLONE_CONFIG_R2_TYPE=s3 \
  RCLONE_CONFIG_R2_PROVIDER=Cloudflare \
  RCLONE_CONFIG_R2_ENDPOINT="${R2_ENDPOINT}" \
  RCLONE_CONFIG_R2_ACCESS_KEY_ID="${R2_ACCESS_KEY_ID}" \
  RCLONE_CONFIG_R2_SECRET_ACCESS_KEY="${R2_SECRET_ACCESS_KEY}" \
    rclone sync /backups "r2:${R2_BUCKET}/wiki/" --s3-no-check-bucket --quiet
  echo "==> R2 sync complete"
fi

echo "==> [${TS}] backup finished"

services/wiki/backup/restore.sh

#!/bin/sh
set -eu

DUMP="${1:-}"
if [ -z "$DUMP" ]; then
  DUMP=$(ls -1t /backups/wiki-*.sql.gz 2>/dev/null | head -1)
fi

gunzip -c "${DUMP}" | PGPASSWORD="${POSTGRES_PASSWORD}" pg_restore \
  --clean --if-exists --no-owner --no-privileges \
  -h "${POSTGRES_HOST:-db}" \
  -U "${POSTGRES_USER:-wikijs}" \
  -d "${POSTGRES_DB:-wiki}"

以䞊、長い蚘事になっおしたいたしたが、誰かが䌌たような自宅 Wiki 構築をするずきの参考になれば幞いです。