MealieをVPSに自前構築する方法
Docker ComposeでMealieをVPSに構築します。レシピURLから材料と手順を抽出し、献立や買い物リスト、nginx、TLS、バックアップまで設定できます。
セルフホスト型レシピ管理ツールの役割
セルフホスト型レシピ管理ツールは、所有するサーバー上のデータベースでレシピを管理します。多くの家庭では Mealie が選ばれています。レシピページの URL を貼り付けると、Mealie はページから材料、手順、分量、調理時間を読み取り、記事本文や広告を除外します。コレクションに保存されるのは、料理に必要な情報です。
アプリの機能はそれほど多くありません。レシピをドラッグして登録できる週間献立があり、その献立から買い物リストを作成できます。料理をする人ごとにログインを用意できます。すべてが 1 つのコンテナで動作し、リクエストがない間はほぼアイドル状態になるため、小規模な VPS でも負荷を意識せず運用できます。
このガイドでは Docker Compose を使用します。services: と volumes: という言葉が初めての場合は、まず Docker Compose ファイルの構成方法を読んでください。以下の内容は、1 つの compose ファイルと 4 つのコマンドだけで構成されています。
Docker Compose で Mealie をインストールする
Mealie はイメージを GitHub container registry に公開しています。2026 年 7 月時点の安定版タグは v3.22.0 です。latest を使わず、バージョンを固定してください。latest を使うと、関係のない日に docker compose pull が更新され、準備していないデータベースマイグレーションが適用される可能性があります。
sudo mkdir -p /srv/mealie
cd /srv/mealie
sudo nano docker-compose.ymlservices:
mealie:
image: ghcr.io/mealie-recipes/mealie:v3.22.0
container_name: mealie
restart: always
ports:
- "127.0.0.1:9925:9000"
deploy:
resources:
limits:
memory: 1000M
volumes:
- mealie-data:/app/data/
environment:
ALLOW_SIGNUP: "false"
PUID: 1000
PGID: 1000
TZ: Europe/Amsterdam
BASE_URL: https://recipes.example.com
volumes:
mealie-data:起動する前に、2 行を確認してください。
ポートは 9925:9000 ではなく 127.0.0.1:9925:9000 と記述します。コンテナ内では 9000 番ポートで待ち受け、ホストの 9925 番ポートから転送します。この転送をループバックアドレスにバインドすると、nginx からは Mealie に接続できますが、インターネットからは接続できません。Docker はパケットフィルターに独自のルールを書き込むため、ファイアウォールでポートを閉じていても、単純な 9925:9000 は外部から到達可能です。この挙動は一度理解しておく価値があります。ufw が公開 Docker ポートを無視する理由を参照してください。
BASE_URL には、実際に使用する公開アドレスを正確に指定してください。スキームを含め、末尾にスラッシュは付けません。Mealie はこのアドレスを基に、パスワードリセットリンクと招待リンクを生成します。http://localhost:9925 に設定すると、パートナーに送信する招待には、サーバー上でしか機能しないリンクが含まれます。
起動し、初回起動のログを監視します。
sudo docker compose up -d
sudo docker compose logs -f mealie初回起動では SQLite データベースを作成し、マイグレーションを実行するため、数秒かかります。ログが落ち着き、マイグレーションの行が表示されなくなったら、ローカルでアプリを確認します。
curl -I http://127.0.0.1:9925200 OK なら、アプリは起動しています。Connection refused なら、コンテナは実行されていません。sudo docker compose ps を実行し、終了コードを確認してください。終了コード 137 で停止したコンテナは、1000M のメモリ制限を超えたため強制終了されています。最小構成のプランで発生することがあります。
最初のログインとオープンサインアップの無効化
デフォルトのアカウントは changeme@example.com、パスワードは MyPassword です。これでログインしたら、すぐに両方を変更してください。この組み合わせはドキュメントに記載されているため、あらゆるスキャナーに知られています。
compose ファイルの ALLOW_SIGNUP: "false" は意図的な設定です。サインアップを開放すると、アドレスを見つけた人なら誰でもレシピボックスにアカウントを作成できます。無効にすると、管理画面からユーザーを追加し、自分で招待リンクを送信できます。このリンクは BASE_URL を基に生成されるため、この値が重要です。同じサーバーで複数のアプリを運用し、すべてで 1 つのパスワードを使いたい場合、Mealie は 自分でホストする Authentik インスタンス などの外部 ID プロバイダーにログイン処理を委任できます。
Mealie では、ユーザーを世帯にまとめます。同じ世帯の全員がレシピコレクション、献立、買い物リストを共有します。これは家族での利用に適しています。同じサーバー上でも世帯を分ければコレクションも分離されるため、アンチョビを入れるかどうかで意見が合わないルームシェアにも適しています。
インポーターを実行する理由
レシピコレクションを開き、URL からレシピを作成する操作を選んで、リンクを貼り付けます。Mealie はページを取得し、構造化レシピデータを探します。これは、多くのレシピサイトが検索エンジン向けに埋め込んでいる、機械で読み取れるデータブロックです。このブロックがあれば、インポートは正確かつすぐに完了します。
画像や、貼り付けたプレーンテキストからもインポートできます。これにより、料理本のページを撮影した画像にも対応できます。これらは処理に時間がかかるため、完了後に内容を確認してください。手書きの分数は誤認識されやすいためです。
一括インポートも同じ画面から実行できます。アドレスの一覧を 1 行に 1 件ずつ貼り付けると、Mealie がバックグラウンドで順番に処理します。二百件のブックマークも、1 回の操作でコレクションに移行できます。
食事計画と買い物リスト
食事プランナーはカレンダーです。レシピを特定の日にドラッグすると、その日に計画されます。買い物リストには、計画したレシピの材料がまとめて登録され、重複する項目も統合されます。そのため、2 つのレシピで玉ねぎが必要な場合も、2 行ではなく 1 行にまとまります。
買い物中は、スマートフォンでリストをリアルタイムに表示できます。リストは自分のサーバーに保存されるため、家庭内の全員が同じリストを同時に確認できます。1 人が牛乳をチェックして完了にすると、別の人の画面からも牛乳が消えます。
nginx と TLS を前段に配置する
Mealie は平文の HTTP を使用し、証明書を独自に処理する機能を持ちません。Mealie の前段に nginx を配置し、nginx でトランスポート層セキュリティ(TLS)を終端します。まず、DNS の A レコードをサーバーに向けてください。証明書の発行時に、その名前が検証されるためです。
sudo apt update && sudo apt install -y nginx
sudo nano /etc/nginx/sites-available/mealieserver {
listen 80;
server_name recipes.example.com;
client_max_body_size 64M;
location / {
proxy_pass http://127.0.0.1:9925;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}sudo ln -s /etc/nginx/sites-available/mealie /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxnginx -t の出力で syntax is ok と test is successful を確認することが重要です。これに合格した場合にのみ reload してください。壊れた設定を reload すると、古い設定のまま動作するため、次回の再起動まで誤りが表面化しません。
nginx のデフォルト値は 1 MB なので、client_max_body_size 64M が必要です。レシピの写真をアップロードしたり、ブラウザーからバックアップを復元したりすると、それより大きな body が送信されます。この行がない場合、nginx から 413 Request Entity Too Large が返されます。Mealie からのエラーではないため、アプリケーションログには何も記録されません。
次に、証明書を発行します。この手順と更新タイマーについては、certbot で nginx 用の Let's Encrypt 証明書を発行するで説明しています。
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d recipes.example.comCertbot は server block を 443 番ポートで待ち受けるように書き換え、80 番ポートからの redirect も追加します。https:// でサイトを読み込み、ブラウザーが証明書を受け入れることを確認してください。Mealie は読み込めるものの、独自のリンクから http:// に移動する場合は、BASE_URL がまだ http のままです。値を修正した後、sudo docker compose up -d を実行して、新しい値でコンテナを再作成してください。
example.com/recipes のようなサブパスで Mealie を提供することはできません。frontend はサブパスから提供できないためです。サブドメインを使用してください。
バックアップと、リストアで実際に行われること
Mealie が管理するすべてのデータはコンテナ内の /app/data/ に保存されます。これは mealie-data ボリュームです。このボリュームをコピーすれば、レシピ、画像、データベースをまとめてコピーできます。
sudo docker volume ls
sudo docker compose stop mealie
sudo docker run --rm -v mealie_mealie-data:/data -v "$PWD":/backup \
alpine tar czf /backup/mealie-data.tgz -C /data .
sudo docker compose start mealieボリューム名にはプロジェクト名がプレフィックスとして含まれます。プロジェクト名は compose ファイルを格納するディレクトリ名です。/srv/mealie では、ボリュームは mealie_mealie-data です。そのため、最初のコマンドは docker volume ls になります。表示された名前を使用し、このガイドに記載された名前をそのまま使わないでください。先にコンテナを停止することが重要です。SQLite は書き込み中であることが多く、実行中にコピーすると、リストア後に読み取れない状態になる可能性があります。
Mealie には管理画面内に独自のバックアップページもあります。このページでは、データベースを JSON として画像とともに保存した、移行可能なアーカイブを作成します。サーバー間の移行にはこちらを使用してください。バージョン変更後も利用できるため、単純なファイルコピーより適しています。アーカイブからのリストアは、意図的に破壊的な処理です。現在のデータベースを削除してからアーカイブを読み込むため、元に戻せません。処理が完了するとログアウトされます。
同じサーバー上に保存している間は、どちらのコピーもバックアップではありません。アーカイブをスケジュールに従って別の場所へ転送してください。その用途には restic による暗号化されたオフサーバーバックアップ が適しています。
Mealie の更新
cd /srv/mealie
sudo nano docker-compose.yml
sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs -f mealieファイル内の固定バージョンを上げてから、イメージを pull してコンテナを再作成します。新しいイメージで最初に起動したときに、マイグレーションが実行されます。メジャーバージョンを変更する前に、ボリュームのコピーを作成してください。マイグレーションが途中で失敗すると、以前のイメージでは開けなくなるデータベースが残るためです。使用中のバージョンから新しいバージョンまでに含まれるすべてのリリースについて、リリースノートを確認してください。
インポーターが失敗する場合
構造化されたレシピデータをまったく公開していないサイトもあります。その場合、Mealie は材料リストが空のまま、タイトルだけをインポートします。これは設定で回避できません。代わりに、レシピ本文を手動で貼り付けてください。
その他の失敗は、レシピサイトの前段にあるボット対策が原因です。サイトがレシピではなくチャレンジページを Mealie に返します。Mealie はすでにブラウザーを偽装し、ユーザーエージェントをローテーションして、この問題を軽減しています。それでもサイトが拒否する場合、公式に記載されている選択肢は、アドレスの評判がより良いプロキシ経由でスクレーパーを実行するか、実際のブラウザーでチャレンジを解決する FlareSolverr インスタンスを実行することです。どちらも任意で、コンテナの環境変数で設定します。
サーバーがサイトにまったく接続できないためにインポートが失敗する場合は、別の問題です。curl -I https://the-site.example/recipe を使ってサーバー上からテストし、スクレーパーを疑う前にステータス行を確認してください。
配置位置
Mealie は、家庭内で最初に導入するセルフホストアプリケーションとして適しています。同居する人に頼まなくても使ってもらえるためです。これは Immich で自分のフォトライブラリを運用するのと同じ種類の用途ですが、より軽量です。また、今年セルフホストする価値のあるものの一覧にも含まれます。1 台の小型サーバーで両方を運用できます。
2 つ目の用途に適した候補は Immich だけではありません。まだ決めていない場合は、PhotoPrism と Immich のメモリ要件とバックアップコマンドを確認しておく価値があります。必要なメモリの下限とバックアップ方法が十分に異なるため、ディスクの残り容量を割り当てる前に比較できます。
FAQ
レシピ URL のインポートに失敗するのはなぜですか?
主な原因は 2 つあります。ページが構造化されたレシピデータを公開していないためスクレーパーが何も見つけられず、材料のないタイトルだけが取得される場合があります。または、サイトの前段にあるボット対策が、レシピの代わりにチャレンジページを返している場合があります。後者では、Mealie の接続先を、アドレスの評判がより良いプロキシ、または実際のブラウザーでチャレンジを解決する自己ホスト型の FlareSolverr インスタンスに変更できます。変更する前に、curl -I を使用してサーバーからページ自体に接続できることを確認してください。
PostgreSQL は必要ですか、それとも SQLite で十分ですか?
家庭で使用する場合は SQLite で十分で、デフォルトでもあります。データディレクトリがネットワーク接続ストレージ上にある場合は PostgreSQL に移行してください。ネットワークファイルシステム上で SQLite を使用すると、データベースのロックエラーが発生し、ファイルが破損する可能性があるためです。PostgreSQL でリストアするには、アーカイブの読み込み前にすべてのデータを削除するため、データベースユーザーが superuser である必要があります。
ドメイン名なしで Mealie を実行できますか?
自分のネットワーク内であれば実行できます。実際に入力するアドレス(http://192.168.1.20:9925 など)を BASE_URL に設定し、nginx は使用しないでください。招待リンクとパスワードリセットリンクは BASE_URL を基に生成されるため、値が間違っていると他のユーザーが開けないリンクになります。ログイン情報が平文で送信されるため、通常の HTTP でインターネットに公開しないでください。
家族にそれぞれのログインを用意するにはどうすればよいですか?
ALLOW_SIGNUP は "false" のままにして、管理画面からユーザーを追加してください。招待リンクが生成されるので、そのリンクを各ユーザーに送信します。同じキッチンを共有する全員を同じ household に所属させると、レシピ、献立、買い物リストを共有できます。1 台のサーバー上で household を分けると、コレクションも分離されます。
Mealie の実行を停止すると、レシピはどうなりますか?
取り出せます。管理画面のバックアップではデータが JSON として保存されます。Mealie ではレシピを通常の markdown ファイルとしてエクスポートすることもでき、ソフトウェアがなくても任意のテキストエディターで読み取れます。必要になる前に 1 回エクスポートし、開けることを確認してください。