OSSのOutlineで組織のナレッジ共有・ドキュメント化を楽しくする

Outline は Notion らしさを感じる使っていて楽しいツールでした。特に、Notion のファンにはたまらない一品ですね。

GitHub - outline/outline: The fastest knowledge base for growing teams. Beautiful, realtime collaborative, feature packed, and markdown compatible.

GitHub - outline/outline: The fastest knowledge base for growing teams. Beautiful, realtime collaborative, feature packed, and markdown compatible.

The fastest knowledge base for growing teams. Beautiful, realtime collaborative, feature packed, and markdown compatible. - outline/outline

ドキュメント管理見直しの背景

組織内で Redmine および付随するWiki、その他 Git 関連サービスによるナレッジ化(ドキュメント化)に取り組んできましたが、Git はさておき Redmine の Wiki はプロジェクトごとに情報が分散しがちで不便に感じていました。(ここでいうプロジェクトは、Redmineの論理単位を指します)

情報が分散する理由

情報が分散してしまう要因はいくつかありますが、ひとつは時間の経過や案件の多様化にあわせて微妙にチーム編成も変わっていき、そしてそれらのチーム担当者が Wiki を自分流に染め上げてしまうことで、全体を見渡して見ると似たような文書がいくつか散らばってしまっている事態が起きてしまいます。

プロジェクトの立ち上げ時はチーム内で思いを共にしやすいですが、一区切りついて運用フェーズに入ると、途端に意識レベルの継承が難しくなりますね。

Redmine の Wiki は、強い信念を持って統制する人がいなければ類似の文書が分散してしまいます。加えると、Wiki のフォーマットで Markdown が使えるとはいえ、非技術型のメンバーでは必ずしも書きやすいものではありませんので、構文の習得コストも若干ながら要します。そして、この「若干」というのが、苦手とするメンバーにおいては「非常」に腰が重くなる課題となります。

SaaSでの解決もあるが

世の中インターネットサービスを用いればいくらでもドキュメント管理が楽になるツールはあるのですが(Notion のように)、残念ながらどの企業でも気楽にインターネットサービスを利用できるわけではありませんよね。情報管理の規程や、決裁権の問題など色々な理由にぶちあたります。

自身も例にもれず SaaS を気楽に使えない社風(セキュリティの審査が厳しすぎる etc)で仕事している一方、不思議なことにオンプレミスでのシステム構築と提供ならば「改善活動」として割と緩く柔軟に実行できるので、ちょうどいい OSS はないものかと探してみました。

Outline との出会い

そこで出会ったのが Outline というOSSです。単語が一般名詞すぎて、なかなか検索にヒットしなさそうな、差別化しづらいキーワードですね。

ちょっと試してみたところ、まるで Notion を使っているような気分になれる楽しいツールでした。Notionファンにとっては注目しないわけにはいきません!ということで、本記事では構築方法の一例を紹介します。

Community 版(OSS)と、有料版のサービス提供があるようです。細部は見ていませんが、お金を払うなら Notion を使いたいよなと個人的には思いますね。

Outline – Team knowledge base & wiki

本記事で説明すること

  • とりあえず Outline をローカルで起動してみるための方法の説明(前半)
  • Outline がどのようなツールかを簡単に画面キャプチャで紹介(後半)

構築には興味がなくて使い勝手を知るのが目的の場合、セットアップのセクションは読み飛ばしてください。

全体像をおさえるためのポイント

2022年8月時点の技術仕様をもとに整理しています。その後のアップデートで変化が生じているかもしれませんのでご注意願います。
  • 認証機能は持っていない
    • 何らかの外部認証が必要(Slack, Google, その他一般の OpenID Connect)
    • あるいは Magic Link でも利用できる(一時的なログイン用URLがメールで送られてくるはず)。もちろんSMTPサーバーが必要
  • バイナリ(画像等)データは S3 オブジェクトストレージへ格納する設計
  • データベースは Postgres を利用する
    • ORMとして Sequelize というライブラリを用いているようなので、MySQLでも使えるかもしれないが、Postgres固有の依存部分については未確認

