SSD Nodes Learn 🎉 VPS $5.50/月〜
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-13

Plankaをセルフホストする方法 Docker Compose VPS構築

PlankaをVPSへDocker Composeで導入する手順です。Postgres、Traefik、管理者初期設定用変数に加え、ログインできなくなるBASE_URLの設定ミスも確認できます。

セルフホスティングで Planka を導入すると得られるもの

Planka をセルフホスティングすると、チームは Trello ですでに知られているカード、リスト、ラベルのモデルを備えた Kanban ボードを、管理下の VPS で運用できます。ユーザー数の上限やユーザー単位の課金はありません。費用はサーバー代だけです。このガイドでは、データに Postgres を使用し、ユーザーがアップロードするすべてのファイルを named volume に保存して、Traefik の背後で Docker Compose によりデプロイします。

対象読者は、Trello の無料プランから移行する 2 〜 5 人のチームです。どのボードを運用するかまだ決めていない場合は、先にセルフホスト可能な Trello 代替製品の比較を読んでください。このガイドでは選択がすでに完了していることを前提とし、デプロイだけを扱います。

Compose plugin を備えた Docker Engine が動作する VPS と、その VPS を指す DNS A レコードが必要です。また、そのサーバー上ですでに TLS (transport layer security) を終端している Traefik インスタンスも必要です。Traefik がまだない場合は、先に複数の Compose アプリの前段に Traefik reverse proxy を構成するを実施してください。以下のファイルの内容が分かりにくい場合は、VPS 向け Docker Compose の基礎も読んでください。

Planka に必要な VPS の容量はどの程度ですか?

プロジェクトはハードウェアの最低要件を公開していません。そのため、見かけた数値は測定値ではなく、出発点として扱ってください。ホスティングサービスのページで繰り返し示される 2 vCPU と 4 GB は、プロバイダーが余裕を持たせた標準値であり、プロジェクトが測定した必須要件ではありません。5 人が利用するボードには十分すぎる構成です。

実際に動作する構成は小規模です。API とビルド済みフロントエンドを提供する Node.js プロセスが 1 つ、データを保持する Postgres プロセスが 1 つ動作します。さらに、Planka コンテナ内では、外向きリクエストをフィルタリングする小規模なプロキシプロセスが 1 つ動作します。1 vCPU と 2 GB のプランで、2 人から 5 人が利用するボードを運用できます。余ったメモリの大部分は Postgres のキャッシュに使われます。

メモリより先にディスク容量を決めてください。増加するのは主に添付ファイルだからです。この段落をそのまま信頼せず、自分のインスタンスを測定してください。

docker stats --no-stream
docker system df -v

1 つ目のコマンドは、コンテナごとの現在のメモリ使用量と CPU 使用率を表示します。2 つ目のコマンドは、各ボリュームが使用している容量を表示します。両方とも、インストール直後ではなく、通常の作業を 1 週間行った後に測定してください。アイドル状態のボードでは、チームの実際の使用状況は分かりません。

Compose ファイルを作成する

ディレクトリを作成して所有権を変更します。これで、sudo を通さずにこれらのファイルを編集できます。

sudo mkdir -p /opt/planka
sudo chown "$USER":"$USER" /opt/planka
cd /opt/planka

Compose ファイルの隣にある .env ファイルへ Secret を生成します。Compose はこのファイルを自動的に読み込み、値を置換します。

umask 077
{
  printf 'SECRET_KEY=%s\n' "$(openssl rand -hex 64)"
  printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)"
  printf 'ADMIN_PASSWORD=%s\n' "$(openssl rand -hex 12)"
} > .env
chmod 600 .env

openssl rand -hex には意図があります。16 進数文字列に含まれるのは数字と a から f までの英字だけです。そのため、貼り付け先の DATABASE_URL 接続文字列を壊しません。スラッシュや at 記号を含む base64 パスワードを使うと、ホスト名が間違っているように見える接続エラーが発生し、原因の特定に 1 時間かかることがあります。より広い対策については、Compose ファイルから Secret を除外する を参照してください。

