Skip to Content

Media Storage

Sitepins requires an S3-compatible object storage bucket for application-level assets. Before configuring your bucket, it is essential to understand the architectural distinction between application media and website content media.

Two Different Kinds of Media

KindConcrete ExamplesStorage DestinationWhy?
Website Content MediaBlog post images, illustrations, PDF downloads, hero bannersYour Git RepositoryVersioned alongside your Markdown and MDX documents. When you clone or build your static site generator, your images are right there.
Application UI AssetsUser profile pictures (avatars), organization thumbnails, project cover imagesThe S3 BucketThese belong to the CMS interface, not to any individual website repository.

Because website media is stored directly in your Git repository, a misconfigured S3 bucket will never break your website content or images. Only user avatars and project cover cards rely on the bucket.


Supported S3 Providers

Any storage service implementing the standard S3 API is supported:

  • AWS S3
  • Cloudflare R2 (Zero egress fees)
  • MinIO (Self-hosted on your own hardware)
  • DigitalOcean Spaces
  • Backblaze B2

Provider Setup Examples

1. Amazon Web Services (AWS S3)

In api/.env:

S3_ENDPOINT="https://s3.us-east-1.amazonaws.com" S3_REGION="us-east-1" S3_ACCESS_KEY="AKIAIOSFODNN7EXAMPLE" S3_SECRET_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" S3_BUCKET_NAME="my-sitepins-assets" S3_FORCE_PATH_STYLE=false

In app/.env:

NEXT_PUBLIC_BUCKET_URL="https://my-sitepins-assets.s3.us-east-1.amazonaws.com"

2. Cloudflare R2

Cloudflare R2 requires setting S3_REGION="auto" and using your unique account endpoint:

In api/.env:

S3_ENDPOINT="https://<ACCOUNT_ID>.r2.cloudflarestorage.com" S3_REGION="auto" S3_ACCESS_KEY="<R2_ACCESS_KEY_ID>" S3_SECRET_KEY="<R2_SECRET_ACCESS_KEY>" S3_BUCKET_NAME="sitepins-assets" S3_FORCE_PATH_STYLE=false

In app/.env (use your R2 public bucket URL or custom domain):

NEXT_PUBLIC_BUCKET_URL="https://pub-<hash>.r2.dev" # or with a custom domain: # NEXT_PUBLIC_BUCKET_URL="https://assets.example.com"

3. MinIO (Self-Hosted)

MinIO gateways require path-style access (http://endpoint/bucket instead of http://bucket.endpoint):

In api/.env:

S3_ENDPOINT="http://minio:9000" S3_REGION="us-east-1" S3_ACCESS_KEY="minioadmin" S3_SECRET_KEY="minioadmin" S3_BUCKET_NAME="sitepins-assets" S3_FORCE_PATH_STYLE=true

In app/.env (publicly accessible MinIO URL):

NEXT_PUBLIC_BUCKET_URL="http://localhost:9000/sitepins-assets"

4. DigitalOcean Spaces

In api/.env:

S3_ENDPOINT="https://sgp1.digitaloceanspaces.com" S3_REGION="sgp1" S3_ACCESS_KEY="<DO_SPACES_KEY>" S3_SECRET_KEY="<DO_SPACES_SECRET>" S3_BUCKET_NAME="my-spaces-bucket" S3_FORCE_PATH_STYLE=false

In app/.env:

NEXT_PUBLIC_BUCKET_URL="https://my-spaces-bucket.sgp1.digitaloceanspaces.com"

Existing setups configured with legacy variables (DOS_PUBLIC_ACCESS_KEY, DOS_PUBLIC_SECRET_KEY, DOS_BUCKET_NAME, and DOS_REGION) remain backward-compatible and auto-derive endpoints automatically.


Bucket CORS Configuration (Mandatory)

Browsers upload assets directly to your storage bucket or display them in image components. You must configure CORS on your bucket to allow requests from your web app origin.

AWS S3 / MinIO CORS JSON:

[ { "AllowedHeaders": ["*"], "AllowedMethods": ["GET", "PUT", "POST", "HEAD"], "AllowedOrigins": [ "http://localhost:3000", "https://cms.example.com" ], "ExposeHeaders": ["ETag"], "MaxAgeSeconds": 3000 } ]

DigitalOcean Spaces / S3 XML format:

<CORSConfiguration> <CORSRule> <AllowedOrigin>http://localhost:3000</AllowedOrigin> <AllowedOrigin>https://cms.example.com</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedMethod>PUT</AllowedMethod> <AllowedMethod>POST</AllowedMethod> <AllowedMethod>HEAD</AllowedMethod> <AllowedHeader>*</AllowedHeader> <ExposeHeader>ETag</ExposeHeader> <MaxAgeSeconds>3000</MaxAgeSeconds> </CORSRule> </CORSConfiguration>

Verifying Your Configuration

  1. Log in to Sitepins and navigate to Account β†’ Profile & Security.
  2. Click Upload Display Picture and select an image.
  3. If the upload succeeds and your avatar displays immediately:
    • Your S3_* credentials on the API are working.
    • Your NEXT_PUBLIC_BUCKET_URL on the Web app is correctly deriving the image host.
  4. Troubleshooting:
    • Upload succeeds, but image is broken: NEXT_PUBLIC_BUCKET_URL does not point to the correct public bucket origin, or the bucket permissions are not publicly readable.
    • Upload fails with a browser CORS error: Update your bucket’s CORS rules to include your web app origin.
    • MinIO signature failure: Ensure S3_FORCE_PATH_STYLE=true.
Last updated on