S3 compatibility
No S3-compatible service implements the whole API. This page says what was tested, what is missing, and where a feature is present but behaves differently enough to matter.
Verified 2026-08-08 against the live nlsouth-std cluster through the public
endpoint, with a fixed workload per client.
Clients#
| Client | Version | Result |
|---|---|---|
| boto3 / botocore | 1.43.67 | ✅ all core operations |
| aws-cli v2 | 2.36.19 | ✅ cp both ways, ls, 12 MB multipart |
| rclone | 1.75.0 | ✅ copy both ways, ls, check |
MinIO client (mc) |
2025-08-13 | ✅ cp both ways, ls |
| restic | 0.19.1 | ✅ init, backup, check, restore |
| Terraform S3 backend | 1.15.8 | ⚠️ state works, locking does not — see below |
| s3fs, Cyberduck, Veeam | — | not yet tested |
restic's check verifies repository integrity, so that is a real backup-target
result rather than a byte round trip.
⚠️ Terraform: use_lockfile gives you no locking#
Terraform 1.10+ implements S3 state locking with a conditional write — a
PUT with If-None-Match: * that is supposed to fail if the lock object
already exists.
Storey does not enforce write-side conditionals. A conditional PUT against
an existing key succeeds and overwrites it. Read-side conditionals are honoured
correctly; it is specifically the write path.
This fails silently. terraform apply with use_lockfile = true completes
without error, so a team believes state is locked when nothing is enforcing it,
and two concurrent applies can both "acquire" the lock and corrupt state.
Do not rely on use_lockfile with Storey. Serialise applies in CI, or use a
separate lock backend. State storage itself works correctly.
We would rather tell you this than let you find it after a corrupted state file.
Core operations — verified#
| Capability | Notes |
|---|---|
| PUT / GET / HEAD / DELETE object | |
| Ranged GET | HTTP 206, byte range exact |
| Multipart upload | Initiate, part, complete, abort |
ListObjects v1 and v2 |
|
| Prefix and delimiter listing | CommonPrefixes correct |
| Batch delete | Up to 1000 keys |
| Server-side copy | Within one region |
| Presigned URLs (SigV4) | Fetched anonymously over HTTPS |
| Path-style addressing | |
| Virtual-host addressing | <bucket>.api.<cluster>…, wildcard certificate |
| CORS configuration | Rules round-trip |
| Static website hosting | Requires the website flag and a global alias |
| SSE-C | Genuinely encrypts — GET without the key is refused |
CreateBucket over S3 is refused. Buckets carry a region, a tier, quotas
and an entitlement check, none of which CreateBucket can express. Use the
console or the management API.
Not available#
| Feature | Notes |
|---|---|
| Object versioning | The most commonly assumed feature. It is not there. |
| Object Lock / WORM / legal hold | Not available |
| Bucket policies (JSON) | Use grants instead |
| Object and bucket tagging | Blocks tag-driven cost allocation |
| Event notifications | Not available |
| Replication configuration | By design — regions are independent |
| STS / temporary credentials | Use presigned URLs |
| Requester pays, transfer acceleration, archive classes | Out of scope |
Versioning and Object Lock together mean Storey is not a ransomware-resistant backup target today. If that is your requirement, say so before building.
Present but partial#
| Feature | Behaviour |
|---|---|
| Lifecycle rules | Configuration is accepted and reads back intact. Whether expiry actually executes over time is not yet verified — do not depend on it deleting anything. |
| Bucket / object ACLs | Read-only. GetBucketACL returns a synthetic owner grant; setting ACLs is not supported. Tools that read ACLs work; tools that set them fail. |
| SSE-S3 / SSE-KMS | Not yet tested |
| SigV2 | Not tested — no modern client offers it |
Differences from AWS S3 worth knowing#
Bucket names are tenant-scoped. Yours do not collide with anyone else's, so the usual globally-unique naming games are unnecessary.
The region string includes the tier — nlsouth-std, not nlsouth. It
identifies the cluster and is part of the SigV4 signature; getting it wrong
produces SignatureDoesNotMatch.
No dots in bucket names. A dot breaks TLS certificate matching for virtual-host addressing.
Website hosting does not open the S3 path. Enabling it exposes objects on the website hostname only; the API path stays authenticated. The two surfaces cannot be confused for each other.