セットアップ方法(テスト用のローカル環境)

とりあえず試してみたいので、いつものようにサクッとDockerで・・・と思ったのですが、Quickstart というには割とハマりどころが多くセットアップに若干時間がかかりました。

終わってみれば大したことはない話の連続なのですが、公式ガイドの説明不足感も少々感じます。(2022/08現在の感想)

設定ファイル(docker.env)の準備

公式リファレンスに Docker Compose 用の設定例一覧があります(このままでは動きません)

https://app.getoutline.com/share/770a97da-13e5-401e-9f8a-37949c19f97e/doc/docker-7pfeLP5a8t

とりあえず Outline を触ってみたいという目的でローカルで試す分には、以下の内容で動きます。オブジェクトストレージ(ここでは Minio)の設定はサボってますのでたぶん動きません。

ここでは認証プロバイダーとして OpenID Connect (GitLab) を利用しています。

# –––––––––––––––– REQUIRED ––––––––––––––––
NODE_ENV=production

# `openssl rand -hex 32` を用いてランダムキーを生成する
SECRET_KEY=c56a56a43464b0f7b1ba845db4506735cc661e75bb0d5e32388b4391c0883f86
# `openssl rand -hex 32` を用いてランダムキーを生成する
UTILS_SECRET=a503e5c7fc00fde4d90d9a9d9c25cf1ccf04fefd0b48bf0eb2e977c0526038c8

# For production point these at your databases, in development the default
# should work out of the box.
DATABASE_URL=postgres://user:pass@postgres:5432/outline
DATABASE_URL_TEST=postgres://user:pass@postgres:5432/outline-test
DATABASE_CONNECTION_POOL_MIN=
DATABASE_CONNECTION_POOL_MAX=
# Uncomment this to disable SSL for connecting to Postgres
PGSSLMODE=disable

# For redis you can either specify an ioredis compatible url like this
REDIS_URL=redis://redis:6379
# or alternatively, if you would like to provide addtional connection options,
# use a base64 encoded JSON connection option object. Refer to the ioredis documentation
# for a list of available options.
# Example: Use Redis Sentinel for high availability
# {"sentinels":[{"host":"sentinel-0","port":26379},{"host":"sentinel-1","port":26379}],"name":"mymaster"}
# REDIS_URL=ioredis://eyJzZW50aW5lbHMiOlt7Imhvc3QiOiJzZW50aW5lbC0wIiwicG9ydCI6MjYzNzl9LHsiaG9zdCI6InNlbnRpbmVsLTEiLCJwb3J0IjoyNjM3OX1dLCJuYW1lIjoibXltYXN0ZXIifQ==

# URL should point to the fully qualified, publicly accessible URL. If using a
# proxy the port in URL and PORT may be different.
URL=http://localhost:3000
PORT=3000

# See [documentation](docs/SERVICES.md) on running a separate collaboration
# server, for normal operation this does not need to be set.
COLLABORATION_URL=

# To support uploading of images for avatars and document attachments an
# s3-compatible storage must be provided. AWS S3 is recommended for redundency
# however if you want to keep all file storage local an alternative such as
# minio (https://github.com/minio/minio) can be used.

# A more detailed guide on setting up S3 is available here:
# => https://wiki.generaloutline.com/share/125de1cc-9ff6-424b-8415-0d58c809a40f
#
AWS_ACCESS_KEY_ID=get_a_key_from_aws
AWS_SECRET_ACCESS_KEY=get_the_secret_of_above_key
AWS_REGION=xx-xxxx-x
AWS_S3_ACCELERATE_URL=
AWS_S3_UPLOAD_BUCKET_URL=http://s3:4569
AWS_S3_UPLOAD_BUCKET_NAME=bucket_name_here
AWS_S3_UPLOAD_MAX_SIZE=26214400
AWS_S3_FORCE_PATH_STYLE=true
AWS_S3_ACL=private


