Store Dreep media in your own Amazon S3 bucket
Create an S3 bucket, attach an IAM policy that allows s3:PutObject, s3:GetObject and s3:DeleteObject on its objects plus s3:ListBucket on the bucket, and generate an access key for that IAM user. In Dreep, open Settings, Storage, pick AWS S3 and enter the bucket name, region and keys. Dreep probes the bucket, then stores originals and variants there.
Where S3 sits in the request path
Your backend
Calls Dreep with the project API key
Dreep
Signs S3 requests with your IAM key
Your S3 bucket
Originals and variants
Dreep CDN URL
What your users load
Connect S3 in five steps
You need an active Starter or Pro plan and an empty Dreep project. Do the AWS side first so the keys are ready.
- 1
Create the bucket
Create a general purpose bucket in the region closest to your users or your compliance boundary. Keep Block Public Access on and the default SSE-S3 encryption; neither gets in Dreep's way.
- 2
Create an IAM user with the policy below
Create a dedicated IAM user for Dreep, attach the inline policy from the next section with your bucket name, and nothing else. Never use root credentials or a key shared with other systems.
- 3
Create an access key
Generate an access key for that user (the 'application running outside AWS' use case). Copy the Access Key ID and Secret Access Key.
- 4
Enter the details in Dreep
In Settings, Storage, choose Connect Your Own Bucket, select AWS S3, and enter the bucket name, the bucket's region (for example eu-west-2) and both keys. There is no endpoint field for S3: Dreep builds https://s3.<region>.amazonaws.com from the region.
- 5
Let Dreep probe and save
Dreep puts and deletes an object named __dreep_byob_probe_test__ and lists the bucket's top-level prefixes. On success the keys are encrypted with AES-256-GCM and every new upload in the project goes to S3.
The least-privilege IAM policy
These four actions are the full set of S3 calls Dreep makes against a connected bucket. Replace acme-media with your bucket name.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DreepObjects",
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::acme-media/*"
},
{
"Sid": "DreepList",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::acme-media"
}
]
}| IAM action | Why Dreep needs it |
|---|---|
| s3:PutObject | Writing the probe object, every original and every generated variant. |
| s3:GetObject | Streaming originals and cached variants. Also authorises HeadObject, used to check whether a variant already exists. |
| s3:DeleteObject | Removing the probe object and deleting assets you delete in Dreep. |
| s3:ListBucket | Listing prefixes when you test the connection. Without it, S3 answers HeadObject on a missing key with 403 instead of 404, which Dreep treats as a storage failure rather than 'not generated yet'. |
- Using SSE-KMS with a customer managed key? Also allow kms:GenerateDataKey and kms:Decrypt on that key, or writes and reads will be denied.
- The dashboard has an optional CORS check that calls GetBucketCors. Add s3:GetBucketCORS on the bucket ARN only if you want that check to work; uploads don't need it.
Fields, rules and limits
| Item | Detail |
|---|---|
| Bucket Name | Exact S3 bucket name, not an ARN or URL. |
| Region | The bucket's region code. A wrong region returns 'Bucket region mismatch'. |
| Access Key ID / Secret | From the dedicated IAM user. Stored encrypted, bound to the project. |
| Plan | Starter ($2.99/mo) or Pro ($5.99/mo), with an active subscription. |
| Project state | Must be empty: connect before the first image or file is uploaded. |
| Changing buckets | Not possible once connected. Use a new project for a new bucket. |
| CORS | Not needed for uploads. Browsers PUT to Dreep's upload URL on cdn.dreep.cloud, and Dreep writes to S3 server-side. |
What appears in the bucket
Originals are written under projects/<project-slug>/<folder-path>/originals/<asset-id>/<filename>, and generated variants (resized and converted images, video renditions) under transforms/<content-hash>/<params-hash>.<ext>. Treat both prefixes as Dreep-managed: S3 lifecycle rules that delete or transition objects to Glacier classes under them will break delivery for those assets.
Server-side upload, now backed by S3
A direct upload from your backend, with a transform applied before storage. The stored result lands in your bucket; the URL is still a Dreep CDN URL.
import fs from "node:fs";
import { Dreep } from "dreep";
const dreep = new Dreep({ apiKey: process.env.DREEP_API_KEY });
const asset = await dreep.upload({
file: fs.createReadStream("invoice-scan.png"),
folder: "receipts/2026",
transform: { width: 1600, format: "webp", quality: 82 },
});
asset.url; // Dreep CDN URL, bytes stored in your S3 bucketf, err := os.Open("invoice-scan.png")
if err != nil {
log.Fatal(err)
}
defer f.Close()
asset, err := client.Upload(ctx, dreep.UploadOptions{
File: f,
Filename: "invoice-scan.png",
Destination: dreep.Destination{Folder: "receipts/2026"},
Transform: &dreep.Transform{Width: 1600, Format: dreep.FormatWebP, Quality: 82},
})curl -X POST https://api.dreep.cloud/api/v1/upload \
-H "Authorization: Bearer $DREEP_API_KEY" \
-F "file=@invoice-scan.png" \
-F "folder=receipts/2026" \
-F "width=1600" -F "format=webp" -F "quality=82"Troubleshooting
| Error | Usual cause on AWS | Fix |
|---|---|---|
| BYOB_CONNECTION_FAILED | AccessDenied, InvalidAccessKeyId or SignatureDoesNotMatch on the probe: wrong key, policy not attached, or bucket name typo in the ARN. | Check the policy ARNs match the bucket exactly, including the /* on the object statement. |
| Bucket region mismatch | S3 returned a 301 redirect because the bucket lives in another region. | Enter the region shown on the bucket's properties page. |
| BYOB_PLAN_UPGRADE_REQUIRED | Project is on Free or the subscription isn't active. | Upgrade to Starter or Pro. |
| BYOB_PROJECT_NOT_EMPTY | The project already has assets. | Connect S3 on a new, empty project. |
| BYOB_ALREADY_CONFIGURED_IMMUTABLE | A bucket is already attached to the project. | Keep it, or create a new project. |
| BYOB_STORAGE_UNREACHABLE | A later read or write failed: key deactivated, policy edited, bucket policy denying the user, KMS key access removed. | Restore the four actions (and KMS access if used) for the IAM user. |
Wasabi, DigitalOcean Spaces, MinIO and other S3-compatible storage
The same rules apply to all of them: paid plan, empty project, permanent bucket, and keys with read, write, delete and list on one bucket.
| Provider | Region field | Endpoint |
|---|---|---|
| Wasabi | e.g. eu-central-1 | Built for you: s3.<region>.wasabisys.com |
| DigitalOcean Spaces | e.g. nyc3, fra1 | Built for you: <region>.digitaloceanspaces.com |
| MinIO | Your configured region | Required: your MinIO URL |
| Generic S3-compatible | As your provider documents | Required: the provider's S3 endpoint |
Already have files in this bucket? Import them
BYOB and import do different jobs. BYOB makes Dreep write new uploads and variants into your bucket. Import copies objects that already exist in a bucket into a Dreep project, so they become Dreep assets with Dreep URLs.
To import, open Integrations in the Dreep dashboard, connect Amazon S3 with an access key, secret, region and bucket name, browse the objects, choose what to bring over and a destination folder, and start the job. It runs in the background with progress and logs. It is a one-off copy, not an ongoing sync, so read-only credentials are enough.
Who this is for
- Teams with data residency or retention policies that require media in their own AWS account.
- Companies that already back up, audit and bill S3 centrally and want media to follow the same controls.
- Products that want Dreep's transforms, signed URLs and SDKs without building the pipeline on Lambda.
When Dreep is not the right fit
- Projects that already hold media in Dreep storage: BYOB has to be set before the first upload.
- Workloads that must stay entirely inside a VPC with no third-party access to the bucket.
- Keeping an existing S3 library where it is while Dreep serves it: BYOB covers new uploads; existing objects need the separate import, which copies them into a Dreep project.
Frequently asked questions
What is the minimum IAM policy Dreep needs on S3?
s3:PutObject, s3:GetObject and s3:DeleteObject on arn:aws:s3:::your-bucket/*, plus s3:ListBucket on arn:aws:s3:::your-bucket. Those four actions cover every call Dreep makes.
Can I keep S3 Block Public Access enabled?
Yes. Dreep signs every request with your IAM key and serves media through Dreep CDN URLs, so the bucket never needs to be public.
Why does Dreep need s3:ListBucket?
To list prefixes when testing the connection, and because without it S3 returns 403 rather than 404 for a missing object. Dreep checks for existing variants with HeadObject and needs the 404 to tell 'not generated yet' from a permissions failure.
Do I need a CORS configuration on the S3 bucket for browser uploads?
No. Browsers upload to the presigned URL Dreep returns on cdn.dreep.cloud, and Dreep writes to S3 from its own servers, so the browser never makes a cross-origin request to S3.
Can I use an IAM role instead of access keys?
Not today. The connection form takes an Access Key ID and Secret Access Key, so use a dedicated IAM user scoped by the policy on this page and rotate its key on your normal schedule.
What happens to assets if I delete them in Dreep?
Dreep deletes the original object from your bucket. Treat the transforms/ prefix as Dreep-managed cache and don't build processes on its layout.
Related
Bring your own bucket
BYOB across S3, R2, Wasabi, Spaces, MinIO and generic S3.
Connect Cloudflare R2
The R2 version of this guide, with token permissions.
Dreep vs S3 + Lambda
What it takes to build this pipeline yourself on AWS.
Pricing
BYOB is included on Starter and Pro.
Documentation
Uploads, transforms, signed URLs and BYOB.
Put your S3 bucket behind Dreep
Create a Starter or Pro project, attach the four-action IAM policy, and connect the bucket before your first upload.