本文へ移動
購読
GUIDE#saas更新 2026.10.02

Mattermostで社内チャットをセルフホストする:Slackとの違いと導入手順

Mattermost 11.11をDocker Composeで構築し、mmctlでの初期設定、日本語化と日本語検索の実際の挙動、Webhook連携、無償版の制限までを検証した手順です。

level
中級
time
約45分
stack
Docker Compose / Mattermost Team Edition 11.11.0 / PostgreSQL 17
author
@ryoma_t
// 前提条件
Docker と docker compose が使えるLinuxサーバー / HTTPSで公開するためのリバースプロキシとDNS
Mattermostの情シス窓口チャンネルに、Incoming Webhookのディスク使用率アラートとtaroの返信が並んだ画面
画像:nagoya.dev(実際の画面・実行結果をもとに作成)

Mattermost(マターモスト)とは、Slackに近い操作感のチームチャットを、自社のサーバーで運用できるオープンソースのメッセージング基盤です。チャンネル、スレッド、ファイル共有、Webhookによる外部連携といった基本機能を、無償のTeam Editionでも使えます。

ここではMattermost Team Edition 11.11.0(執筆時点の最新版)をPostgreSQL 17と組み合わせてDocker Composeで起動し、mmctl での初期設定、日本語表示と日本語検索の実際の挙動、監視アラートを流すIncoming Webhook、バックアップまでを検証した結果にもとづいて手順化しています。無償版で使えない機能も、画面や設定値で確かめた範囲で明記します。

この記事でわかること

  • MattermostとSlackの違いと、無償のTeam Editionで使えない機能(SSO、MFAの強制など)
  • UID 2000の権限に注意したcompose.yamlと、mmctlのローカルモードでの初期設定
  • 日本語化の設定と、日本語検索で見つかる語・見つからない語の実例
  • Incoming Webhookで監視アラートを投稿する方法と、HTTPS化・モバイル通知の注意点
  • Rocket.Chatとの比較、バックアップと更新の手順

Mattermostとは:Slackとの違い

Mattermostは米国のMattermost社が開発しているチャットツールで、ソースコードを公開した無償のTeam Editionと、有償ライセンスで機能が加わるエディションがあります。サーバーはGo言語で書かれた1つのバイナリで、PostgreSQLにメッセージを保存し、添付ファイルはローカルディスクかS3互換ストレージに置きます。

Slackとの違いは、機能の差よりも「誰がデータと運用を持つか」にあります。情シスとして比較すると、次のような構図になります。

観点Slack(SaaS)Mattermost Team Edition(セルフホスト)
データの置き場所Slackのクラウド自社のサーバー・DB
ログイン方式メール、Google、上位プランでSAML SSOメール+パスワード(SSOは有償エディション)
社外の組織とのやり取りSlack Connectで共有チャンネル別の仕組み(ゲストや共有チャンネルは有償機能)
外部連携アプリ、ワークフロー、WebhookWebhook、スラッシュコマンド、プラグイン、REST API
モバイル通知Slackが提供Mattermost社のプッシュ通知サーバーを経由
運用の負担ほぼベンダー側更新、バックアップ、証明書、監視まで自社

セルフホストを選ぶ理由は、閉域網や工場のようにインターネットへの常時接続が難しい拠点がある、メッセージを社外のクラウドに置けない契約や規程がある、ユーザー数に比例する費用を抑えたい、のいずれかであることがほとんどです。逆に、取引先とのやり取りが多い、SSOとアカウントの自動削除を必須にしたい、運用担当が1人しかいない、という条件なら、SaaSのチャットのほうが総コストは下がります。SaaS同士の比較はSlack・Teams・Chatworkの選び方で整理しています。

無償版(Team Edition)でできること・できないこと

11.11.0のTeam Editionで設定値とAPIの応答を確認したところ、次のような線引きでした。

  • ログインはメールアドレス(またはユーザー名)とパスワード。GitLabのSSOを有効にする設定値を入れても、クライアント向けの設定にGitLabのログイン項目は現れず、SAML・OpenID Connect・AD/LDAPも無効でした
  • 多要素認証(TOTP)は、EnableMultifactorAuthentication を有効にすれば各ユーザーが設定できます。一方で全員に強制する EnforceMultifactorAuthentication は、設定値を変えてもクライアント側では無効のままで、ライセンスが必要です
  • Incoming・Outgoing Webhookは既定で有効、個人用アクセストークンは既定で無効
  • 旧バージョンにあった全文検索エンジンBleveの設定項目(BleveSettings)は存在せず、検索はデータベース検索(ActiveSearchBackend: database)で動きます