# –––––––––––––– AUTHENTICATION ––––––––––––––

# Third party signin credentials, at least ONE OF EITHER Google, Slack,
# or Microsoft is required for a working installation or you'll have no sign-in
# options.

# To configure Slack auth, you'll need to create an Application at
# => https://api.slack.com/apps
#
# When configuring the Client ID, add a redirect URL under "OAuth & Permissions":
# https://<URL>/auth/slack.callback
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=

# To configure Google auth, you'll need to create an OAuth Client ID at
# => https://console.cloud.google.com/apis/credentials
#
# When configuring the Client ID, add an Authorized redirect URI:
# https://<URL>/auth/google.callback
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

# To configure Microsoft/Azure auth, you'll need to create an OAuth Client. See
# the guide for details on setting up your Azure App:
# => https://wiki.generaloutline.com/share/dfa77e56-d4d2-4b51-8ff8-84ea6608faa4
AZURE_CLIENT_ID=
AZURE_CLIENT_SECRET=
AZURE_RESOURCE_APP_ID=

# To configure generic OIDC auth, you'll need some kind of identity provider.
# See documentation for whichever IdP you use to acquire the following info:
# Redirect URI is https://<URL>/auth/oidc.callback
OIDC_CLIENT_ID=123456789de7d7d02d2059bb462ea22fa41171fe2b62716663b25cd166112345
OIDC_CLIENT_SECRET=12345678950f50276be776cc47995531dd927f5152f63850760a2b5032712345
#OIDC_AUTH_URI=
#OIDC_TOKEN_URI=
#OIDC_USERINFO_URI=

#GitLab
OIDC_AUTH_URI=https://gitlab.example.com/oauth/authorize
OIDC_TOKEN_URI=https://gitlab.example.com/oauth/token
OIDC_USERINFO_URI=https://gitlab.example.com/oauth/userinfo
OIDC_USERNAME_CLAIM=username
OIDC_DISPLAY_NAME=GitLab
OIDC_SCOPES=openid email

# Specify which claims to derive user information from
# Supports any valid JSON path with the JWT payload
#OIDC_USERNAME_CLAIM=preferred_username

# Display name for OIDC authentication
#OIDC_DISPLAY_NAME=OpenID

# Space separated auth scopes.
#OIDC_SCOPES=openid profile email


# –––––––––––––––– OPTIONAL ––––––––––––––––

# Base64 encoded private key and certificate for HTTPS termination. This is only
# required if you do not use an external reverse proxy. See documentation:
# https://wiki.generaloutline.com/share/1c922644-40d8-41fe-98f9-df2b67239d45
SSL_KEY=
SSL_CERT=

# If using a Cloudfront/Cloudflare distribution or similar it can be set below.
# This will cause paths to javascript, stylesheets, and images to be updated to
# the hostname defined in CDN_URL. In your CDN configuration the origin server
# should be set to the same as URL.
CDN_URL=

# Auto-redirect to https in production. The default is true but you may set to
# false if you can be sure that SSL is terminated at an external loadbalancer.
FORCE_HTTPS=false

# Have the installation check for updates by sending anonymized statistics to
# the maintainers
ENABLE_UPDATES=true

# How many processes should be spawned. As a reasonable rule divide your servers
# available memory by 512 for a rough estimate
WEB_CONCURRENCY=1

# Override the maxium size of document imports, could be required if you have
# especially large Word documents with embedded imagery
MAXIMUM_IMPORT_SIZE=5120000

# You can remove this line if your reverse proxy already logs incoming http
# requests and this ends up being duplicative
DEBUG=http

# For a complete Slack integration with search and posting to channels the
# following configs are also needed, some more details
# => https://wiki.generaloutline.com/share/be25efd1-b3ef-4450-b8e5-c4a4fc11e02a
#
SLACK_VERIFICATION_TOKEN=
SLACK_APP_ID=
SLACK_MESSAGE_ACTIONS=