次に docker-compose.yml を実行します。kanban.example.com を、出現する 2 か所すべてで自分のホスト名に置き換えます。

services:
  planka:
    image: ghcr.io/plankanban/planka:2.1.1
    restart: unless-stopped
    volumes:
      - planka-data:/app/data
    environment:
      - BASE_URL=https://kanban.example.com
      - DATABASE_URL=postgresql://planka:${POSTGRES_PASSWORD}@postgres/planka
      - SECRET_KEY=${SECRET_KEY}
      - TRUST_PROXY=true
      - DEFAULT_ADMIN_EMAIL=you@example.com
      - DEFAULT_ADMIN_PASSWORD=${ADMIN_PASSWORD}
      - DEFAULT_ADMIN_NAME=Your Name
      - DEFAULT_ADMIN_USERNAME=admin
    networks:
      - proxy
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.planka.rule=Host(`kanban.example.com`)"
      - "traefik.http.routers.planka.entrypoints=websecure"
      - "traefik.http.routers.planka.tls.certresolver=default"
      - "traefik.http.services.planka.loadbalancer.server.port=1337"
    depends_on:
      postgres:
        condition: service_healthy

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

volumes:
  planka-data:
  db-data:

networks:
  proxy:
    external: true
  internal:

このファイルには、説明しておくべき重要な判断が 4 つあります。変更した後で問題になるのは、主にこの 4 つです。

  • Planka サービスには ports: ブロックがありません。Traefik は proxy ネットワーク経由でコンテナに接続するため、ポート 1337 はホスト上で公開されません。公開すると、プロキシと証明書を迂回する経路を誰でも利用できるようになります。
  • loadbalancer.server.port=1337 はコンテナ内のポートを指定します。Planka は 1337 で待ち受けます。上流の例が 3000 でのみ接続できるのは、そのポートをホストにマッピングしているためです。ここではホストへのマッピングがないため、Traefik にコンテナのポートを指定する必要があります。
  • condition: service_healthy は Postgres の healthcheck と組み合わせて使用します。これがないと、データベースが接続を受け付ける前に Planka が起動します。最初のクエリに失敗して終了するため、クラッシュループのように見えます。仕組みについては、Compose の healthcheck と起動順序 を参照してください。
  • データベースサービスを postgres という名前にしているのには理由があります。Planka 2 は、内部フィルターを通して自身の送信リクエストを処理します。このフィルターのデフォルトのブロックリストは localhost,postgres です。サービス名を変更すると、そのリストからデータベースが気付かないうちに除外されます。

何も起動する前に、Compose が Secret を読み取れることを確認します。

docker compose config | grep -E 'image:|BASE_URL|POSTGRES_USER'

これにより、.env の値がすでに置換されたファイルが表示されます。値が空の場合、Compose が .env ファイルを読み取っていません。通常は、別のディレクトリからコマンドを実行していることが原因です。

管理者ブートストラップ変数の実際の動作

Planka 1.13 以降では、管理者は自動作成されません。そのため、新しいデータベースにはログインできるユーザーが存在しません。DEFAULT_ADMIN_* グループは、この問題を解決する2つの方法の1つです。

起動時に、Planka は DEFAULT_ADMIN_EMAIL に一致するユーザーを探します。該当するユーザーがいない場合は、同じグループで指定されたパスワード、表示名、ユーザー名を使ってユーザーを作成します。これは空のデータベースに対する初回起動時に実行されます。そのため、これらの変数はアカウントを管理するものではなく、アカウントを初期作成するためのものです。

DEFAULT_ADMIN_EMAIL には、見落としやすいもう1つの役割があります。この変数が設定されている間、指定されたアカウントは、誰であってもインターフェースから編集または削除できません。これはロックアウト防止策です。そのため、UI からこのアカウントの名前変更やメールアドレスの変更もできません。変数を削除して再起動すると、そのアカウントは通常の管理者になり、他のアカウントと同じように編集できます。