実務上いちばん効くのはSSOがないことです。退職者が出たら、Entra IDやKeycloakでアカウントを止めても、Mattermostのアカウントは別に止めなければなりません。Team Editionで運用するなら、退職処理の手順書に mmctl user deactivate を必ず入れておきます。保存期間の自動削除やコンプライアンス用のエクスポートも有償エディションの機能なので、「チャットの保存期間を規程で決めている」会社は、最初から有償版を前提に比較してください(価格と機能の対応は公式のエディション比較で確認してください)。

Docker Composeで起動する

設定・データ・ログなどをホストのディレクトリに置く構成です。コンテナ内のMattermostはroot以外のユーザー(UID 2000)で動くため、ディレクトリの所有者を先に合わせておきます。

terminal
mkdir -p ~/mattermost && cd ~/mattermost
mkdir -p volumes/{config,data,logs,plugins,client-plugins}
sudo chown -R 2000:2000 volumes
printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 16)" > .env
chmod 600 .env
compose.yaml
services:
  postgres:
    image: postgres:17-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: mmuser
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: mattermost
    volumes:
      - mm_pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U mmuser -d mattermost"]
      interval: 5s
      retries: 10

  mattermost:
    image: mattermost/mattermost-team-edition:11.11.0
    restart: unless-stopped
    ports:
      - "127.0.0.1:8065:8065"
    environment:
      TZ: Asia/Tokyo
      MM_SQLSETTINGS_DRIVERNAME: postgres
      MM_SQLSETTINGS_DATASOURCE: postgres://mmuser:${POSTGRES_PASSWORD}@postgres:5432/mattermost?sslmode=disable&connect_timeout=10
      MM_SERVICESETTINGS_SITEURL: https://chat.example.co.jp
    volumes:
      - ./volumes/config:/mattermost/config
      - ./volumes/data:/mattermost/data
      - ./volumes/logs:/mattermost/logs
      - ./volumes/plugins:/mattermost/plugins
      - ./volumes/client-plugins:/mattermost/client/plugins
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  mm_pgdata:

MM_ で始まる環境変数は config.json の設定を上書きし、上書きした項目はSystem Console(管理画面)では変更できなくなります。DB接続とサイトURLのように「画面から変えられては困る値」だけを環境変数に置き、それ以外は画面か mmctl で管理するのが扱いやすい分担です。MM_SERVICESETTINGS_SITEURL は利用者がブラウザーで開くURLそのもので、ローカルで試すだけなら http://localhost:8065 にします。

terminal
docker compose up -d
curl -s http://127.0.0.1:8065/api/v4/system/ping
docker compose exec mattermost mmctl --local system status

"status":"OK" と、mmctl の「Server status: OK」「Database Status: OK」「Filestore Status: OK」が出れば起動しています。検証では起動から15秒ほどで応答が返りました。利用者が数人の段階で、メモリ使用量はMattermostが約440MB、PostgreSQLが約77MBでした。

公式イメージは、コンテナ内のソケット経由で管理コマンドを実行できる「ローカルモード」を既定で有効にしています(イメージの環境変数 MM_SERVICESETTINGS_ENABLELOCALMODE=true)。mmctl --local はパスワードもトークンも使わずにシステム管理者の操作ができるため、docker compose exec を実行できる人は実質的にMattermostの全権を持ちます。Dockerを操作できるアカウントは、情シスの管理者に限定してください。

初期設定:管理者・チーム・ユーザーを作る

最初の管理者、チーム、ユーザー、チャンネルを mmctl で作ります。ブラウザーで最初にアクセスした人が管理者になる初期画面を、第三者に先に開かれる心配がなくなります。

terminal
mm() { docker compose exec -T mattermost mmctl --local "$@"; }
mm user create --email [email protected] --username sysadmin --password '初回だけ使うパスワード' --system-admin
mm team create --name corp --display-name "社内"
mm user create --email [email protected] --username taro --password '初回だけ使うパスワード' --firstname 太郎 --lastname 山田
mm team users add corp sysadmin taro
mm channel create --team corp --name it-helpdesk --display-name "情シス窓口"
mm channel users add corp:it-helpdesk sysadmin taro