# Optionally enable google analytics to track pageviews in the knowledge base
GOOGLE_ANALYTICS_ID=

# Optionally enable Sentry (sentry.io) to track errors and performance
SENTRY_DSN=

# To support sending outgoing transactional emails such as "document updated" or
# "you've been invited" you'll need to provide authentication for an SMTP server
SMTP_HOST=
SMTP_PORT=
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_FROM_EMAIL=
SMTP_REPLY_EMAIL=
SMTP_TLS_CIPHERS=
SMTP_SECURE=true

# Custom logo that displays on the authentication screen, scaled to height: 60px
# TEAM_LOGO=https://example.com/images/logo.png

# The default interface language. See translate.getoutline.com for a list of
# available language codes and their rough percentage translated.
DEFAULT_LANGUAGE=en_US

# Optionally enable rate limiter at application web server
RATE_LIMITER_ENABLED=true

# Configure default throttling paramaters for rate limiter
RATE_LIMITER_REQUESTS=5000
RATE_LIMITER_DURATION_WINDOW=60

Docker Compose ファイルの準備

注意(再掲):今回はフロント部分の起動が主な目的だったので、Storage(Minio)の設定は適当です。ファイルアップロードを動かすには追加で設定が必要だと思います。

version: "3"
services:
  outline:
    image: outlinewiki/outline
    env_file: ./docker.env
    ports:
      - "3000:3000"
    depends_on:
      - postgres
      - redis
      - storage

  redis:
    image: redis
    env_file: ./docker.env
    ports:
      - "6379:6379"
    volumes:
      - ./redis.conf:/redis.conf
    command: ["redis-server", "/redis.conf"]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 30s
      retries: 3

  postgres:
    image: postgres:14
    env_file: ./docker.env
    ports:
      - "5432:5432"
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 30s
      timeout: 20s
      retries: 3
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: outline

  storage:
    image: minio/minio
    env_file: ./docker.env
    ports:
      - "9000:9000"
    entrypoint: sh
    command: -c 'minio server /data'
    deploy:
      restart_policy:
        condition: on-failure
    volumes:
      - ./data/minio:/data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3

そして、データベース作成のコマンドとして公式リファレンスには以下の説明があるのですがこのままでは動きません。(誤記レベルなので、そのうちドキュメントも修正されると思います)

Create the database with the following command:

docker-compose run --rm outline yarn db:create --env=production-ssl-disabled  

Migrate the new database to add needed tables, indexes, etc:

docker-compose run --rm outline yarn db:migrate --env=production-ssl-disabled  

以下のようにエラーが出ます。

docker-compose run --rm outline yarn db:create --env=production-ssl-disabled
[+] Running 3/0
 ⠿ Container outline-storage-1   Running                                                                                                                                                                         0.0s
 ⠿ Container outline-redis-1     Running                                                                                                                                                                         0.0s
 ⠿ Container outline-postgres-1  Running                                                                                                                                                                         0.0s
yarn run v1.22.18
error Command "db:create" not found.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

package.jsondb:create コマンドが存在していないためです。

https://github.com/outline/outline/blob/31931fc80c765c8422a3a5cc3233d9289450e299/package.json

"db:create-migration": "sequelize migration:create",
"db:migrate": "sequelize db:migrate",
"db:rollback": "sequelize db:migrate:undo",
"db:reset": "sequelize db:drop && sequelize db:create && sequelize db:migrate",

sequelize db:create とすれば動きますが、Docker で postgres を起動した場合は既にデータベースの作成まで完了しているので、Create は無視してスキーマの作成 db:migrate の部分だけ実行します。

ほか、Outline 起動時に Postgres のSSLチェックがあるので、ローカル起動の場合には外す必要がある点も要注意です。

envファイルで PGSSLMODE を無効化しましょう

# Uncomment this to disable SSL for connecting to Postgres
PGSSLMODE=disable

https://github.com/outline/outline/blob/2a6d6f58042b477b0c175a95b554066fced6a572/server/database/sequelize.ts#L17-L23