パスワードの行には特に注意が必要です。environment: 以下の内容は、コンテナ上で docker inspect を実行できる人なら誰でも読み取れます。そのため、DEFAULT_ADMIN_PASSWORD をそこに永続的に保存してはいけません。ログインしてインターフェースでパスワードを変更し、その行を削除してから、もう一度 docker compose up -d を実行してください。

より安全な方法では、変数を完全に使いません。DEFAULT_ADMIN_* グループ全体をコメントアウトし、対話形式でアカウントを作成します。

docker compose run --rm planka npm run db:create-admin-user

メールアドレス、パスワード、表示名、任意のユーザー名の入力を求められ、ユーザーがデータベースに直接書き込まれます。パスワードが Compose ファイルやコンテナの環境変数に保存されることはありません。VPS にシェルアクセスできる人が複数いる場合は、この方法を使用してください。depends_on のため、このコマンドは最初に Postgres を起動します。そのため、まだ一度も起動していないスタックでも動作します。

どちらの方法でも、Planka のパスワードは手動で管理する必要があります。チームが収集する認証情報が4組目になる場合は、代わりに 自分でシングルサインオン サーバーとして運用する Authentik などの OIDC プロバイダーにログインを委任できます。プロバイダーが停止した場合に備え、ブートストラップ管理者は緊急用アカウントとして残しておきます。

ホスト名と一致しない場合に BASE_URL がログインを壊す理由

BASE_URL は、スキームを含み、末尾にスラッシュを付けずに、ユーザーがブラウザーへ入力する正確なアドレスです。このスタックでは https://kanban.example.com です。Planka はこの値から独自のリンクと WebSocket 接続を生成するため、BASE_URL が誤っていても明確なエラーにはなりません。ページは読み込まれますが、その後いつまでも読み込みが完了しない状態になります。

よくあるケースは、上流の例をコピーしたまま BASE_URL=http://localhost:3000 を残し、実際のドメインで HTTPS 経由でサイトへアクセスすることです。ログインフォームは送信され、認証情報も受け付けられます。しかし、ボードは表示されません。ブラウザーの開発者コンソールを開くと、/socket.io/ へのリクエストが失敗していることを確認できます。クライアントはライブ接続を localhost:3000 へ開くよう指示されていますが、ノートパソコン上ではそのアドレスが存在しないためです。

TRUST_PROXY=true は同じ問題のもう一方の要素です。Planka は Traefik の背後にあるため、Docker ネットワーク内では、すべてのリクエストがプロキシのアドレスから平文の HTTP で到達します。TRUST_PROXY がない場合、アプリは Traefik が設定する X-Forwarded-ProtoX-Forwarded-For のヘッダーを無視します。そのため、接続は安全ではないと判断され、すべてのクライアントが 1 つの共有 IP アドレスとして扱われます。設定すると、アプリはこれらのヘッダーを読み取り、ブラウザーとスキームを一致させます。

Traefik は追加設定なしで WebSocket をプロキシします。ここで Traefik を推奨する理由の 1 つです。nginx では、socket.io に proxy_set_header Upgrade $http_upgradeproxy_set_header Connection "upgrade" を含む専用の location ブロックが必要です。これがないと、別の原因で同じ読み込み中のスピナーが表示されます。

後からボードを新しいホスト名へ移行する場合は、BASE_URL の値と Traefik の Host() ルールを同時に変更します。一方だけを変更してもう一方を忘れると、再びスピナーが表示されます。https://example.com/planka のようなサブパスからの Planka の配信は、2026 年 3 月にリリースされたバージョン 2.1.0 以降で機能します。古いタグでは、専用のサブドメインを割り当ててください。

Planka が添付ファイルとアバターを保存する場所

