Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/workflows/s3-integration.yml
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'
Comment thread
goanpeca marked this conversation as resolved.

- 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
4 changes: 4 additions & 0 deletions docs/src/content/docs/configuration/invokeai-yaml.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ Available strategies:

Changing this setting only affects newly-created images. Existing images remain in their current locations unless you run [Image Storage Maintenance](/features/image-storage-maintenance/).

:::caution[Disk backend only]
This setting has no effect when [`storage_backend`](/configuration/object-storage/) is set to `s3`. Object storage has no directory structure to organize and cannot be reorganized in place (there is no move operation for S3), so the S3 backend always stores images flat. The strategy selector and Image Storage Maintenance are hidden in the Settings panel when the S3 backend is active.
:::

#### Logging

Several different log handler destinations are available, and multiple destinations are supported by providing a list:
Expand Down
215 changes: 215 additions & 0 deletions docs/src/content/docs/configuration/object-storage.mdx
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.
4 changes: 4 additions & 0 deletions docs/src/content/docs/features/image-storage-maintenance.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ InvokeAI can move existing images into the folder layout selected by [`image_sub

This operation changes where image files and thumbnails are stored on disk. It does not change image names, boards, generation metadata, or gallery records.

:::note[Disk backend only]
Image Storage Maintenance applies to the local **disk** backend. It is not available when [`storage_backend`](/configuration/object-storage/) is `s3` — object storage is always flat and has no move operation, so the maintenance panel is hidden in Settings for S3 deployments.
:::

## Access

Image storage maintenance is available from the in-application Settings panel.
Expand Down
49 changes: 48 additions & 1 deletion docs/src/generated/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@
{
"category": "PATHS",
"default": "flat",
"description": "Strategy for organizing images into subfolders. 'flat' stores all images in a single folder. 'date' organizes by YYYY/MM/DD. 'type' organizes by image category. 'hash' uses first 2 characters of UUID for filesystem performance.",
"description": "Strategy for organizing images into subfolders. 'flat' stores all images in a single folder. 'date' organizes by YYYY/MM/DD. 'type' organizes by image category. 'hash' uses first 2 characters of UUID for filesystem performance. Disk backend only: has no effect when storage_backend=\"s3\" (S3 always stores images flat, since object storage cannot be reorganized in place).",
"env_var": "INVOKEAI_IMAGE_SUBFOLDER_STRATEGY",
"literal_values": [
"flat",
Expand Down Expand Up @@ -251,6 +251,53 @@
"type": "<class 'pathlib.Path'>",
"validation": {}
},
{
"category": "STORAGE",
"default": "disk",
"description": "Backend for storing generated images. \"disk\" uses the local filesystem; \"s3\" uses any S3-compatible object store (AWS S3, Backblaze B2, etc.). Choose this once at deploy time: there is no migration between backends, and images written to one are not visible to the other.",
"env_var": "INVOKEAI_STORAGE_BACKEND",
"literal_values": [
"disk",
"s3"
],
"name": "storage_backend",
"required": false,
"type": "typing.Literal['disk', 's3']",
"validation": {}
},
{
"category": "STORAGE",
"default": null,
"description": "Bucket name for the s3 storage backend. Required when storage_backend=\"s3\".",
"env_var": "INVOKEAI_S3_BUCKET",
"literal_values": [],
"name": "s3_bucket",
"required": false,
"type": "typing.Optional[str]",
"validation": {}
},
{
"category": "STORAGE",
"default": null,
"description": "Endpoint URL for the s3 storage backend. Leave unset to talk to AWS S3; set to a provider-specific URL (e.g. https://s3.us-west-004.backblazeb2.com for Backblaze B2) for any other S3-compatible store.",
"env_var": "INVOKEAI_S3_ENDPOINT_URL",
"literal_values": [],
"name": "s3_endpoint_url",
"required": false,
"type": "typing.Optional[str]",
"validation": {}
},
{
"category": "STORAGE",
"default": null,
"description": "Region name for the s3 storage backend. Optional; if unset, the standard AWS region resolution applies (AWS_REGION / AWS_DEFAULT_REGION / AWS config). For Backblaze B2, set the region embedded in your endpoint, e.g. us-west-004.",
"env_var": "INVOKEAI_S3_REGION",
"literal_values": [],
"name": "s3_region",
"required": false,
"type": "typing.Optional[str]",
"validation": {}
},
{
"category": "LOGGING",
"default": [
Expand Down
Loading
Loading