---
title: "DifyのストレージをAmazon S3に切り替える設定手順：.envの優先順位と既存ファイル・鍵の移行"
url: "https://www.issoh.co.jp/tech/details/4131/"
published: 2024-11-12
updated: 2026-11-03
categories: ["Dify"]
publisher: "株式会社一創"
---

# DifyのストレージをAmazon S3に切り替える設定手順：.envの優先順位と既存ファイル・鍵の移行

セルフホスト版のDifyは、アップロードされたファイルを既定でホストのディスク（`docker/volumes/app/storage`）に保存します。保存先をAmazon S3に切り替える設定は環境変数の書き換えで済みますが、Dify 1.14.1で設定ファイルが `docker/envs/` 配下に分割されてから、書いたはずの値が効かない事故が起きやすくなりました。同じ保存先には、自動生成された `SECRET_KEY` と、モデルプロバイダーのAPIキーを復号するRSA秘密鍵も置かれます。空のS3へ切り替えると、`SECRET_KEY` を自動生成に任せている環境ではログイン状態に影響し、秘密鍵を移さなければ登録済みのAPIキーを復号できません。この記事では最新の1.18.0（2026年10月23日公開）のソースと `docker compose config` による解決結果をもとに、効く書き場所と移行の順番を示します。

## まとめ：S3切り替えで書く場所と移すもの

結論から書くと、`STORAGE_TYPE=s3` は `docker/.env` に、S3の認証情報は `docker/envs/core-services/shared.env` に、プラグインデーモン用の `PLUGIN_*` は `.env` に書きます。`PLUGIN_*` は `envs` 配下に書いても反映されません。

| 設定                                   | 書くファイル      | 理由                                                        |
| ------------------------------------ | ----------- | --------------------------------------------------------- |
| STORAGE\_TYPE=s3                     | docker/.env | .env の opendal が最後に読まれて勝つ                                 |
| S3\_BUCKET\_NAME・S3\_REGION          | shared.env  | api・api\_websocket・worker・worker\_beat・plugin\_daemon に渡る |
| S3\_ACCESS\_KEY・S3\_SECRET\_KEY      | shared.env  | .env だと web・sandbox にも渡る                                  |
| SECRET\_KEY（固定値）                     | docker/.env | 空欄だと保存先に自動生成される                                           |
| PLUGIN\_STORAGE\_TYPE・PLUGIN\_S3\_\* | docker/.env | envs 配下の値は変数展開で読まれない                                      |

運用中の環境は「`SECRET_KEY` を固定→S3の設定→`docker compose up -d`→`flask migrate-oss` で既存ファイルを移す→S3側で確認」の順で切り替えます。移行コマンドは秘密鍵の `privkeys` を運びますが、自動生成された `SECRET_KEY` のファイルは運ばないので、最初に固定します。

## S3に保存されるファイルと保存されないデータ

`STORAGE_TYPE` が切り替えるのは「ファイル実体」の置き場です。ナレッジの元文書（`upload_files`）、チャットの添付画像、ツールの生成ファイル（`tools`）などです。アプリ定義や会話履歴はPostgreSQL、ベクトルは `VECTOR_STORE` のデータベースに残ります。見落とされやすいのは、ファイル以外の2つです。