Planka 2 では、ユーザーがアップロードしたすべてのファイルを、コンテナ内の 1 つのパス /app/data に保存します。添付ファイル、ユーザーアバター、ボードの背景画像はすべてこのパスの下にあります。Version 1 では 3 つのディレクトリに分かれていたため、古い記事からコピーした Compose ファイルでは、存在しなくなったパスをマウントすることになります。その結果、実際のデータディレクトリがマウントされません。

この単一のマウントが、アップグレード後もボードを維持できるか、障害対応に追われるかを分けます。/app/data がボリューム上にない場合、アップロードしたファイルはコンテナの書き込み可能レイヤーに保存されます。このレイヤーはコンテナを再作成すると削除されます。イメージタグを変更するたびに、コンテナは再作成されます。ボードは正常に表示され、カードもすべて残ります。しかし、データベースの行が存在しないファイルを参照し続けるため、添付ファイルへのリンクはすべて無効になります。

上記の Compose ファイルにある名前付きボリュームで、この問題を防げます。バインドマウントも使用でき、通常のツールでファイルをバックアップしやすくなります。ただし、追加の手順が 1 つ必要です。コンテナ内の Node プロセスは UID 1000 で実行されるため、ホスト側のディレクトリを root 所有にすると、最初のアップロードで権限エラーが発生します。

sudo chown -R 1000:1000 /opt/planka/data

この 2 つの方式の違いについては、バインドマウントと名前付きボリュームの比較で説明しています。

利用しているプランのディスク容量を添付ファイルが超える場合、Planka は S3_ENDPOINTS3_BUCKET および対応する key 変数を使って、S3 互換ストレージに保存できます。ホスト型のバケットや、別のサーバー上にあるセルフホストの MinIO オブジェクトストアを指定できます。この設定は新しいアップロードに適用されるため、チームがボードを使い始める前に決めてください。

スタックを起動し、正常に動作したことを確認する

docker compose pull
docker compose up -d
docker compose ps

docker compose psには、postgreshealthyとして、plankarunningとして表示されます。Planka がループ状態で再起動している場合は、アプリではなく、最初にデータベース接続を確認します。

docker compose logs -f planka

正常な初回起動では、データベースのマイグレーションが実行され、その後、サーバーがポート 1337 で待ち受けていることが報告されます。ログだけを信用せず、Postgres に直接問い合わせてスキーマが実際に作成されたことを確認します。

docker compose exec postgres psql -U planka -d planka -c '\dt'

boardcardを含むテーブル一覧が表示されれば、マイグレーションは実行済みです。「Did not find any relations」と表示される場合、Planka はデータベースに接続できていません。DATABASE_URLを、.env内のPOSTGRES_USERおよびPOSTGRES_PASSWORDの値と比較します。

次に、VPS ではなく、自分のマシンからルートを確認します。

curl -I https://kanban.example.com

HTTP/2 200は、Traefik が証明書を保持し、コンテナに到達できていることを示します。Traefik が 404 を返す場合、ルーターのラベルが一致していません。最も多い原因は、コンテナがproxyネットワークに接続されていないことです。サイトを開き、admin アカウントでログインします。

すべてのバージョン更新前に pg_dump を取得する

ボードのデータは2つのストアに分かれて保存されるため、バックアップでは Postgres データベースと planka-data ボリュームの両方を対象にする必要があります。スタックの稼働中にデータベースをダンプします。

docker compose exec -T postgres pg_dump -U planka -d planka > "planka-db-$(date +%F).sql"

-T は必須です。これを指定しないと Compose が擬似端末を割り当て、端末層がストリーム内の改行コードを書き換えます。その結果、リストアの途中で失敗するダンプファイルが生成されます。障害に気付くのが数週間後になることもあり、最悪のタイミングで問題が発生します。

次にアップロードデータをバックアップします。Compose はプロジェクトのディレクトリ名を付加するため、まず実際のボリューム名を確認してください。

