Attached.StorageBackends.S3 (Attached v0.2.0)

Copy Markdown View Source

Stores files on Amazon S3 or any S3-compatible service (MinIO, Cloudflare R2, DigitalOcean Spaces, ...).

HTTP goes through the optional req dependency — newly generated Phoenix apps already include it; add it otherwise:

{:req, "~> 0.5"}

Request signing (SigV4) is implemented in-house — no AWS SDK or signing library required.

Configuration

config :attached,
  storage_backends: [
    s3_main: {Attached.StorageBackends.S3,
      bucket: "my-bucket",
      region: "eu-central-1",
      access_key_id: System.fetch_env!("AWS_ACCESS_KEY_ID"),
      secret_access_key: System.fetch_env!("AWS_SECRET_ACCESS_KEY")}
  ]

The same module can back several named instances (e.g. a second bucket for backups) — each entry carries its own config.

Optional instance config keys:

  • :endpoint — base URL of an S3-compatible service, e.g. "http://localhost:9000" (MinIO) or "https://<account>.r2.cloudflarestorage.com" (R2). When set, path-style addressing is used (<endpoint>/<bucket>/<key>); without it, virtual-host style against AWS (https://<bucket>.s3.<region>.amazonaws.com/<key>).
  • :session_token — STS session token for temporary credentials.
  • :url_expires_in — presigned URL lifetime in seconds (default 300).
  • :response_content_type — when true (the default), url/3 looks up the original/variant content type in the database and bakes it into the presigned URL as response-content-type, so browsers receive the real MIME type instead of S3's stored default. Set false to skip the lookup (e.g. when no repo is configured).
  • :req_options — extra options merged into every Req.request/1 (timeouts, instrumentation, plug: {Req.Test, ...} for tests).

URLs

url/3 returns a presigned S3 GET URL — files are served directly from S3, Attached.Web.Plug is not involved. The lifetime defaults to :url_expires_in and can be overridden per call with expires_in:.

Note: Attached.url/2,3 passes keys through Attached.Web.Signer.sign/1 before they reach this backend, so url/3 first unwraps that token back to the raw storage key (verifying the HMAC when secret_key_base is configured) and then presigns. The Signer's own :url_expires_in plays no role for S3 — only the presign expiry does.

Limitations

  • upload/4 and compose/3 buffer file contents in memory. Fine for typical attachment sizes; multipart upload for very large files is a candidate for a future version.
  • compose/3 concatenates by download + re-upload. S3's server-side compose (multipart UploadPartCopy) requires 5 MB minimum part sizes and is not used.