# `Ankusa.BlobStore.S3`
[🔗](https://github.com/jamescarr/ankusa/blob/ankusa-v0.7.0/packages/ankusa/lib/ankusa/blob_store/s3.ex#L1)

`Ankusa.BlobStore` backed by S3 or any S3-compatible endpoint (MinIO,
Cloudflare R2, or the `floci` emulator in `docker-compose.integration.yml`).

Requests are signed with
[`aws_signature`](https://hex.pm/packages/aws_signature) — the SigV4
implementation behind the official aws-elixir SDK — and sent with
[`Req`](https://hex.pm/packages/req). Signing is exactly the kind of code not
to hand-roll: a canonicalization bug is invisible until it fails in
production, and the failure mode is a signature mismatch on someone else's
infrastructure.

opts:

  * `:bucket`            — required
  * `:region`            — required, e.g. `"us-east-1"`
  * `:access_key_id`, `:secret_access_key`, `:session_token` — static
                            credentials. Without them the chain in
                            `Ankusa.AWS.Credentials` runs: the
                            `AWS_*` environment, web identity (IRSA), then
                            the EC2 instance role (IMDSv2)
  * `:endpoint`          — default `"https://s3.#{region}.amazonaws.com"`;
                            point at `http://localhost:4566` for floci/MinIO
  * `:timeout_ms`        — default `10_000`, for both connect and response of one
                            request that carries at most 1 MiB
  * `:upload_timeout_ms`, `:op_deadline_ms`, `:multipart_threshold_bytes`,
    `:multipart_part_bytes`, `:breaker_*`, `:pool_*` — one deadline per
                            operation, multipart above a size, a breaker per
                            bucket and a connection pool per endpoint. See
                            `Ankusa.BlobStore.Options`
  * `:req_options`       — transport options for the HTTP client, e.g. a
                            custom Finch pool (`finch: [name: MyFinch]`), a
                            proxy (via `:connect_options`), or `plug:` for
                            `Req.Test` in tests. See `Ankusa.HttpClient` —
                            an allowlist, because a redirected or re-tuned
                            request would no longer match its signature.
  * `:retries`           — default `2`: extra attempts after a transport
                            error or a `408`/`429`/`5xx`, each re-signed,
                            jittered, `Retry-After` honoured. See
                            `Ankusa.BlobStore.Retry`
  * `:sse`               — `%{type: :aes256}` or `%{type: :kms, key_id: arn}`:
                            server-side encryption, sent (and signed) as
                            `x-amz-server-side-encryption` on every `put/4`.
                            See `Ankusa.BlobStore.SSE`

`put_if_absent/4` sends `If-None-Match: *` (a `412`/`409` is `:already_exists`);
every `PUT` carries `x-amz-checksum-sha256` and a `GET` verifies it
(`Ankusa.BlobStore.Checksum`); a body over `multipart_threshold_bytes` is a
multipart upload (create, parts each with `Content-MD5`, complete, abort on
failure).

Addressing is always path-style (`{endpoint}/{bucket}/{key}`) — the one
scheme every target (AWS, MinIO, R2, floci) accepts unambiguously.

## Local dev

    config :ankusa,
      storage: %{
        blob_store:
          {Ankusa.BlobStore.S3,
           bucket: "ankusa-segments-dev",
           region: "us-east-1",
           endpoint: "http://localhost:4566",
           access_key_id: "test",
           secret_access_key: "test"}
      }

`docker compose -f docker-compose.integration.yml up -d floci s3-bootstrap` brings up
the emulator and creates the bucket.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