検証では「Created user sysadmin」「New team corp successfully created」のように1行ずつ結果が返り、mm channel list corp で既定の town-square、off-topic と作成した it-helpdesk の3つが並びました。既定値では誰でも参加できる公開サーバーではなく招待制(TeamSettings.EnableOpenServer: false)になっていますが、メールでの新規登録(EmailSettings.EnableSignUpWithEmail)は有効です。招待リンクは転送されると誰でも使えてしまうため、アカウントを作成できるメールドメインを example.co.jp に制限しておきます(設定名は TeamSettings.RestrictCreationToDomains)。リンクが社外に漏れた場合は、チームの設定で招待IDを再生成すると古いリンクは無効になります。

多要素認証は mm config set ServiceSettings.EnableMultifactorAuthentication true で有効にし、利用者に各自の「プロフィール → セキュリティ」から設定してもらいます。無償版では強制できないため、システム管理者のアカウントだけは設定済みかを定期的に確認します。

日本語で使う:表示言語と検索の癖

既定の表示言語は英語(DefaultClientLocale: "en")です。サーバー側とクライアント側の既定を日本語にします。

terminal
mm config set LocalizationSettings.DefaultServerLocale ja
mm config set LocalizationSettings.DefaultClientLocale ja

ここで注意したいのは、既定の言語は「これから作るユーザー」にしか効かないことです。検証でも、設定前に作ったユーザー(検証ではtaro)は locale: en のまま、設定後に作ったユーザーは ja になりました。既存のユーザーには「設定 → 表示 → 言語」で本人に切り替えてもらうか、案内を出します。

日本語検索は、PostgreSQLの標準構成でも多くの場合は機能します。検証チャンネルに7件のメッセージを投稿し、APIで検索した結果のうち、傾向がわかるものを表にしました。

投稿したメッセージ検索語結果
明日の定例会議は10時からです会議 / 定例会議見つかる
VPNの接続手順を更新しました接続手順見つかる
VPNの接続手順を更新しましたVPN見つからない(VPN* なら見つかる)
会議室の予約はkintoneでお願いします予約見つかる
会議室の予約はkintoneでお願いしますkintone見つからない
明日 kintone の研修がありますkintone見つかる
Slack連携の設定を確認Slack見つからない(Slack* なら見つかる)

日本語の語句は文の途中にあっても見つかる一方で、英数字の語は前後が空白で区切られていないと単語として扱われず、完全一致では見つかりません。語の先頭にある場合だけ、末尾に * を付けた前方一致で拾えます。製品名やホスト名で検索することが多いチャンネルでは、「英数字の語の前後に半角スペースを入れる」という書き方のルールを決めておくと、検索漏れが目に見えて減ります。有償エディションではElasticsearchなどの外部検索エンジンを使う選択肢もあります。

他システムと連携する:Incoming Webhook

監視ツールやn8nなどからの通知は、Incoming Webhookで特定のチャンネルに流すのが簡単です。

terminal
HOOK_ID=$(mm webhook create-incoming --channel corp:it-helpdesk --user sysadmin --display-name "監視通知" --lock-to-channel | awk '/^Id:/ {print $2}')
curl -s -X POST -H 'Content-Type: application/json' \
  -d '{"text":"#### ディスク使用率アラート\nsrv-file01 の /data が 91% です"}' \
  "https://chat.example.co.jp/hooks/$HOOK_ID"

投稿に成功すると ok が返り、本文はMarkdownとして表示されます。--lock-to-channel を付けておくと、送信側がJSONの channel を書き換えて別のチャンネルに投稿することを防げます。送信側のJSONに username を入れても、既定では投稿者名の上書きが無効(ServiceSettings.EnablePostUsernameOverride: false)なので、作成したユーザーの名前で表示されます。WebhookのURLを知っている人は誰でも投稿できるため、URLはパスワードと同じ扱いで保管してください。定期的な集計や申請の受け付けまで自動化するなら、n8nでのワークフロー自動化にMattermostノードがあります。

HTTPS化とモバイルアプリ