docker volume ls | grep planka
docker run --rm -v planka_planka-data:/data -v "$PWD":/backup alpine \
  tar czf /backup/planka-files-$(date +%F).tgz -C /data .

このプロジェクトのリポジトリには docker-backup.shdocker-restore.sh も含まれており、公式ドキュメントでは nightly cron job への登録が案内されています。どちらの方法でも問題ありません。ただし、リストアしたことのないバックアップは不十分です。1度は scratch VPS にリストアし、ログインして添付ファイルを開けることを確認してください。

すべてのバージョン変更の直前にダンプを実行します。昨夜取得したバックアップは、これから実行するマイグレーションの直前に取得したバックアップとは異なります。

タグを固定し、リリースノートを確認する

このファイルの2つのイメージタグは、意図的に固定しています。

ghcr.io/plankanban/planka:2.1.1は、2026年8月時点の特定のリリースです。latestは upstream が公開するたびに変わるため、通常の docker compose pull によって、選択していないタイミングでスキーマ移行が実行される可能性があります。その番号を変更する前に、リリースノートを確認してください。破壊的変更とセキュリティ修正は、リリースノートに記載されています。Version 2.0.3 はセキュリティリリースとして公開されました。このような変更は、意図せず取り込むのではなく、内容を確認してから適用する必要があります。

postgres:16-alpineをメジャーバージョンに固定する理由は、さらに重要です。Postgres は、メジャーバージョンに依存する形式でデータディレクトリにデータを書き込みます。そのため、別のメジャーバージョンが書き込んだディレクトリをサーバーは開けません。postgres:latestと記述してタグを17へ追従させると、コンテナは起動しません。

FATAL:  database files are incompatible with server
DETAIL:  The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17.

データは失われません。再起動しても解決しません。新しい Postgres メジャーバージョンへ移行するには、旧バージョンでダンプを取得し、新しいバージョンの新しいデータディレクトリへリストアします。これはスタックを停止して計画的に実施する作業です。イメージの pull に伴って実行される処理ではありません。

新規構築ではなく、既存の Planka 1.x 環境を移行する場合、このアップグレードには独自の手順があり、プロジェクトのドキュメントに記載されています。事前に取得したバックアップがなければ、version 1へ戻す方法はありません。

障害パターンと表示される文字列

Planka がループして再起動し、ログにデータベースが示される。 DATABASE_URL の認証情報が Postgres の環境変数と一致していません。POSTGRES_PASSWORD はデータディレクトリの初回初期化時にのみ適用されるため、最初の起動に失敗した後で変数を修正しても何も変わりません。db-data ボリュームを削除して、再度起動する必要があります。

ログインは成功するが、ボードが読み込まれない。 BASE_URL がブラウザーのアドレスバーに表示されたアドレスと一致していないか、TRUST_PROXY がありません。ブラウザーコンソールには、/socket.io/ へのリクエストが失敗したことが表示されます。

他の機能は動作するが、アップロードだけ失敗する。 root が所有する bind mount が原因です。ホスト上のディレクトリに対して sudo chown -R 1000:1000 を実行し、コンテナを再起動します。

アップグレード後に添付ファイルが消えた。 /app/data がボリューム上に設定されていなかったため、ファイルがアップグレードで置き換えられたコンテナーレイヤーに保存されていました。バックアップからファイルを復元し、再度 image tag に触れる前にボリュームを追加します。

Traefik が 404 を返す。 コンテナが proxy ネットワークに接続されていないか、Host() ルールが DNS レコードと一致していません。docker compose config を実行すると、置換後のラベルを確認できます。タイプミスはここで見つかります。

通知または webhook が届かない。 Planka 2 は送信 HTTP リクエストを内部フィルター経由で処理し、デフォルトのブロックリストには localhostpostgres が含まれます。同じホスト上の別のコンテナを宛先とする webhook は、仕様によりブロックされる場合があります。フィルターを削除するのではなく、OUTGOING_ALLOWED_HOSTS を調整します。