コード的にはこのあたりの挙動

GitLabでのOIDCの例

一例として GitLab を OpenID Connect の Provider として設定した場合の画面例です。

コールバック先(認証後の戻り先)は以下のようにします。

http://localhost:3000/auth/oidc.callback

(ドメイン部分は適切に変更を。ここではローカルホストで Outline を起動している場合の設定です)

ここでは GitLab の admin 権限を有している場合の連携承認画面です。

Welcome

そして Outline の Welcome 画面へ。無事に起動できました。

Outline の画面例の紹介

ここからは簡単に画面をいくつかキャプチャします。

Collection とやら

サイドバーに追加されました。その配下は Empty となっていて、「Create a document」というボタンから推察するとコレクションという集まりの単位(カテゴリ的な考え)配下にドキュメントというテキストが集まっているように見えます。

Permisson 設定にはグループがあります。

グループを作成するとこんな感じで追加できました。特に違和感なく、直感的です。

Page公開

ナレッジ公開のためには、作成したページを共有できなければ意味がありませんよね。

共有したあとは /share/ のパス配下でURLが生成されました。まずは認証が必要な例。

次に、認証不要な例。

Publish to internet により認証なしで参照できるようになります。

もちろん、ここでの「Publish to internet」はOutlineサーバーを外部ネットワークへ公開していなければ接続できませんので、「認証なしでもページを参照できるようにする」の意味ですね。

上記の通り、ページへアクセスできました。

Integrations

見慣れたものから、見慣れないものまで様々ありますね。

Diagram は構成図を気楽に書けるので有り難いかも。

Diagram

Diagrams.net Integration – Outline – Team knowledge base & wiki

Alfred を利用している方は、Outline のコンテンツ検索が更に便利になりそうです。

Alfred Integration – Outline – Team knowledge base & wiki

設定

サイドバー下部にある個人名の「Profile」ボタンから設定画面へ移動できます。

「Team」メニューは、おそらくAdmin権限の操作メニューだと思います。下記の通り「Share Links」として外部公開しているページ一覧も見れるので、不必要に外部公開しているページがないかの確認もしやすそうですね。

試していて「これ、Notionじゃん」という印象だったのですが、Import にて Notion に対応していました。まさに意識しているようで、Zipファイルで持ってこれるようですね(NotionでのExportって試したことないのですが)

Notion の Import 画面

API

充実していそうです。

Developers – Outline – Team knowledge base & wiki

実際にAPIを叩いて確認していないのですが、Notionのように面倒くさい Block のネスト構造ではないように見えます。だとすると、使いやすいかも。(そうなると逆に様々な Integration をどのように表現しているのか気になるところ)

比較情報として Notion API の説明記事がありますので参考としてください。

Notion API からページの本文を取得するためにBlockを理解する
Notion API からページの本文を取得するためにBlockを理解する
https://fand.jp/notion/describe-notion-api-block/
Notion のページ本文をAPIで取得するために重要な Block について説明します。

Web サイトか Outline か

ガッツリとWebサイト的なイントラページを作りたい場合は(←おかしな表現ですが、要は自由なデザインをしたい場合は)、WordPressなり静的サイトジェネレーターなり、あるいは Issue 管理とあわせて Redmine & Wikiなりを用いることも選択肢になるでしょう。ただ、WebサイトやBlog的なコンテンツ管理は、記事の投稿(執筆)へのアクションが重くなりがちです。

fand.jp WordPress 関連記事 | Web勉強ログ Hatena Bookmark Counter for https://fand.jp/tags/wordpress/

fand.jp 静的サイトジェネレーター(SSG)関連記事 | Web勉強ログ Hatena Bookmark Counter for https://fand.jp/tags/%E9%9D%99%E7%9A%84%E3%82%B5%E3%82%A4%E3%83%88%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AC%E3%83%BC%E3%82%BF%E3%83%BCssg/

fand.jp Redmine 関連記事 | Web勉強ログ Hatena Bookmark Counter for https://fand.jp/tags/redmine/