- **ワークスペースごとの秘密鍵**：`KEY_PROVIDER_TYPE=local`（既定）では、モデルプロバイダーのAPIキーやツールの認証情報を暗号化するRSA鍵の秘密鍵が `privkeys/<tenant_id>/private.pem` として保存先に書かれます（[公式の環境変数リファレンス](https://docs.dify.ai/en/self-host/deploy/configuration/environments)にも「keeps the private key in the `STORAGE_TYPE` backend」とあります）。
- **自動生成の `SECRET_KEY`**：1.14.1以降の `.env.example` は `SECRET_KEY=` が空欄で、コメントは「Leave empty to auto-generate a persistent key in the storage directory.」です。実装（`api/configs/secret_key.py`）は保存先から `.dify_secret_key` を読み、無ければ新しい鍵を生成して書き込みます。

保存先を空のS3に変えると、秘密鍵が見つからず `PrivkeyNotFoundError` になり、`SECRET_KEY` は別の値で作り直されます。`SECRET_KEY` はファイルの署名付きURLやツールのOAuthクライアント設定の暗号化にも使われています。

既定は `STORAGE_TYPE=opendal`・`OPENDAL_SCHEME=fs` で、単一ホストならこれで困りません。S3へ移す理由になるのは、EBSの容量が足りなくなったとき（[Amazon EBSのボリュームタイプと料金](/tech/details/15440/)）と、apiとworkerを複数ノードやEKSのレプリカに分けるときです。

## 1.14.1以降の設定ファイル構成と.envの優先順位

### envs配下を読んだあと.envを最後に読む読み込み順

1.14.0までは `docker/.env.example` に `S3_*` の7項目がありました。1.14.1で `docker/envs/` が新設され、S3の項目は `envs/core-services/shared.env.example` へ移り、1.18.0の `.env.example` には `S3_` で始まる行がありません。

api・worker・worker\_beat・plugin\_daemon は `env_file` で `shared.env` などを読み（いずれも `required: false`）、リストの最後に `./.env` を置いています。Composeは同じ変数が複数ファイルにあれば後のファイルの値を採るため、`.env` が最優先です。`.env.example` の冒頭にも「Values in docker/.env take precedence over docker/envs/\*.env files.」とあります。

### shared.envにs3を書いても.envのopendalに戻される実測

罠は、公式手順が `cp .env.example .env` から始まり、その雛形の159行目に `STORAGE_TYPE=opendal` がある点です。1.18.0のcomposeファイルで、`.env` を雛形のまま、`shared.env` に `STORAGE_TYPE=s3` を書いて `docker compose config`（Docker Compose 5.6.0）で解決させた結果が次です。

| 書いた場所                 | api の STORAGE\_TYPE | plugin\_daemon の PLUGIN\_STORAGE\_TYPE |
| --------------------- | ------------------- | -------------------------------------- |
| shared.env だけ         | opendal             | local                                  |
| .env の行を s3 に変更       | s3                  | local                                  |
| .env に PLUGIN\_\* も追記 | s3                  | aws\_s3                                |

`shared.env` の `S3_BUCKET_NAME` はapiに届いているのに、`STORAGE_TYPE` だけが `.env` の `opendal` に上書きされ、保存先はローカルのままでした。`.env` を消すと「env file … .env not found」でcompose自体が止まるので、`.env` の該当行を `s3` に書き換えます。

### S3シークレットアクセスキーの.env経由での配布範囲

「全部 `.env` に書けば確実」という手順は動きますが、勧めません。同じ実測で `S3_SECRET_KEY` を `.env` に置くと、web（フロントエンド）・sandbox（コード実行）・agent\_backend の環境変数にも入りました。公式リファレンスも `PASSWORD_PEPPER` の項で、同じ理由から `.env` に入れないよう書いています。

`STORAGE_TYPE=s3` だけを `.env` に、鍵とバケット名は `shared.env` に分けます。AWS上ならIAMロールにして鍵そのものを置かないのが最善です。

## S3バケットの事前作成とIAMポリシーの権限設計

### Difyが呼ぶS3 APIから逆算した最小権限

apiの実装（`api/extensions/storage/aws_s3_storage.py`）が呼ぶのは `head_bucket`・`put_object`・`get_object`・`head_object`・`delete_object`・`download_file`・`list_objects_v2`（`scan`）です。プラグインデーモンが使う dify-cloud-kit v0.1.3 も HeadBucket・PutObject・GetObject・HeadObject・DeleteObject・ListObjectsV2 で、同じポリシーで足ります。

```
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DifyObjects",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::dify-storage-example/*"
    },
    {
      "Sid": "DifyBucket",
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": "arn:aws:s3:::dify-storage-example"
    }
  ]
}
```

2つ目のステートメントを省くと起動時の `head_bucket` が通りません。AWSの[HeadBucketのAPIリファレンス](https://docs.aws.amazon.com/AmazonS3/latest/API/API%5FHeadBucket.html)は必要な権限を `s3:ListBucket` としており、これはオブジェクトARN（`bucket/*`）ではなくバケットARNに付けます。`s3:DeleteObject` を外すと、ドキュメントやアプリを削除してもS3にファイルが残り続けます。暗号化は2023年1月5日以降、指定が無ければSSE-S3が自動で掛かるので追加の権限は要りません（[AWSの告知](https://aws.amazon.com/blogs/aws/amazon-s3-encrypts-new-objects-by-default/)）。SSE-KMSに変えるなら、そのキーへの `kms:GenerateDataKey` と `kms:Decrypt` を足してください。

`AmazonS3FullAccess` はアカウント内の全バケットへの全操作を許すので、本番データと同じアカウントでは使わないでください。

### バケットを事前に作る理由と公開設定

実装は起動時の `head_bucket` で404が返ると `create_bucket` を呼びますが、`CreateBucketConfiguration` を渡していません。[CreateBucketのAPIリファレンス](https://docs.aws.amazon.com/AmazonS3/latest/API/API%5FCreateBucket.html)どおり、us-east-1以外のリージョンでは場所の指定が要るため、`S3_REGION=ap-northeast-1` では自動作成が失敗します。CLIで先に作り、IAMからは `s3:CreateBucket` を外しておきます。

```
aws s3api create-bucket --bucket dify-storage-example \
  --region ap-northeast-1 \
  --create-bucket-configuration LocationConstraint=ap-northeast-1
```

バケットは公開しないでください。1.18.0のセルフホスト構成では、ブラウザへのファイル配信はDifyのAPIが `FILES_URL` 配下の署名付きURLで中継します。S3の署名付きURL（presigned URL）を発行するのは `DEPLOYMENT_EDITION` がCLOUDのときのアイコン表示だけで、ブラウザがバケットへ直接行く経路がありません。英語圏の解説にはバケットのCORS設定を必須とするものがありますが、既定構成のセルフホストでは不要です。2023年4月に全リージョンへの展開が完了してから作る一般用途バケットは、ブロックパブリックアクセスが既定で有効、ACLも無効です（[AWSの告知](https://aws.amazon.com/blogs/aws/heads-up-amazon-s3-security-changes-are-coming-in-april-of-2023/)）。この設定はそのまま残します。

## S3認証方式別の環境変数設定

### AWS外のサーバー向けアクセスキー認証設定

オンプレミスや他社クラウドで動かすときはIAMユーザーのアクセスキーを使います。`docker/envs/core-services/shared.env` が無ければ雛形からコピーして作ります。

```
# docker/.env（既存の行を書き換える）
STORAGE_TYPE=s3

# docker/envs/core-services/shared.env
S3_REGION=ap-northeast-1
S3_BUCKET_NAME=dify-storage-example
S3_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
S3_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLE
S3_USE_AWS_MANAGED_IAM=false
```

AWSのS3なら `S3_ENDPOINT` は空でよく、boto3がリージョンから標準のエンドポイントを決めます。`shared.env.example` の `S3_REGION=us-east-1` と `S3_BUCKET_NAME=difyai` は雛形の値で、コードの既定はどちらも未設定です。雛形をコピーして書き換え忘れると `difyai` というバケット名のまま接続を試み、その名前が他のアカウントに取られていれば403が握りつぶされて（後述）起動だけは成功します。反映は `docker compose restart` ではなく `docker compose up -d` で行ってください。restartはコンテナを作り直さないため、env\_fileの変更が入りません。

### EC2・ECS・EKS向けIAMロール認証設定

AWS上ではIAMロールを使います。`S3_USE_AWS_MANAGED_IAM=true` にすると、実装は `boto3.Session()` の標準の認証情報チェーンで、EC2のインスタンスプロファイル・ECSのタスクロール・EKSのIRSAやPod Identityから資格情報を得ます。

```
# docker/envs/core-services/shared.env
S3_USE_AWS_MANAGED_IAM=true
S3_REGION=ap-northeast-1
S3_BUCKET_NAME=dify-storage-example
```

この分岐でクライアントに渡るのは `region_name` だけで、`S3_ENDPOINT` と `S3_ADDRESS_STYLE` は使われません。プライベートサブネットからは、エンドポイントURLを変えずに済むゲートウェイ型のVPCエンドポイントでS3へ出ます。

EC2上のDocker Composeでは、コンテナからインスタンスメタデータ（IMDSv2）に届くかも確認します。AWSの[EC2ユーザーガイド](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html)は、コンテナ環境ではホップ数の上限1だと応答を受け取れない場合があるとして、上限を2へ上げることを検討するよう案内しています。起動直後から認証エラーが続くなら、次で2に上げます。

```
aws ec2 modify-instance-metadata-options --instance-id i-0123456789abcdef0 \
  --http-endpoint enabled --http-tokens required --http-put-response-hop-limit 2
```

EC2での構築とBedrock接続は[DifyをAWS EC2に構築しBedrockへIAMロール接続する手順](/tech/details/2725/)、EKSでは[DifyをHelm chartでAWS EKSに構築する手順](/tech/details/7097/)で扱っています。

### MinIOなどS3互換ストレージの接続設定

S3互換ストレージも `STORAGE_TYPE=s3` のまま `S3_ENDPOINT` を指定します。`S3_ADDRESS_STYLE` は `auto`・`virtual`・`path` を受け付け、boto3の `addressing_style` にそのまま渡ります。コンテナ名で参照する構成は `bucket.minio` の名前解決ができないので `path` にします。

```
# docker/envs/core-services/shared.env
S3_ENDPOINT=http://minio:9000
S3_REGION=us-east-1
S3_BUCKET_NAME=dify
S3_ACCESS_KEY=dify-app
S3_SECRET_KEY=change-me
S3_ADDRESS_STYLE=path
S3_USE_AWS_MANAGED_IAM=false
```

Difyのcomposeファイルにも `minio` サービスがありますが、これはMilvus用（`container_name: milvus-minio`、2023年3月版のイメージ）で、ファイル保存先として用意されたものではありません。MinIOのOSS版は2025年10月にビルド済みバイナリの提供をやめ、[GitHubリポジトリ](https://github.com/minio/minio)は2026年4月25日にアーカイブ（読み取り専用）になりました。今から選ぶなら[MinIOのOSS版アーカイブ後の採用判断](/tech/details/16006/)を先に確認してください。

## 既存ファイルとSECRET\_KEYを移行する手順

運用中の環境を切り替えるときの手順です（新規構築なら不要）。移行が終わるまで既存ファイルとAPIキーが読めないので、メンテナンス時間帯に行い、実行中のワークフローや定期実行が終わったことを確かめます。`docker compose stop nginx` で入口を閉じても、サービス名なしの `docker compose up -d` はnginxも起動します。移行中は `docker compose up -d api worker worker_beat plugin_daemon` のようにサービスを指定して起動し、移行後にnginxを戻します。

### S3切り替え前のSECRET\_KEY固定

`.env` の `SECRET_KEY=` が空なら、いま使われている鍵はローカルの保存先にあります。その値を `.env` に書き写してから切り替えます。値を明示すれば、実装は保存先を見ずにその値を使います。

```
cd dify/docker
cat volumes/app/storage/.dify_secret_key
# 出力された値を docker/.env の SECRET_KEY= に書く
```

### flask migrate-ossによる既存ファイルのS3移行

DifyのAPIには、ローカル（opendal）の保存先から現在の `STORAGE_TYPE` の保存先へコピーする `flask migrate-oss` があります。`STORAGE_TYPE` が `opendal` のままだと「Target STORAGE\_TYPE must be a cloud OSS」で止まるので、S3の設定を反映してから実行します。

```
docker compose exec api flask migrate-oss --dry-run
docker compose exec api flask migrate-oss
```

既定でコピーする接頭辞は `privkeys`・`upload_files`・`image_files`・`tools`・`website_files`・`keyword_files`・`ops_trace` の7つです。`--path` を指定すると既定の7つは置き換わる（追加ではない）ため、`ls volumes/app/storage` でほかのディレクトリがあれば、まず既定どおり実行し、追加分だけを `--path` で別に流します。コピー後は `upload_files` のDBレコードの `storage_type` も書き換わります（`--no-update-db` で止められます）。読み書きのエラーがあれば `--force` なしではDB更新の前に確認が入りますが、接頭辞の走査エラーはこの確認の対象外なので、ログも見てください。

`aws s3 sync volumes/app/storage s3://dify-storage-example` でディレクトリごと送っても読み出しはできますが、DBの `storage_type` は古いままです。英語圏の記事にある `flask upload-local-files-to-cloud-storage` は1.18.0のソースに存在しません。

### S3切り替え後の保存先・認証情報の検証

ナレッジにテスト用ファイルを1つ上げ、S3の `upload_files/` 配下にオブジェクトが増えたかを確認します。Dify側の画面は保存先がローカルでも成功と表示されるので、判定はS3側で行います。あわせて、既存のモデルプロバイダーでチャットが返ること（秘密鍵の移行）と、ログインが切れていないこと（`SECRET_KEY` の固定）を確かめます。ローカルの旧データは、既存ファイルの読み出しと復元手順を確かめ、決めたロールバック期間が過ぎるまで残します。S3側の誤削除に備えるなら、バージョニングや[S3 Object Lockによるイミュータブルバックアップ](/tech/details/18742/)を組み合わせます。

## プラグインデーモンの保存先をS3へ移す設定

プラグインデーモンの保存先（インストール済みプラグインとパッケージのキャッシュ）は、`STORAGE_TYPE` とは別に `PLUGIN_STORAGE_TYPE`（既定 `local`）で決まります。単一ホストならローカルのままで困りませんが、ノードを入れ替える構成では再インストールが要ります。

注意点は書き場所です。composeの `plugin_daemon` は `environment:` で `S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-false}` のように値を組み立てており、この `${…}` はシェル環境変数、`--env-file` で指定したファイル、既定の `.env` などから展開されます。サービスの `env_file` は、この変数展開の入力にはなりません。`shared.env.example` には `PLUGIN_S3_*` の行がありますが、そこに書いても前掲の実測どおり `local` のままです。

```
# docker/.env に書く
PLUGIN_STORAGE_TYPE=aws_s3
# dify-plugin-exampleを事前作成し、このバケット用のIAM権限を追加する
# 前掲ポリシーのResourceを専用バケットのARNに置き換えたポリシーを別途付与する
PLUGIN_STORAGE_OSS_BUCKET=dify-plugin-example
PLUGIN_S3_USE_AWS=true
PLUGIN_S3_USE_AWS_MANAGED_IAM=true
PLUGIN_AWS_REGION=ap-northeast-1
```

`PLUGIN_S3_USE_AWS=true` は省けません。composeの既定は `false` で、その場合dify-cloud-kitはS3互換ストレージ用の分岐に入り、`PLUGIN_S3_ENDPOINT` を接続先URLとして使います。アクセスキー方式なら `PLUGIN_S3_USE_AWS_MANAGED_IAM` を外して `PLUGIN_AWS_ACCESS_KEY`・`PLUGIN_AWS_SECRET_KEY` を足します。プラグイン実行用の作業ディレクトリ（`PLUGIN_WORKING_PATH=/app/storage/cwd`）はローカルのままなので、`volumes/plugin_daemon` のマウントは外せません。インストール時のエラーは[Difyのプラグインがインストールできない原因と対処法](/tech/details/7098/)にまとめています。

## 保存に失敗するときの切り分け

「どこに書いたか」ではなく「コンテナに何が渡ったか」から見ます。

```
docker compose exec api env | grep -E "^(STORAGE_TYPE|S3_)"
docker compose exec plugin_daemon env | grep -E "^(PLUGIN_STORAGE_TYPE|S3_|AWS_REGION)"
```

- **`STORAGE_TYPE=opendal` が返る**：`.env` の159行目付近がそのままです。`shared.env` 側の `STORAGE_TYPE`（雛形の499行目）も `opendal` のまま残りやすい行です。
- **起動は成功するのにアップロードで失敗する**：`head_bucket` が403を返しても実装は `pass` するため、IAMポリシーの不足やバケット名の誤りは最初の `put_object` まで表に出ません。
- **起動時にCreateBucketのエラーで止まる**：バケットが存在せず、リージョン指定の無い自動作成が失敗しています。バケット名の綴りを確かめ、前述のCLIで作成します。
- **モデルを呼ぶと `Private key not found`**：`privkeys` が移っていません。`flask migrate-oss --path privkeys` を実行します。
- **既存ドキュメントが「ファイルが見つかりません」**：`get_object` の `NoSuchKey` が `FileNotFoundError` に変換されたものです。権限ではなく未移行を疑います。

AWS環境の設計から任せたい場合は、一創の[インフラ構築（AWS・Google Cloud・Azure）](/service/system/aws/)でDifyを含む構成の支援を行っています。

## よくある質問

### shared.envにSTORAGE\_TYPE=s3を書いたのに反映されないのはなぜですか？

最後に読まれる `docker/.env` に、雛形由来の `STORAGE_TYPE=opendal` が残っているためです。その行を `s3` にして `docker compose up -d` で作り直します。

### STORAGE\_TYPEをopendalのままS3を使うことはできますか？

できます。`OPENDAL_SCHEME=s3` にすると、Difyは `OPENDAL_S3_` で始まる変数を集めてOpenDALに渡します（例：`OPENDAL_S3_BUCKET`・`OPENDAL_S3_REGION`）。この経路では `S3_BUCKET_NAME` などは読まれず、`flask migrate-oss` も移行先にopendalを受け付けません。AWSのS3なら `STORAGE_TYPE=s3` の方が扱いやすいです。

### S3へ切り替えると、それまでのファイルやAPIキーはどうなりますか？

自動では移りません。APIキーを復号する秘密鍵も同じ場所にあるため、`flask migrate-oss` で `privkeys` ごとコピーします。`SECRET_KEY` が空欄の環境は、先に値を固定しないとログイン状態に影響します。

### Difyのデータをバックアップするには何を取ればよいですか？

ファイル保存先（S3またはvolumes/app/storage）に加えて、PostgreSQLのダンプ、ベクトルデータベースのボリューム、`docker/.env` と `docker/envs/` の設定ファイルが必要です。S3はバケットのバージョニングで世代を残せますが、DBと時点をそろえないと参照切れが起きます。

### MinIOなどのS3互換ストレージでも使えますか？

使えます。`S3_ENDPOINT` に互換ストレージのURLを、`S3_ADDRESS_STYLE=path` を指定します。ただし `S3_USE_AWS_MANAGED_IAM=true` の分岐ではエンドポイントが無視されるため、互換ストレージはアクセスキー方式で接続します。

## 関連記事

- [DifyをAWS EC2に構築しBedrockへIAMロール接続する手順【Dify 1.18.0対応】](/tech/details/2725/)
- [DifyをHelm chartでAWS EKSに構築する手順｜コミュニティ版0.39.0のvalues設定と落とし穴](/tech/details/7097/)
- [Difyのプラグインがインストールできない原因と対処法｜bad signature・依存エラー・容量【1.17.1対応】](/tech/details/7098/)
- [MinIOとは？S3互換オブジェクトストレージの仕組みとOSS版アーカイブ後の採用判断【2026年9月時点】](/tech/details/16006/)

---

出典: [DifyのストレージをAmazon S3に切り替える設定手順：.envの優先順位と既存ファイル・鍵の移行](<https://www.issoh.co.jp/tech/details/4131/>)（株式会社一創）
