Buckets
S3-compatible storage for user uploads, media, exports and backups. Works with django-storages, boto3, the AWS CLI and every S3 SDK.
When to use a bucket
An app’s own folder is rebuilt on every deploy, so files your users upload there are lost the next time you deploy. Put them in a bucket instead: it keeps files apart from your code, serves them over HTTPS, and works with the S3 libraries your framework already has.
Buckets is a $3/month add-on on any plan: 5 GB stored, 500,000 Class A writes and 5 million Class B reads a month, then $0.03 per GB-month, $6 per million writes and $0.50 per million reads from your points. The $10/month Developer pack includes Functions, Queues, Tunnels and Buckets with double the allowances. Downloads are always free. See Pricing below.
Create a bucket and a key
- Open Buckets in the dashboard sidebar. Without the add-on the page asks you to pay first and unlocks as soon as the payment is confirmed. Then choose New bucket. Names are 3 to 63 lowercase letters, numbers and dashes, and are shared by everyone on Rimbra, so shop-media-prod is a better pick than media.
- Leave it Private unless every file in it is meant for anyone to see.
- Under Access keys choose New key. Pick Read and write or Read only, and all buckets or just the ones this app needs.
- Copy the variables it shows into your application’s Variables tab. The secret is shown only once; if you lose it, make a new key and revoke the old one.
AWS_ACCESS_KEY_ID=RK…
AWS_SECRET_ACCESS_KEY=…
AWS_ENDPOINT_URL_S3=https://s3.rimbra.app
AWS_REGION=autoDjango
Install django-storages and boto3 (pip install django-storages boto3), then point Django’s default storage at your bucket. FileField and ImageField uploads, default_storage and file.url all use it from then on.
# settings.py
import os
STORAGES = {
"default": {"BACKEND": "storages.backends.s3.S3Storage"},
"staticfiles": {"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"},
}
AWS_ACCESS_KEY_ID = os.environ["AWS_ACCESS_KEY_ID"]
AWS_SECRET_ACCESS_KEY = os.environ["AWS_SECRET_ACCESS_KEY"]
AWS_STORAGE_BUCKET_NAME = "shop-media"
AWS_S3_ENDPOINT_URL = "https://s3.rimbra.app"
AWS_S3_REGION_NAME = "auto"
AWS_S3_ADDRESSING_STYLE = "path"
AWS_S3_SIGNATURE_VERSION = "s3v4"
AWS_DEFAULT_ACL = None
AWS_S3_FILE_OVERWRITE = False
# Private bucket: file.url is a signed link that lasts an hour.
AWS_QUERYSTRING_AUTH = True
# Public bucket instead: plain links with no signature.
# AWS_QUERYSTRING_AUTH = False
# AWS_S3_CUSTOM_DOMAIN = "s3.rimbra.app/shop-media"Django notes
- Moving from local MEDIA_ROOT: upload the existing folder once with aws s3 sync ./media s3://shop-media/ (see the AWS CLI below). Saved paths in your database stay the same.
- Static files can stay on your app (whitenoise or Django’s static serving). Only user uploads need the bucket.
- AWS_S3_FILE_OVERWRITE = False keeps two uploads with the same name apart, as Django does on disk.
- If your settings set AWS_DEFAULT_ACL = "public-read", it’s accepted and ignored: a bucket is public or private as a whole, set in its settings.
Node
// npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const s3 = new S3Client({
endpoint: "https://s3.rimbra.app",
region: "auto",
forcePathStyle: true,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
});
await s3.send(new PutObjectCommand({ Bucket: "shop-media", Key: "avatars/42.png", Body: buffer, ContentType: "image/png" }));
// A link to a private file that works for an hour
const url = await getSignedUrl(s3, new GetObjectCommand({ Bucket: "shop-media", Key: "avatars/42.png" }), { expiresIn: 3600 });Python (boto3)
import os
import boto3
from botocore.config import Config
s3 = boto3.client(
"s3",
endpoint_url="https://s3.rimbra.app",
aws_access_key_id=os.environ["AWS_ACCESS_KEY_ID"],
aws_secret_access_key=os.environ["AWS_SECRET_ACCESS_KEY"],
region_name="auto",
config=Config(signature_version="s3v4", s3={"addressing_style": "path"}),
)
s3.upload_file("report.pdf", "shop-media", "reports/2026-11.pdf") # multipart above 8 MB
for obj in s3.list_objects_v2(Bucket="shop-media", Prefix="reports/").get("Contents", []):
print(obj["Key"], obj["Size"])
url = s3.generate_presigned_url("get_object", Params={"Bucket": "shop-media", "Key": "reports/2026-11.pdf"}, ExpiresIn=3600)AWS CLI
Tell the CLI once to use path-style addresses, then pass the endpoint and region with each command (or set AWS_ENDPOINT_URL_S3 and AWS_REGION).
aws configure set default.s3.addressing_style path
aws s3 ls s3://shop-media --endpoint-url https://s3.rimbra.app --region auto
aws s3 cp ./photo.jpg s3://shop-media/photos/ --endpoint-url https://s3.rimbra.app --region auto
aws s3 sync ./media s3://shop-media/media --endpoint-url https://s3.rimbra.app --region auto
aws s3 presign s3://shop-media/photos/photo.jpg --expires-in 3600 --endpoint-url https://s3.rimbra.app --region autoPublic and private buckets
- Private (the default): every request needs an access key, or a signed link that expires. Use it for user uploads, invoices, exports and backups.
- Public: anyone can read a file at https://s3.rimbra.app/<bucket>/<key>, for example product images on a shop. Listing the bucket and writing to it still need a key.
- Signed links last from a minute up to 7 days. Make them from your code (presigned GET, as above) or from a file’s panel in the dashboard.
- Files are served with their content type, ETag, Last-Modified and Cache-Control, and support Range requests. They are sandboxed (Content-Security-Policy: sandbox), so an uploaded HTML or SVG file can’t run scripts. Buckets are for files, not for hosting sites; deploy sites as applications.
Uploads straight from the browser
To let users upload without the file passing through your server, have your server make a presigned PUT URL and send it to the page. The browser then PUTs the file to it directly.
Add your site’s origin under the bucket’s Settings → CORS first (for example https://shop.example.com), or the browser blocks the request.
// On your server
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const key = `uploads/${crypto.randomUUID()}-${fileName}`;
const uploadUrl = await getSignedUrl(s3, new PutObjectCommand({ Bucket: "shop-media", Key: key, ContentType: fileType }), { expiresIn: 600 });
// In the browser
await fetch(uploadUrl, { method: "PUT", body: file, headers: { "Content-Type": file.type } });In the dashboard
- Open a bucket to browse it by folder. Drop files or whole folders anywhere on the list to upload them; large files go up in parts, with progress for each.
- Select files or folders to delete them together. Rename or move a file or a folder from its row or its panel.
- Click a file to preview images, PDFs and text, copy its public link, make a signed link (1 hour, 1 day or 7 days), or download it.
- Folders are just the part of a key before a slash. New folder adds an empty marker (folder/) so it shows before anything is in it.
- Activity on the Buckets page, and Metrics on each bucket, chart bytes stored, writes and lists (Class A), reads (Class B) and bytes downloaded over 24 hours or 7 days, and 30 days on paid plans. They count the same requests billing does, so the dashboard's own uploads and previews aren't in them.
- Each bucket, tab and folder has its own address (for example app.rimbra.app/buckets/shop-media/files/avatars), so Back and Forward work and you can share a link to a folder.
Limits
- Files can be up to 5 GB, in one PUT or a multipart upload. Clients switch to multipart for large files on their own; parts are 5 MB to 5 GB, up to 10,000 parts.
- Keys (file names) can be up to 1,024 bytes of UTF-8. Listings are in byte order, 1,000 per page.
- Unfinished multipart uploads are removed after 7 days.
- Up to 50 access keys per workspace, and up to 20 CORS origins per bucket.
- Without the Buckets add-on (or the Developer pack), buckets and access keys can’t be created; the API answers 402. If the add-on ends, reads and writes stop until you add Buckets again. Nothing is deleted.
Pricing
The Buckets add-on ($3/month) and the Developer pack ($10/month) include storage and operations each month, on any plan. No plan includes Buckets on its own. Past the allowance, you pay only for what you use, from your Rimbra points. Downloads are free, with no egress charge and nothing taken from your plan’s bandwidth.
- Storage is measured every hour and added up over the month. A GB-month is 1 GB kept for a whole month, so 20 GB kept for half a month is 10 GB-months.
- Class A: PutObject, CopyObject, CreateMultipartUpload, UploadPart, CompleteMultipartUpload, ListObjects, ListBuckets, ListParts, ListMultipartUploads, CreateBucket and other writes. Class B: GetObject, HeadObject, HeadBucket and other reads. Deletes are free. Requests you make in the dashboard aren’t counted.
- The allowance resets at the start of each billing cycle, the day your workspace signed up. Charges are worked out every hour and taken from your balance once a day; they show in Billing next to your top-ups.
- Top up from the Buckets page or Billing ($5, $10, $25, $50 or any amount). The balance never expires and also pays for plans, add-ons and domains.
- We email you at 80% of an allowance, when your balance drops under $2 while you’re past the allowance, and if it runs out.
- Past the allowance with pay as you go off, or a balance of zero or less, uploads and other writes are refused with AccountProblem until you top up or turn it on. Reads and deletes keep working; with an empty balance, for 30 days.
| Buckets add-on | Developer pack | Past the allowance | |
|---|---|---|---|
| Storage | 5 GB-months | 10 GB-months | $0.03 per GB-month |
| Class A operations (writes and lists) | 500,000 | 1 million | $6.00 per million |
| Class B operations (reads) | 5 million | 10 million | $0.50 per million |
| Downloads | Free | Free | Free |
Worked example
A Django shop with the Buckets add-on keeps 26 GB of product photos and customer uploads all month. Its app makes 1.5 million uploads and listings and serves 18 million reads, and visitors download 400 GB.
| Used | Included | Billed | Cost | |
|---|---|---|---|---|
| Storage | 26 GB-months | 5 GB-months | 21 GB-months × $0.03 | $0.63 |
| Class A | 1.5 million | 500,000 | 1 million × $6.00 | $6.00 |
| Class B | 18 million | 5 million | 13 million × $0.50 | $6.50 |
| Downloads | 400 GB | — | Free | $0.00 |
| Total from the balance | $13.13 |
Supported S3 operations
ListBuckets, CreateBucket, HeadBucket, DeleteBucket (when empty), GetBucketLocation, Get/Put/DeleteBucketCors, ListObjectsV2, ListObjects, GetObject (Range, If-None-Match, If-Modified-Since, response-content-* overrides on signed requests), HeadObject, PutObject (Content-MD5 and x-amz-checksum-* are checked), CopyObject, DeleteObject, DeleteObjects, and multipart: CreateMultipartUpload, UploadPart, UploadPartCopy, CompleteMultipartUpload, AbortMultipartUpload, ListParts, ListMultipartUploads.
ACL and tagging requests are accepted and change nothing. Versioning, lifecycle rules, bucket policies and virtual-hosted addresses (bucket.s3.rimbra.app) are not supported.