一方で、Outline や Notion のようなアプリケーションはチームでのコラボレーションの強みがありますね。ちょっと執筆して、いい感じだったらPunlishして、他の人が変更を修正して・・・という一連のサイクルを気楽にまわすことができます。また、Integration にも紹介されているように様々な SaaS や OSS と連携して Outline のページを華やかにすることができますし、書き手を楽しくさせることができます。

おわりに

改めて公式リファレンスを見ると、このページそのものが Outline により公開されていますね。「Outline によりどのようなサービス(Webページ)が提供ができるのか」というひとつの目安になります。

Hosting Outline - Outline

組織やチームの成熟度合の組み合わせにより、かつバリエーションが無限にあるので一律では言い表せませんが、知識のドキュメント化が定着しておらず属人化で困っている組織も少なくないと思います。そのような組織が第一にすべきは、属人化した知識・ナレッジを書き出して共有する文化の醸成でしょう。

そして共有を促進するうえでの最初の障壁は、皆が全然書いてくれない(後回しにしてしまう)ことです。そのため執筆の障壁を少しでも下げてあげることが必要で、エディタを含む操作感などの書きやすさが非常に重要です。どんなにWebサイトの構造がキマっていても中身となるコンテンツが疎かでは話になりません。

このような問題を解決する手段として Outline は魅力的に感じました。あるいは、Notionを使いたいけど使えない(許可されない)企業でも、自社ホスティングならば大丈夫という謎の判断軸があることも珍しくありませんので😂、その場面でも有力な選択肢となりますね。

おまけ BookStack というOSS

今回のナレッジツールのためのOSS検証は、Outline のほかに BookStack を試してみるつもりでした。ただ、Outlineが個人的に大ヒットだったため BookStack の検証はやる気がなくなってしまいました。

一応、起動して軽く操作した感想だけメモしておきます。

操作概念

  • 名前の通り、「本」という単位でドキュメントを捉えていきます
  • 概念は以下の通り
    • 本棚:本を複数貯蔵する場
    • 本:チャプターやページという単位で区切りながらドキュメントの塊を作っていく
    • チャプター:本の中身の分割単位
    • ページ:コンテンツそのもの
  • ページそれぞれにコメントを記載することができる

概念的には身近なものなので捉えやすいと思います。「本」をどのようなレベル感で記載するかは、利用する組織やチームの範囲、物量によって現場に適合させたほうがいいと思います。

例えば

  • 「契約事務処理について」という本
  • 「○○部におけるAWSの利用ガイドライン」という本

といった感じでしょうか。

起動コンフィグ

GitHub に沿ってある程度起動できます。

  • APP_URL に書いたURLがHTMLの各種リンクで設定されるので、コンテナ起動ポートと一致させないとうまく動きませんのでご注意を。
  • パスワードはもちろん検証・開発用です。本番環境(Production)では適切に見直してください。
---
version: "2"
services:
  bookstack:
    # 2022/08/13時点は v22.07.3-ls31 が最新のようです
    image: lscr.io/linuxserver/bookstack
    container_name: bookstack
    environment:
      - PUID=1000
      - PGID=1000
      - APP_URL=http://127.0.0.1:6875
      - DB_HOST=bookstack_db
      - DB_USER=bookstack
      - DB_PASS=passw0rd
      - DB_DATABASE=bookstackapp
    volumes:
      - ./data2/app:/config
    ports:
      - 6875:80
    restart: unless-stopped
    depends_on:
      - bookstack_db
  bookstack_db:
    image: lscr.io/linuxserver/mariadb
    container_name: bookstack_db
    environment:
      - PUID=1000
      - PGID=1000
      - MYSQL_ROOT_PASSWORD=passw0rd
      - TZ=Asia/Tokyo
      - MYSQL_DATABASE=bookstackapp
      - MYSQL_USER=bookstack
      - MYSQL_PASSWORD=passw0rd
    volumes:
      - ./data2/mysql:/config
    restart: unless-stopped