-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat(services): add S3CompatibleImageFileStorage (first cloud impl of ImageFileStorageBase) #9182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
goanpeca
wants to merge
6
commits into
invoke-ai:main
Choose a base branch
from
goanpeca:enh/s3-image-file-storage
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
5d20ee3
feat(services): add S3CompatibleImageFileStorage (first cloud impl of…
goanpeca 9a059f3
Merge remote-tracking branch 'upstream/main' into enh/s3-image-file-s…
Pfannkuchensack 86b2b49
fix(s3): implement image_root/thumbnail_root/evict_cache_paths on S3 …
Pfannkuchensack 4d9efff
feat(s3): disable filesystem subfolder moves for the S3 backend
Pfannkuchensack 783d2f9
docs(s3): document no disk↔s3 migration and disk-only subfolder strategy
Pfannkuchensack 05a767b
Chore Typegen + openapi
Pfannkuchensack File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| # Manual S3-compatible (Backblaze B2 / AWS S3) integration tests. | ||
| # | ||
| # Runs the real-bucket tests in tests/integration against an ACTUAL S3-compatible | ||
| # object store, using credentials from repository secrets. This is intentionally | ||
| # NOT part of normal PR CI: it is opt-in (workflow_dispatch) so it can be run on | ||
| # demand before taking the S3 image-storage backend out of draft, or before a | ||
| # release. | ||
| # | ||
| # Required repository secrets (a Backblaze B2 test bucket can be provided for this): | ||
| # CI_TEST_S3_BUCKET e.g. invokeai-ci | ||
| # CI_TEST_S3_ENDPOINT_URL e.g. https://s3.us-west-004.backblazeb2.com (leave empty for AWS S3) | ||
| # CI_TEST_S3_REGION e.g. us-west-004 | ||
| # CI_TEST_S3_ACCESS_KEY_ID B2 keyID (or AWS access key id) | ||
| # CI_TEST_S3_SECRET_ACCESS_KEY B2 applicationKey (or AWS secret access key) | ||
| # | ||
| # Without the secrets the tests skip cleanly, so this workflow is safe to merge | ||
| # before a bucket is provisioned. | ||
|
|
||
| name: 's3 integration' | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| s3-integration: | ||
| name: 's3 integration (real bucket)' | ||
| runs-on: ubuntu-24.04 | ||
| timeout-minutes: 20 | ||
| env: | ||
| PIP_USE_PEP517: '1' | ||
| UV_SYSTEM_PYTHON: 1 | ||
|
|
||
| steps: | ||
| - name: checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: setup uv | ||
| uses: astral-sh/setup-uv@v8.1.0 | ||
| with: | ||
| version: '0.6.10' | ||
| enable-cache: true | ||
| python-version: '3.11' | ||
|
|
||
| - name: setup python | ||
| uses: actions/setup-python@v6 | ||
| with: | ||
| python-version: '3.11' | ||
|
|
||
| - name: install dependencies | ||
| env: | ||
| UV_INDEX: 'https://download.pytorch.org/whl/cpu' | ||
| run: uv pip install --editable ".[test,s3]" | ||
|
|
||
| - name: run s3 integration tests | ||
| env: | ||
| CI_TEST_S3_BUCKET: ${{ secrets.CI_TEST_S3_BUCKET }} | ||
| CI_TEST_S3_ENDPOINT_URL: ${{ secrets.CI_TEST_S3_ENDPOINT_URL }} | ||
| CI_TEST_S3_REGION: ${{ secrets.CI_TEST_S3_REGION }} | ||
| CI_TEST_S3_ACCESS_KEY_ID: ${{ secrets.CI_TEST_S3_ACCESS_KEY_ID }} | ||
| CI_TEST_S3_SECRET_ACCESS_KEY: ${{ secrets.CI_TEST_S3_SECRET_ACCESS_KEY }} | ||
| run: pytest -m s3_integration tests/integration/test_image_files_s3_real.py -v | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| --- | ||
| title: Object Storage (S3-Compatible) | ||
| sidebar: | ||
| order: 4 | ||
| --- | ||
|
|
||
| import { Aside, Tabs, TabItem } from '@astrojs/starlight/components' | ||
|
|
||
| InvokeAI can store generated images in any S3-compatible object store | ||
| instead of the local filesystem. This is intended for **multi-user and | ||
| hosted Invoke deployments** where the application server is ephemeral | ||
| (containers, autoscaled instances, Kubernetes pods) and image artifacts | ||
| need to live somewhere durable and shared across replicas. | ||
|
|
||
| The implementation lives in | ||
| `invokeai/app/services/image_files/image_files_s3.py` and works with | ||
| AWS S3, [Backblaze B2](https://www.backblaze.com/cloud-storage), and | ||
| any other provider that speaks the S3 API. | ||
|
|
||
| <Aside type="note" title="Image files only — latents and presigned-URL delivery are follow-ups"> | ||
| This release covers image files (`ImageFileStorageBase`). Latents | ||
| serialization (`ObjectSerializerBase`) and presigned-URL frontend | ||
| delivery (`UrlServiceBase`) still use the disk backend; both are | ||
| tracked as separate follow-up PRs. | ||
| </Aside> | ||
|
|
||
| ## When to use object storage | ||
|
|
||
| Use the S3 backend when **any** of these apply: | ||
|
|
||
| - You're running InvokeAI behind a load balancer with more than one | ||
| replica — they need to share a single image gallery. | ||
| - Your application servers are ephemeral and a local volume would not | ||
| persist across restarts. | ||
| - You want to back up, version, or apply lifecycle rules to generated | ||
| images using your object-store provider's tooling. | ||
| - You're hosting a multi-tenant Invoke instance and want to keep | ||
| artifacts off the application server entirely. | ||
|
|
||
| For a single-user desktop install, **stay on the disk backend** — it's | ||
| simpler, faster, and benefits from an in-process LRU cache. | ||
|
|
||
| ## Selecting the backend | ||
|
|
||
| The backend is selected by the `storage_backend` setting. Like all | ||
| `InvokeAIAppConfig` fields (`s3_bucket`, `s3_endpoint_url`, `s3_region`, ...), | ||
| it can be set in `invokeai.yaml` or via the equivalent `INVOKEAI_`-prefixed | ||
| environment variable: | ||
|
|
||
| ```sh | ||
| # Default; uses local filesystem under the InvokeAI root | ||
| INVOKEAI_STORAGE_BACKEND=disk | ||
|
|
||
| # Use any S3-compatible bucket | ||
| INVOKEAI_STORAGE_BACKEND=s3 | ||
| ``` | ||
|
|
||
| When `s3` is selected, the variables in the next section are required. | ||
|
|
||
| <Aside type="caution" title="Pick one backend at deploy time — there is no migration"> | ||
| `storage_backend` is a deployment-level decision, not a setting you | ||
| toggle on a live install. **There is no migration between `disk` and | ||
| `s3`**: images written to one backend are invisible to the other, so | ||
| switching an existing instance orphans its entire gallery (the database | ||
| still lists the images, but their files live in the backend you left | ||
| behind). Choose the backend before generating images, and keep it fixed | ||
| for the life of the deployment. | ||
|
|
||
| The S3 backend exists for **hosted and multi-replica deployments** that | ||
| want their image output decoupled from any single server's local | ||
| filesystem — ephemeral containers, autoscaled instances, shared storage | ||
| across replicas. A single-user desktop install should stay on `disk`. | ||
| </Aside> | ||
|
|
||
| <Aside type="note" title="Install the s3 extra"> | ||
| The S3 backend depends on `boto3`, which is an optional dependency. Install it with the `s3` extra: `pip install "invokeai[s3]"` (or `uv pip install "invokeai[s3]"`). Without it, selecting `storage_backend=s3` raises an `ImportError` at startup. | ||
| </Aside> | ||
|
|
||
| ## Required configuration | ||
|
|
||
| | Variable | Required | Description | | ||
| | ----------------------------- | :------: | -------------------------------------------------------------------------------------------- | | ||
| | `INVOKEAI_S3_BUCKET` | yes | Bucket / container name. Must already exist; InvokeAI does not create it. | | ||
| | `INVOKEAI_S3_ENDPOINT_URL` | *see* | Provider endpoint. Required for non-AWS providers (e.g. B2's `https://s3.us-west-004.backblazeb2.com`). Omit to talk to AWS S3. | | ||
| | `INVOKEAI_S3_REGION` | no | Region name. If unset, boto3's normal resolution applies (`AWS_REGION` / `AWS_DEFAULT_REGION` / AWS config). Some S3-compatible providers require a specific region for signing (e.g. B2's `us-west-004`). | | ||
|
|
||
| ### Credentials | ||
|
|
||
| Credentials follow boto3's standard chain: `AWS_ACCESS_KEY_ID` and | ||
| `AWS_SECRET_ACCESS_KEY`, instance profiles, IRSA, `~/.aws/credentials`, | ||
| etc. For Backblaze deployments there's a convenience mapping — see | ||
| below. | ||
|
|
||
| ## Backblaze B2 | ||
|
|
||
| InvokeAI honors the standard B2 environment variable names so you can | ||
| use the same credentials you'd give any other B2 tool. If | ||
| `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` are unset and these are | ||
| present, they are mapped onto the AWS names when the boto3 client is | ||
| constructed (the process environment is **not** mutated). | ||
|
|
||
| ```sh | ||
| INVOKEAI_STORAGE_BACKEND=s3 | ||
| INVOKEAI_S3_BUCKET=my-invokeai-images | ||
| INVOKEAI_S3_ENDPOINT_URL=https://s3.us-west-004.backblazeb2.com | ||
| INVOKEAI_S3_REGION=us-west-004 | ||
|
|
||
| B2_APPLICATION_KEY_ID=000xxxxxxxxxxxxxxxxxxxxx | ||
| B2_APPLICATION_KEY=K000xxxxxxxxxxxxxxxxxxxxxxxxxxx | ||
| ``` | ||
|
|
||
| The endpoint shown above (`s3.us-west-004.backblazeb2.com`) is the | ||
| default for B2 buckets in the `us-west-004` region — replace | ||
| `us-west-004` with whichever region your bucket lives in. You can find | ||
| the exact endpoint on the bucket's detail page in the B2 console. | ||
|
|
||
| <Aside type="tip" title="Application keys, not master keys"> | ||
| Create a B2 **application key** scoped to just the bucket InvokeAI | ||
| will use. This limits blast radius if the credentials leak. | ||
| </Aside> | ||
|
|
||
| ## Other providers | ||
|
|
||
| <Tabs syncKey="objectStorageProvider"> | ||
| <TabItem label="AWS S3" icon="cloud-download"> | ||
| ```sh | ||
| INVOKEAI_STORAGE_BACKEND=s3 | ||
| INVOKEAI_S3_BUCKET=my-invokeai-images | ||
| INVOKEAI_S3_REGION=us-east-1 | ||
| # Endpoint URL omitted: boto3 talks to AWS by default. | ||
| # Credentials via instance profile, IRSA, or AWS_ACCESS_KEY_ID/SECRET. | ||
| ``` | ||
| </TabItem> | ||
| <TabItem label="Backblaze B2" icon="cloud-download"> | ||
| ```sh | ||
| INVOKEAI_STORAGE_BACKEND=s3 | ||
| INVOKEAI_S3_BUCKET=my-invokeai-images | ||
| INVOKEAI_S3_ENDPOINT_URL=https://s3.us-west-004.backblazeb2.com | ||
| INVOKEAI_S3_REGION=us-west-004 | ||
| B2_APPLICATION_KEY_ID=... | ||
| B2_APPLICATION_KEY=... | ||
| ``` | ||
| </TabItem> | ||
| </Tabs> | ||
|
|
||
| Other S3-compatible providers work the same way: set | ||
| `INVOKEAI_S3_ENDPOINT_URL` to their endpoint and supply the appropriate | ||
| `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`. | ||
|
|
||
| ## Object layout | ||
|
|
||
| Inside the bucket, InvokeAI uses two prefixes that mirror the on-disk | ||
| layout so the database's image-name references continue to resolve | ||
| unambiguously: | ||
|
|
||
| ``` | ||
| <bucket>/ | ||
| images/<image-name>.png | ||
| thumbnails/<image-name>.webp | ||
| ``` | ||
|
|
||
| Images are always stored **flat** under these two prefixes. The | ||
| [`image_subfolder_strategy`](/configuration/invokeai-yaml/#image-subfolder-strategy) | ||
| setting (`date`, `type`, `hash`) is a disk-only feature and has no effect | ||
| on S3: object storage has no directory structure to organize, and the | ||
| strategy cannot be changed after the fact because there is no in-place | ||
| move/reorganize operation for object stores. Accordingly, the subfolder | ||
| strategy selector and the Image Storage Maintenance panel are hidden in | ||
| the app's Settings when the S3 backend is active. | ||
|
|
||
| InvokeAI also writes per-object user-metadata (`invokeai-metadata`, | ||
| `invokeai-workflow`, `invokeai-graph`) so workflow and graph lookups | ||
| can be served by a cheap `HEAD` request without downloading the full | ||
| PNG. | ||
|
|
||
| ## Performance trade-offs | ||
|
|
||
| <Aside type="note" title="Known trade-off: first-byte latency"> | ||
| The disk backend keeps recently-accessed images in an in-process LRU | ||
| cache, so re-reads are essentially free. The S3 backend is currently | ||
| **stateless** — every read goes to the object store, which means | ||
| first-byte latency is meaningfully higher than disk, especially for | ||
| gallery scrolls that touch many thumbnails. | ||
|
|
||
| This is by design for the initial release: it keeps the implementation | ||
| small and avoids cache-coherency bugs across replicas. A read-through | ||
| local cache is a planned follow-up, gated on profiling. | ||
| </Aside> | ||
|
|
||
| In practice this is not a problem for most multi-user deployments — | ||
| the frontend already paginates the gallery and modern object stores | ||
| are fast enough to keep the UI responsive. Single-user desktop | ||
| installs should not switch to S3 just for the architectural symmetry; | ||
| the disk backend is faster for that case. | ||
|
|
||
| ## Operational notes | ||
|
|
||
| - **Bucket pre-creation.** The bucket must exist before InvokeAI | ||
| starts. The application does not create it and will fail loudly on | ||
| first write if the bucket is missing. | ||
| - **Credential rotation.** Restart the Invoke process to pick up new | ||
| credentials — the boto3 client is built once at startup. | ||
| - **Versioned buckets (B2).** B2 buckets retain prior object versions | ||
| by default. Use a lifecycle rule to age out hidden versions if you | ||
| don't want deleted images to linger. | ||
| - **Server-side encryption / object lock.** Configure these on the | ||
| bucket itself; InvokeAI passes uploads through unchanged. | ||
|
|
||
| ## Follow-ups | ||
|
|
||
| The current scaffold covers image files only. Latents serialization | ||
| and presigned-URL frontend delivery (so browsers fetch directly from | ||
| the bucket) are tracked as separate follow-ups; until those land, | ||
| deployments using the S3 image backend will still have the application | ||
| server proxy image bytes to the browser. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.