Dify のセルフホストは Docker Compose が公式手順ですが、RHEL 系のサーバーや root 権限のデーモンを常駐させられない環境では Podman を使う場面があります。Podman の Docker 互換 API は Docker Compose から使えますが、Dify が起動するかは環境ごとの確認が要ります。主につまずくのは rootless という実行形態の制約です。この記事では Dify 1.18.0(2026年10月23日リリース)と Podman 6.1.4 の実ファイルを確認しながら、起動・ポート・SELinux・ファイル所有権・自動起動・Notion連携の設定先を順に整理します。Podman と Docker の仕組みの違いそのものはPodmanとdockerの違いは?デーモンレス・rootlessの仕組みと移行判断で扱っています。
まとめ
- Podman のソケットに本家の Docker Compose(Dify の要件は2.24.0以上)を接続すれば、公式の compose ファイルを書き換えずに起動を試せます。
podman-composeでは起動しないという相談があり、構文の扱いの違いが原因候補に挙がっています。 - Dify 1.18.0 の公式手順は、
.envのDIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKENを埋めるよう求めています。空のままだと Agent バックエンドとサンドボックスが起動しません。Podman に限らない設定で、1.16系までの変数名はDIFY_AGENT_SHELLCTL_AUTH_TOKENでした。 - ホストの非特権ポート下限が既定の1024の場合、rootlessではホスト側の1024未満のポートを公開できません。
.env.exampleのEXPOSE_NGINX_PORT=80が最初に止まる箇所です。 - 既定構成のバインドマウント25箇所に SELinux の
:z/:Z指定は1つも無く、うち12箇所は./nginxと./ssrf_proxyの設定ファイルです。volumes/だけでなくdocker/ディレクトリ全体にラベルを付けます。 - 既定のrootless UIDマッピングでは、コンテナ内の非rootユーザーが作成したファイルは、ホスト側でsubuid範囲のUIDに対応付けられます。バックアップや削除は
podman unshare経由で行い、再起動後の自動起動にはpodman-restart.serviceを有効にします。 - Notion連携の
NOTION_INTEGRATION_TYPEはdocker/envs/core-services/shared.env.exampleにあります。設定漏れは api 側でInternal secret is not setとして出るので、worker を作り直しても直りません。
Podmanで動かす前に確認するホスト側の前提
Podman の rootless チュートリアルは「Rootless Podman requires the user running it to have a range of UIDs listed in the files /etc/subuid and /etc/subgid」と明記しています。書式は USERNAME:UID:RANGE で、値はユーザーごとに重ならないようにします。重なると、あるユーザーが別のユーザーの名前空間を壊せてしまうためです。
grep "^$(whoami):" /etc/subuid /etc/subgid
# 出力が無ければ割り当てる(範囲は既存ユーザーと重ならない値にする)
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 "$(whoami)"
出力があればこの手順は不要です。もう一つの前提は Compose のバージョンです。Dify の Docker Compose 手順は「Dify は Docker Compose 2.24.0 以降を必要とします」としています。Dify の compose は env_file に path と required: false を並べる書き方を使っており、古い Compose ではこれを読めません。
Dify 1.18.0 の docker-compose.yaml には47のサービスが定義されていますが、既定の COMPOSE_PROFILES=${VECTOR_STORE:-weaviate},${DB_TYPE:-postgresql},collaboration で起動するのは16コンテナです。privileged: true を指定しているのは既定では起動しない opengauss だけなので、既定構成のまま rootless で動かす分には特権コンテナを要求されません。
Podmanソケット経由でDifyを起動する手順
podman.socketとcompose実行環境の準備
Podman は常駐デーモンを持ちませんが、podman system service がオンデマンドで REST API を提供します。rootless の既定ソケットは unix://$XDG_RUNTIME_DIR/podman/podman.sock(例:unix:///run/user/1000/podman/podman.sock)です。podman-system-service のマニュアルはこの API を「a compatibility layer offering support for the Docker v1.40 API」と説明していますが、v6.1.4 のソース(version/version.go)では互換 API の現行版が1.44.0、最小版が1.24.0です。Compose はこの範囲で交渉して接続します。
以下はsystemdを使用するLinux x86_64環境向けに、Composeを単体バイナリで導入する例です。別のCPUアーキテクチャでは対応する配布ファイルを選んでください。podman compose のマニュアルは、このコマンドを外部の compose 実装を呼ぶ薄いラッパーと位置付け、「If installed, docker-compose takes precedence」としています。単体の docker-compose を置いておけば、podman compose はそれを優先して使い、Podman のソケットへの接続も自動で設定します。
systemctl --user enable --now podman.socket
mkdir -p ~/.local/bin
curl -fsSL -o ~/.local/bin/docker-compose \
https://github.com/docker/compose/releases/download/v5.6.1/docker-compose-linux-x86_64
chmod +x ~/.local/bin/docker-compose
export PATH="$HOME/.local/bin:$PATH"
export PODMAN_COMPOSE_PROVIDER="$HOME/.local/bin/docker-compose"
podman compose version
podman compose は実行のたびに外部コマンドを呼ぶ旨の警告を出します。気になる場合は PODMAN_COMPOSE_WARNING_LOGS=false で止められます。docker-compose を直接呼ぶなら、export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock を ~/.bashrc などに書いておきます。
Dify 1.18.0の取得と起動コマンド
Dify はインストーラではなく、リポジトリを取得して docker ディレクトリの compose を起動する配布形態です。公式手順は最新のリリースタグを指定して clone しています。既定ブランチをそのまま取ると開発中のコードになるため、タグを固定します。
cd "$HOME"
git clone --branch 1.18.0 https://github.com/langgenius/dify.git
cd dify/docker
cp .env.example .env
# Agent サンドボックス用のトークンを埋める
sed -i "s|^DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN=.*|DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN=$(openssl rand -base64 32)|" .env
# 以下の起動前に、後述の公開ポート変更と、SELinux enforcing 環境でのラベル付けを完了する
podman compose up -d
podman compose ps -a
トークンの行は .env.example では空欄です。公式手順は「設定しないと、Agent バックエンドもサンドボックスも起動しません」と注意しています。compose 側では同じ値が local_sandbox の SHELLCTL_AUTH_TOKEN と agent_backend の環境変数に渡ります。1.16系の旧名 DIFY_AGENT_SHELLCTL_AUTH_TOKEN も代わりに読まれるので、古い .env を引き継いだ場合はどちらかに値が入っていれば動きます。ps で init_permissions だけが Exited になっているのは想定どおりです。
podman-composeを避ける理由
Python 製の podman-compose(最新は v1.6.0)でも起動を試みた例はありますが、Dify の GitHub にある Discussion #30043(2025年12月)では「podman-compose に置き換えると起動しない」という相談に対し、depends_on の条件指定、プロファイル付きサービス、コンテナ名、ネットワーク、init_permissions の扱いの違いが原因候補として挙げられています(回答は自動応答ボットによるもので、メンテナーの見解ではありません)。Dify の compose は版が上がるたびに自動生成し直されるので、互換実装との差を毎回潰す作業になります。本家の Compose を Podman のソケットに向けるほうが切り分けが楽です。Kubernetes 上で動かすなら compose ではなく Helm の構成になるため、DifyをHelm chartでAWS EKSに構築する手順|コミュニティ版0.39.0のvalues設定と落とし穴を参照してください。
rootlessでポート80が使えないときの回避策
ホスト側の特権ポート制限は、rootlessのDockerでも生じます。Podman の troubleshooting の項目48は原因を「Unprivileged users on a Linux system can not bind to ports below 1024 by default.」と説明しています。rootless の既定のネットワーク(pasta)では Listen failed for HOST TCP port */80: Permission denied、ホストネットワークでは bind: permission denied の形で出ます。Dify の docker/.env.example は EXPOSE_NGINX_PORT=80、EXPOSE_NGINX_SSL_PORT=443 なので、コピーしたままの .env だと nginx コンテナがこれに当たります。
| 回避策 | 変更対象 | 権限 | 影響範囲 | 向く場面 |
|---|---|---|---|---|
| 公開ポートを変更 | docker/.env | 不要 | Difyのみ | 前段にプロキシあり |
| bind下限を下げる | /etc/sysctl.d/ | sudo必須 | ホスト全体 | Dify単体で受ける |
前段に ALB や nginx を置く構成なら、公開ポートの変更で足ります。ホストに手を入れずに済むので、共用サーバーではこちらを選びます。
# docker/.env
EXPOSE_NGINX_PORT=8080
EXPOSE_NGINX_SSL_PORT=8443
Dify 単体で80番を直接受けるなら、非特権ユーザーが bind できるポートの下限を下げます。troubleshooting も、再起動後に残すには /etc/sysctl.d/ にファイルを置くよう案内しています。
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-podman-ports.conf
sudo sysctl --system
この設定はホストの全ユーザーに効き、80番以上を誰でも bind できるようになります。複数の利用者がログインするサーバーでは避けてください。下限を80にすれば443番も対象に入ります。
SELinuxでマウント先が読めないときのラベル付け
SELinuxの確認は、Podmanの採用理由とは分けて行います。RHELやFedoraでも、実際のモードはgetenforceで確認し、Enforcingの場合に以下のラベル設定を行ってください。troubleshooting は「By default, Podman does not change the labels set by the OS.」と書いており、ラベルの無いディレクトリはコンテナから読めません。ログには権限エラーしか出ないので、パーミッションの問題と誤診しやすい箇所です。
Dify 1.18.0 の docker-compose.yaml 全体では ./ で始まるバインドマウントが63箇所(うち ./volumes/ 配下が40箇所)あり、:z / :Z は1つもありません。既定の16コンテナに絞っても25箇所あり、そのうち12箇所は ./nginx/nginx.conf.template や ./ssrf_proxy/squid.conf.template といった、clone したままの設定ファイルです。volumes/ だけにラベルを付けても、nginx と ssrf_proxy はテンプレートを読めずに止まります。ラベルは docker/ ディレクトリ全体に、初回起動の前に付けます。親ディレクトリのラベルは、あとから作られる volumes/ 配下にも引き継がれます。
# 恒久的なルールとして登録し、適用する(restorecon でも消えない)
sudo semanage fcontext -a -t container_file_t "$HOME/dify/docker(/.*)?"
sudo restorecon -Rv ~/dify/docker
# 反映されたか確認する
ls -Zd ~/dify/docker ~/dify/docker/nginx
chcon -Rt container_file_t でも同じラベルになりますが、ファイルシステムの再ラベルや restorecon で元に戻ります。長く運用するなら semanage fcontext で登録してください。
compose に :Z を書き足す方法は2つの理由で勧めません。一つは、compose は自動生成物なので次の更新で消えることです。もう一つは、troubleshooting が「Only the current container can use a private volume.」と書くとおり、:Z はそのコンテナ専用のラベルを付ける点です。./volumes/app/storage は init_permissions・api・api_websocket・worker の4コンテナが共有しているので、:Z にすると後から起動したコンテナ以外が読めなくなります。どうしてもサフィックスで対応するなら、共有用の小文字 :z を使います。SELinux を permissive に落とせば動きますが、Podman を選ぶ理由が隔離の強化なら本末転倒です。
rootlessで起きるファイル所有権のずれとバックアップ
Docker では見えなかった問題がここで出ます。Dify 1.18.0 の init_permissions は、初回起動時に busybox で chown -R 1001:1001 /app/api/storage を実行します。1001 は api イメージの実行ユーザー dify の UID です。rootless の Podman では、コンテナ内の root があなた自身の UID に、それ以外の UID は /etc/subuid の範囲に対応付けられます。subuid が100000から始まるなら、コンテナ内の 1001 はホストでは 101000 になります。PostgreSQL や Redis のデータも同じく、ホスト側ではあなたの UID 以外で書かれます。
結果として、ホストのユーザーのままでは volumes/ 配下を消せず、コピーも権限エラーで欠けます。podman unshare は、コンテナと同じ UID の対応付けでコマンドを実行する仕組みで、マニュアルも「manually clearing storage and other data related to images and containers」に使えるとしています。
# ホストからは 101000 のような番号で見える
ls -ln volumes/app/storage
# 名前空間の中では 1001 として見える
podman unshare ls -ln volumes/app/storage
# バックアップは停止してから名前空間の中で取る
podman compose down
podman unshare tar -czf ../dify-volumes-$(date +%F).tar.gz volumes
# PostgreSQL のデータを完全に破棄する場合だけ実行する。
# 通常の更新・コンテナ再作成では削除しない。
# バックアップと復元確認が済むまで、次の行はコメントのままにする。
# podman unshare rm -rf volumes/db/data
この tar で保存されるのはバインドマウントの volumes/ 配下だけです。Agent 用の名前付きボリューム dify_agent_local_sandbox_home と dify_agent_local_sandbox_workspace(実際の名前にはプロジェクト名の接頭辞が付くので podman volume ls で確認)は podman volume export で別に書き出し、.env と envs/ 配下の設定・秘密情報も保存しておきます。更新後は .env.example と .env の差分も確認してください。
再起動後もDifyを自動で立ち上げる設定
Dify の既定構成では、初期化用のinit_permissionsを除く常駐サービスに restart: always が設定されています。Docker ではホストの再起動後にデーモンがこれらを立ち上げ直しますが、Podman にはデーモンがありません。Podman の –restart の説明は「Podman provides a systemd unit file, podman-restart.service, which restarts containers after a system reboot.」としており、このユニットを有効にしない限り、再起動後の Dify は止まったままです。
# ログアウト後もユーザーのサービスを動かし続ける
sudo loginctl enable-linger "$(whoami)"
# 再起動時に restart: always のコンテナを起こす
systemctl --user enable podman-restart.service
enable-linger が無効な場合、最後のログインセッション終了後にユーザーサービスが停止することがあります。停止までの時間や挙動はsystemd-logindの設定に依存します。この2行は必ずセットで入れてください。
Notion連携の環境変数の設定先と反映確認
セルフホストの Dify で Notion をナレッジのデータソースにするには、Dify の Notion 同期のドキュメントのとおり NOTION_INTEGRATION_TYPE=internal と NOTION_INTERNAL_SECRET を設定します。シークレットは Notion のインテグレーション設定ページで内部インテグレーションを作り、Secrets から取得した値です。作成にはそのワークスペースのオーナー権限が必要です。インテグレーションを作っただけでは対象ページを読めないため、連携したいページやデータベースの側でインテグレーションへの接続を追加します。
設定項目の保存先に注意してください。cp .env.example .env で作った root の .env に、この項目が無いためです。実体は docker/envs/core-services/shared.env.example にあり、NOTION_INTEGRATION_TYPE=public と NOTION_CLIENT_SECRET、NOTION_CLIENT_ID、NOTION_INTERNAL_SECRET が並んでいます。docker/README.md が「Keep the root .env.example limited to variables required to start the default Docker Compose deployment.」と定めているためで、設計どおりの配置です。
設定先は2つあり、どちらでも動きます。shared.env.example を shared.env にコピーして書き換えるのが設計に沿った方法です。もう一つは root の docker/.env に追記する方法で、同じ README の「Docker Compose reads envs/*.env files when present, then reads .env last so values in .env take precedence.」のとおり .env が優先されます。api と worker の env_file も、envs/ 配下を required: false で並べた最後に ./.env を置いています。設定を1ファイルにまとめたいなら後者を選んでください。
# docker/.env に追記する場合
NOTION_INTEGRATION_TYPE=internal
NOTION_INTERNAL_SECRET=(Notion のインテグレーション設定ページで取得した値)
テンプレートの public を未設定時の既定値と思わないでください。api/configs/extra/notion_config.py では NOTION_INTEGRATION_TYPE の default は None で、説明は「Set to ‘internal’ for internal integrations, or None for public integrations.」です。未設定だと公開インテグレーション扱いの OAuth 経路に進むため、社内ワークスペースに繋ぐなら internal を明示します。
連携が失敗したときの切り分け順序
環境変数を変えたら、api と worker を作り直して値が届いたかを確かめます。
podman compose up -d --force-recreate api worker
# api コンテナに届いたか確認する
podman compose exec api env | grep NOTION
確認先を api に絞るのは、この2つの変数を読むのが api 側の処理だけだからです。1.18.0 では api/extensions/application_services/data_sources.py が NOTION_INTEGRATION_TYPE == "internal" と NOTION_INTERNAL_SECRET を渡し、api/services/data_source/oauth_service.py がシークレットが空なら Internal secret is not set を返します(1.16系までは api/controllers/console/auth/data_source_oauth.py にあった処理です)。
取り込みとインデックス作成は worker(Celery)で走りますが、worker は保存済みの認証情報を langgenius/notion_datasource の資格情報として読み出します。見つからないときの代わりの値も NOTION_INTERNAL_SECRET ではなく NOTION_INTEGRATION_TOKEN という別の変数です。つまり、設定漏れならワークスペースをバインドする時点で Internal secret is not set が出ます。バインドが通ったのに同期だけ失敗するなら、保存済みの認証情報か Notion 側のページ接続を疑ってください。worker を作り直しても前者は直りません。
Notion同期の制約と更新反映の運用
Dify のドキュメントは「画像とファイルはインポートできません。テーブルのデータはテキストに変換されます。」と明記しています。Notion の表を根拠に回答させる場合は、取り込み後のテキストとチャンクを確認し、列見出しと各行の対応が保持されているかを代表的な質問で検証してください。Dify を介さず Notion API でページやデータベースを直接取り出す実装は、Notion APIとは?データソース対応の使い方とレート制限・社内連携の実装【2026年10月】で解説しています。
更新の反映は手動と考えてください。同じドキュメントは冒頭で「Notion 側のデータ更新が自動的に Dify へ反映されます」と書く一方、手順としてはドキュメント一覧の同期ボタンを押す方法を示しています。1.18.0 の api/extensions/ext_celery.py の beat スケジュールにも、api/schedule/ 配下にも Notion 同期の定期タスクはありません。Notion を更新したら誰が同期を押すのかを決めておかないと、古い内容を答え続けるチャットボットになります。同期のたびに埋め込み処理が走り、埋め込みモデルのトークンを消費する点も見込んでおきます。
取り込んだナレッジを応答につなぐ設計はDifyでチャットボットを作る手順|種別の選び分けと社内公開・ナレッジ設定【1.18対応】、ワークフローでの使い方はDifyワークフローの作り方と使い方|ノード選定・ループ・トリガーを実装手順で解説【2026年版】にまとめています。Notion同期では取り込めない添付ファイルは、対応形式ならDifyへ別途アップロードします。Difyの保存先をオブジェクトストレージにする場合、その設定はDifyのストレージをAmazon S3に切り替える設定手順と.envの優先順位で扱っています。社内文書の検索精度まで含めて設計を詰めたい場合は、RAG構築支援で取り込み方式の選定から相談できます。
Podmanを選ばないほうがよい場面
Difyでは公式手順のDockerを第一候補とし、Dockerの導入禁止や既存基盤のPodmanへの統一が要件の場合にPodmanを検討します。rootlessだけが要件なら、Dockerのrootlessモードも比較対象です。それ以外の理由で選ぶと、得るものより検証の手間が大きくなります。
Dify の公式手順は Docker を前提に書かれており、compose は版ごとに自動生成し直されます。1.16.1 と 1.18.0 を比べただけでも、compose のサービス定義は39から47に増え、サンドボックス用トークンの変数名も変わりました。サービスや設定項目が変わるたびに、ポート・ラベル・所有権の3点を自分で確かめ直すことになります。この確認を続けられないチームは Docker のままにすべきです。
プラグインのように依存パッケージを後から取り込む仕組みも、ランタイムの差が疑われやすい領域です。プラグインが入らないときは、Difyのプラグインがインストールできない原因と対処法|bad signature・依存エラー・容量【1.17.1対応】で挙げた署名検証や容量の問題を先に潰してから、ランタイムを疑う順序が効率的です。Docker が使えるクラウド環境なら、DifyをAWS EC2に構築しBedrockへIAMロール接続する手順【Dify 1.18.0対応】の構成のほうが手順どおりに進みます。
逆に、Docker のパッケージが無い環境や root デーモンの常駐が認められない環境では、Podman が現実的な選択肢です。ポート・ラベル・所有権・自動起動の4点を先に済ませれば、残りは Docker と同じ手順で進みます。Podman 自体のコマンドはPodmanコマンド一覧と使い方|run・ps・exec・cpの早見表【Podman 6.1対応】で確認できます。
よくある質問
PodmanでもDify公式のdocker-compose.yamlはそのまま使えますか?
Podman のソケット経由で使う構成を取れます。podman.socket を有効にし、単体の docker-compose(2.24.0以上)を入れて podman compose up -d を実行すれば、compose ファイルを書き換えずに起動を試せます。成功するかは OS・Podman・SELinux の状態で変わるので、対象環境で確認してください。compose ファイルは自動生成物なので、編集せずに環境変数とホスト側の設定で対応してください。
podman-composeでDifyが起動しないのはなぜですか?
Dify の compose が使う depends_on の条件指定やプロファイル、init_permissions などの扱いが podman-compose と食い違うことが、原因候補として挙げられています(回答は自動応答ボットによるもので、再現は確認されていません)。使っている podman-compose の版で再現するかを確かめ、切り分けには Docker Compose と比べてください。
Dify 1.18.0をPodmanで起動するとagent_backendやlocal_sandboxが立ち上がらないのはなぜですか?
多くは .env の DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN が空のままです。1.18.0 の公式手順は openssl rand -base64 32 で生成した値を設定するよう求めており、未設定だと Agent バックエンドもサンドボックスも起動しません。Podman 固有ではなく Docker でも同じです。
Notion連携でInternal secret is not setと出たときはどこを見ますか?
api コンテナの環境変数です。podman compose exec api env | grep NOTION で NOTION_INTEGRATION_TYPE=internal とシークレットが入っているかを確認してください。この判定は api 側で完結するため、worker を作り直しても解消しません。
サーバーを再起動したらDifyのコンテナが起動しなくなったのはなぜですか?
Podman にはコンテナを立ち上げ直すデーモンが無いためです。systemctl --user enable podman-restart.service を有効にし、sudo loginctl enable-linger でユーザーのサービスを常駐させると、restart: always のコンテナが再起動後に戻ります。