S3-compatible buckets (the s3 media store)

Keep your Reels and photos in a bucket: AWS S3, Cloudflare R2, Backblaze B2, MinIO or any service that speaks the S3 API. Bongsu reads the bucket with a key you give it, hands Instagram short-lived signed links straight to the files (the media never passes through Bongsu), lets the app upload straight to the bucket, picks up files dropped in an inbox folder and moves posted media aside. Nothing is ever deleted. A bucket can be your only store or sit next to Google Drive; the dashboard's Media page sets each one up.

1. The bucket and a key

Make a private bucket (no public access: Bongsu signs every link), then a key that can read and write that bucket only.

Service The key
AWS S3 IAM → a user (or role) with the policy below; Security credentials → Create access key
Cloudflare R2 R2 → Manage API tokens → Create API token: Object Read & Write, applied to this bucket only. Copy the Access Key ID and Secret Access Key (not the token value)
Backblaze B2 Application Keys → Add a New Application Key: allow access to this bucket only, Read and Write
MinIO and others a user or service account with read and write on the bucket

The AWS policy (replace bongsu-media; with a folder in the bucket, narrow the paths to it):

{
  "Version": "2012-10-17",
  "Statement": [
    { "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": "arn:aws:s3:::bongsu-media" },
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::bongsu-media/*"
    }
  ]
}

DeleteObject is only for moves: a bucket has no "move", so moving posted media to UPLOADS/_posted/ copies each file and then removes the original, once every copy exists.

2. Add it in the dashboard

Dashboard → Media → Add a bucket (instance admins; it serves every workspace, each in its own folder). A workspace's admins add a bucket of their own in Settings → Media; the paths below use UPLOADS, the folder of a workspace kept from before this release, so read it as the workspace's folder (shown in Settings → Media). Bongsu refuses keys with ., .. or empty segments. Service fills in the endpoint and region; replace what's in brackets:

Service Endpoint Region Path style
AWS S3 https://s3.<region>.amazonaws.com the bucket's (eu-west-1) off
Cloudflare R2 https://<account id>.r2.cloudflarestorage.com auto on
Backblaze B2 https://s3.<region>.backblazeb2.com (the bucket page's Endpoint) the part after s3. (us-west-004) off
MinIO and others the server's address what the server uses (us-east-1 if unsure) on

Then the bucket name, an optional folder in the bucket (Bongsu keeps everything under it, so one bucket can serve other things too) and the key. Test and save checks the bucket answers, reading and writing (it makes UPLOADS/inbox/; for the instance's bucket, in the first active workspace's folder, shown in its Settings → Media), and only then saves it; the key is stored encrypted (the instance needs ENCRYPTION_KEY, Configuration) and never shown again. To change the bucket later, leave both key fields empty to keep the saved key. The first store you add is the default; Make default changes it.

The endpoint must be reachable from the internet. Instagram downloads each file from the bucket itself, through a signed link: a MinIO on your home network works for the inbox and the app, but Instagram can't fetch from it.

A workspace's own bucket is at a public https:// name. Testing and saving one makes the server send requests to the endpoint, so a workspace admin's endpoint can't be http://, an IP address (https://10.0.0.5, https://[::1]), localhost or a private network's name (one word like https://minio, or ending in .local, .internal, .lan); Test asks for your passkey first. The instance's buckets, set up by the instance admins in Media, have no such limit (a MinIO beside the server is fine there). For any bucket, Bongsu doesn't follow a redirect from the endpoint: it fails with "the endpoint answered with a redirect", so give the endpoint the bucket really answers at.

3. Uploads from the app: the CORS rule

The app uploads to the bucket from the browser, so the bucket must allow PUT from your instance's address. Without the rule, everything else works and the upload fails with a network error.

AWS S3 (bucket → Permissions → Cross-origin resource sharing) and Cloudflare R2 (bucket → Settings → CORS policy), with your PUBLIC_URL:

[
  {
    "AllowedOrigins": ["https://bongsu.example.com"],
    "AllowedMethods": ["PUT"],
    "AllowedHeaders": ["Content-Type"],
    "MaxAgeSeconds": 3600
  }
]

Backblaze B2 (bucket → CORS Rules → custom, or b2 bucket update --cors-rules):

[
  {
    "corsRuleName": "bongsu-uploads",
    "allowedOrigins": ["https://bongsu.example.com"],
    "allowedOperations": ["s3_put"],
    "allowedHeaders": ["content-type"],
    "maxAgeSeconds": 3600
  }
]

MinIO answers every origin unless it's configured otherwise; other servers: see their CORS settings.

4. The folders

The same layout as on Drive, as key prefixes under the bucket (or its folder):

Path Use
UPLOADS/<channel>/CONTENT/<project>/exports/shorts/instagram/<name>.mp4 a planned Reel from a repo layout
UPLOADS/<channel>/CONTENT/<project>/posts/<post>/ a planned photo, carousel or Story
UPLOADS/inbox/<project>/ drop media here (with an optional <name>.txt caption) to get a draft in the app
UPLOADS/_posted/ where posted media moves a day after it went out
UPLOADS/_uploads/ files uploaded from the app (each with a short random suffix, so nothing is replaced)

Upload with the service's console, aws s3 cp, rclone or any S3 tool. Planned items given by path are looked up in the default store; drafts remember the store their files are in.

Good to know

  • Checksums: an approval records each file's ETag. Uploading a file again, even the same one, may give it a new ETag (large files are uploaded in parts, and the parts decide it), and then the post needs approving again.
  • Moving files: a file's key is its only name, so a file you move or rename in the bucket is a new file to Bongsu: approve it again. The cleanup's own moves happen after posting, when nothing needs the old name.
  • Limits: one file up to 1 GB from the app and up to 5 GB for a move (a single copy); Instagram's own limits are lower.
  • A key that stops working: Bongsu checks every store weekly; a store that doesn't answer shows as needing attention in the dashboard and the app, and you get one notification.
  • Traffic: Instagram downloads each file from the bucket when publishing, which counts as download traffic on services that charge for it (R2 has no egress fees).

Edit this page