社内の他のPCやスマホから使うには、リバースプロキシでHTTPSにします。ブラウザーやデスクトップアプリは /api/v4/websocket へのWebSocketでリアルタイムに新着を受け取るので、ここでUpgradeヘッダーを通さないと、画面を再読み込みしないと新しいメッセージが出ない状態になります。アップロードの上限は既定で100MB(FileSettings.MaxFileSize: 104857600)なので、プロキシ側の client_max_body_size もそれ以上にしておきます。

/etc/nginx/conf.d/mattermost.conf(抜粋)
server {
    listen 443 ssl;
    server_name chat.example.co.jp;
    # ssl_certificate / ssl_certificate_key は環境に合わせて設定
    client_max_body_size 100m;

    location /api/v4/websocket {
        proxy_pass http://127.0.0.1:8065;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 600s;
    }

    location / {
        proxy_pass http://127.0.0.1:8065;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

公式のモバイルアプリは、有効な証明書のHTTPSで公開されたサーバーにつなぐ前提です。スマホへのプッシュ通知は、自社サーバーから直接ではなく、Mattermost社のプッシュ通知サーバーを経由して各OSの通知基盤に届きます。バイナリには本番用の https://push.mattermost.com と、テスト用の https://push-test.mattermost.com の2つの宛先が含まれており、初期状態ではプッシュ通知自体が無効(EmailSettings.SendPushNotifications: false)でした。無償版で使えるのはテスト用のサーバー(SLAなし)で、本番用は有償ライセンスが前提です(執筆時点)。

プッシュ通知を有効にすると、通知の文面はMattermost社のサーバーと各OSの通知基盤を通ります。文面に何を含めるかは EmailSettings.PushNotificationContents で選べ、generic_no_channel にすると送信者名だけの通知になり、本文もチャンネル名も社外を通りません。スマホ側が受信後に自社サーバーから本文を取りに行く id_loaded は有償機能です(執筆時点)。取引先名や障害内容が通知に載ること自体が問題になる職場では、有効化の前にこの値を決めておきます。

運用:バックアップと更新

バックアップの対象は、PostgreSQL(メッセージ・ユーザー・設定の大半)と、volumes/data(添付ファイル)、volumes/config(config.json)の3つです。

terminal
docker compose exec postgres pg_dump -U mmuser -d mattermost -Fc -f /tmp/mattermost.dump
docker compose cp postgres:/tmp/mattermost.dump ./backup/mattermost-$(date +%F).dump
sudo tar czf ./backup/mattermost-files-$(date +%F).tgz volumes/config volumes/data

DBのダンプと添付ファイルは同じ時点でそろっている必要があるため、夜間にまとめて取得します。config.json にはDBの接続情報や各種の鍵が入るので、バックアップ先のアクセス権も絞ります。

更新はイメージのタグを上げるだけで、起動時にDBのマイグレーションが自動で走ります。新しい版は毎月のように出るため、更新の頻度を下げたい企業では、長期サポート版(ESR)を選んで次のESRまで待つ運用が向いています。どの版がESRかはリリースノートで確認し、上げる前にDBをダンプし、プラグインの対応状況を確かめてから本番に適用します。

Rocket.Chatとの比較

同じセルフホスト型の選択肢として、Rocket.Chatもよく比較されます。どちらもWebhook、REST API、モバイルアプリを備えていますが、得意分野が違います。

観点MattermostRocket.Chat
データベースPostgreSQLMongoDB
得意な使い方開発・運用チームの連絡、障害対応、チャットからの操作社外向けのライブチャットや問い合わせ窓口(オムニチャネル)
他組織との接続有償エディションの共有チャンネルMatrixプロトコルとのフェデレーション
無償版の条件機能制限あり(SSO、MFA強制など)利用規模や機能に条件あり(執筆時点、公式で確認)

社内の情シス・開発・運用の連絡に使うなら、PostgreSQLで運用できるMattermostのほうが、既存のDB運用やバックアップの手順を流用しやすいと考えます。顧客対応の窓口を同じ基盤で持ちたい場合や、Matrixで他組織とつなぎたい場合はRocket.Chatが候補になります。

動作確認

構築と設定が終わったら、次の順で確認します。

terminal
curl -s http://127.0.0.1:8065/api/v4/system/ping
docker compose exec mattermost mmctl --local system status
mm config get LocalizationSettings.DefaultClientLocale
curl -s -X POST -H 'Content-Type: application/json' -d '{"text":"疎通確認"}' "http://127.0.0.1:8065/hooks/$HOOK_ID"

1つ目で "status":"OK" と "ActiveSearchBackend":"database"、2つ目で3つのOK、3つ目で "ja"、4つ目で ok が返れば、サーバー・DB・言語設定・連携の経路がそろっています。最後にブラウザーで一般ユーザー(検証ではtaro)としてログインし、情シス窓口チャンネルに「疎通確認」の投稿が表示され、「会議」のような日本語の語で過去の投稿を検索できることを確かめます。

つまずきポイント

コンテナが再起動を繰り返し、「could not create config file: open /mattermost/config/config.json: permission denied」が出る

ホスト側のディレクトリがrootの所有のままバインドマウントしたときに出ます。ログの全文は「Error: failed to load configuration: could not create config file: open /mattermost/config/config.json: permission denied」でした。コンテナ内のユーザーはUID 2000(/etc/passwd に mattermost:x:2000:2000)なので、sudo chown -R 2000:2000 volumes を実行してから docker compose up -d --force-recreate で作り直します。

言語の設定で「Available Languages must contain Default Client Language.」と出る

LocalizationSettings.AvailableLocales に、既定の言語を含まない一覧を入れたときの入力チェックです。空欄(すべての言語を許可)にするか、ja を含めます。逆に、既定を英語のまま AvailableLocales を en だけにすると設定は通ってしまい、利用者の言語メニューから日本語が消えます。「日本語化できない」という問い合わせは、まずこの値を確認します。

既定の言語を日本語にしたのに、自分の画面が英語のまま

既定の言語は新規ユーザーにしか適用されないためです。検証でも、設定前に作ったユーザーは英語のままでした。本人が「Settings → Display → Language」で日本語を選ぶと切り替わります。言語は各ユーザーの個人設定なので、導入初日の案内に切り替え手順を入れておくと問い合わせが減ります。

英単語で検索しても投稿が見つからない

「会議室の予約はkintoneでお願いします」のように、英数字の語が日本語に挟まれていると、その語だけでは検索できません。前述の表のとおり、語の先頭なら Slack* のような前方一致で拾えますが、文の途中にある場合は見つかりません。運用ルールで前後に空白を入れるか、日本語の語を組み合わせて検索します。

よくある質問

SlackとMattermostの違いは何ですか?

操作感は近いものの、SlackはSaaSでデータと運用をベンダーが持ち、Mattermostは自社サーバーでデータと運用を自分で持ちます。無償のTeam EditionにはSSOやMFAの強制がなく、社外組織との共有チャンネルも有償機能です。閉域網での利用やデータの保管場所に制約がある場合にMattermostが向きます。

Mattermostの料金はいくらですか?

Team Editionは無償で、サーバー・ストレージ・運用の費用だけがかかります。SSO、MFAの強制、保存期間の管理、ゲストなどが必要な場合は、ユーザー数に応じた有償エディションの契約になります。価格とエディションごとの機能は改定されることがあるため、公式サイトの料金ページで最新の内容を確認してください。

Mattermostを日本語化できないのはなぜですか?

多くは3つのどれかです。既定の言語を日本語にしても既存ユーザーには効かないこと、管理者が AvailableLocales で利用できる言語を絞っていること、そして本人の表示設定が英語のままであることです。ユーザーの「設定 → 表示 → 言語」で日本語を選べば切り替わります。

Mattermostで日本語検索するにはどうすればいいですか?

PostgreSQLの標準構成でも、日本語の語句は文の途中にあっても検索できました。見つかりにくいのは日本語に挟まれた英数字の語で、語の先頭にあるなら Slack* のように末尾へ * を付けます。英数字の前後に半角スペースを入れる書き方を社内ルールにすると検索漏れが減ります。

まとめ

MattermostのTeam Editionは、PostgreSQLと一緒にDocker Composeで起動し、UID 2000に合わせたディレクトリと mmctl --local での初期設定から始めます。SSOとMFAの強制がないことを前提に、退職時の無効化手順と登録ドメインの制限を先に決めてください。日本語は既存ユーザーの言語設定と、英数字の検索の癖に注意します。通知はIncoming Webhookで流し、HTTPS化の際はWebSocketとアップロード上限をプロキシ側で合わせます。

次に読む

$ subscribe --weekly
毎週月曜、企業ITとホームラボの要点を5分で。