起動後の運用負荷は小さくなります。リリースノートを確認し、アップグレードの前には毎回データベースをダンプします。restart: unless-stopped により、Docker サービス自体がブート時に有効になっていれば、再起動後にスタックも自動的に復旧します。有効にならない場合については、再起動後に復旧する Compose スタックで説明しています。

FAQ

ログイン後、Planka の読み込みが終わらないのはなぜですか?

認証情報は受け付けられましたが、ライブ接続は確立できていません。Planka は BASE_URL から WebSocket URL を組み立てます。そのため、サイトに https://kanban.example.com でアクセスしているのに、その変数がまだ http://localhost:3000 のままだと、ブラウザーはマシン上に存在しないアドレスへソケット接続を試みます。開発者コンソールには、/socket.io/ への失敗したリクエストが表示されます。BASE_URL に末尾のスラッシュを付けず、公開アドレスを正確に設定してください。次に TRUST_PROXY=true を追加して、アプリがリバースプロキシからの X-Forwarded-Proto ヘッダーを使用するようにし、その後 docker compose up -d を実行します。

最初の Planka 管理者ユーザーを作成するにはどうすればよいですか?

バージョン 1.13 以降、管理者は自動作成されません。DEFAULT_ADMIN_EMAIL に対応するパスワード、名前、ユーザー名の変数を設定してスタックを起動するか、docker compose run --rm planka npm run db:create-admin-user を実行してプロンプトに答えてください。共有サーバーでは、対話形式のコマンドを使用するほうが安全です。パスワードが、docker inspect から読み取れるコンテナ環境に入らないためです。その後も DEFAULT_ADMIN_EMAIL を設定したままにすると、そのアカウントをインターフェースから編集および削除できないように固定できます。

Planka は添付ファイルとアバターをどこに保存しますか?

Planka 2 では、アップロードしたすべてのファイルがコンテナ内の /app/data 配下に保存されます。添付ファイル、ユーザーアバター、ボードの背景が含まれます。このパスを名前付きボリュームにマウントしてください。マウントされていない場合、ファイルはコンテナの書き込み可能レイヤーに保存されます。そのため、コンテナを再作成すると次回失われます。これは、イメージをアップグレードするたびに発生します。bind mount も使用できますが、Node プロセスは UID 1000 で実行されます。ホスト側のディレクトリで sudo chown -R 1000:1000 を実行してください。実行しないと、アップロードが権限エラーで失敗します。

自ホスト型 Planka にはどの程度の RAM が必要ですか?

プロジェクトは、必要なハードウェアの最低要件を公開していません。ホスティングサイトで繰り返し示される 2 vCPU と 4 GB という値は、測定結果ではなくプロバイダーの標準値です。小規模なボードには余裕があります。ワークロード全体は、1 つの Node プロセスと 1 つの Postgres プロセスです。そのため、1 vCPU と 2 GB のプランで 2 人から 5 人のチームに対応できます。通常の 1 週間の運用後に docker stats --no-stream を実行し、実測値に基づいて容量を決めてください。メモリよりもディスクを注意深く監視してください。増加するのは添付ファイルだからです。

データを失わずに Planka をアップグレードするにはどうすればよいですか?

アップグレードの直前にデータベースをダンプし、uploads ボリュームをアーカイブしてください。前夜のスケジュールに任せてはいけません。docker compose exec -T postgres pg_dump -U planka -d planka > planka-db.sql を使用し、疑似端末によってリダイレクト先の出力が破損しないよう -T を保持してください。途中で飛ばすバージョンごとにリリースノートを確認し、イメージタグを latest ではなく特定のリリースに変更します。その後、docker compose pulldocker compose up -d を実行し、マイグレーションのログを監視してください。Postgres のタグはメジャーバージョンに固定してください。サーバーは、別のメジャーバージョンで書き込まれたデータディレクトリを開けないためです。