S3 — Simple Storage Service
Quick start, object, multipart, versioning, lifecycle and notification coverage, the three addressing forms, and the ACLs, website serving and encryption that do nothing.
Objects, multipart uploads, versioning, lifecycle rules and event notifications, addressed path-style or virtual-hosted with no configuration.
Status: ✅ Supported
Quick start
export AWS_ENDPOINT_URL=http://localhost:4566
aws s3api create-bucket --bucket demo
echo hello > hello.txt
aws s3 cp hello.txt s3://demo/hello.txt
aws s3 ls s3://demo/
aws s3api get-object --bucket demo --key hello.txt /dev/stdout
Any credentials work; with none configured, run eval "$(overcast env)" first
— see Using AWS SDKs and CLI.
What works
| Area | Behaviour |
|---|---|
| Objects | Put, get, head, copy and delete, with x-amz-meta-*, Content-Type, Content-Disposition, Cache-Control and friends preserved |
| Conditional and partial reads | Range on GetObject and HeadObject, with 416 InvalidRange for one that names no bytes; If-Match and If-None-Match on GetObject |
| Conditional writes | If-None-Match: * and If-Match on PutObject and CompleteMultipartUpload, refused with 412 PreconditionFailed, so only one racing writer wins |
| Listing | ListObjects and ListObjectsV2 with prefix, delimiter, max-keys (validated, capped at 1000), start-after, continuation tokens, encoding-type=url and fetch-owner; DeleteObjects up to 1000 keys, quiet mode included |
| Multipart | Create, upload part, complete, abort, list uploads and list parts. CompleteMultipartUpload checks the parts list the way S3 does — a wrong or unknown ETag is InvalidPart, out-of-order parts InvalidPartOrder, a non-final part under 5 MiB EntityTooSmall, an empty list MalformedXML — and a refused completion leaves the upload in place to retry or abort |
| Versioning | Enabled and Suspended with AWS’s semantics — version ids, delete markers, and ListObjectVersions in AWS’s order with resumable pagination |
| Lifecycle | Expiration, transitions, noncurrent-version rules, ExpiredObjectDeleteMarker and AbortIncompleteMultipartUpload, applied by an hourly sweeper on the injected clock |
| Notifications | SQS, SNS, Lambda and EventBridge destinations with prefix/suffix filters; records carry versionId and sequencer |
| Presigned URLs | Signed against IAM access keys and STS session credentials; with OVERCAST_SIGV4_VALIDATE=true an invalid or expired URL is a 403 InvalidSignatureException |
| Bucket config | Policy, tagging, CORS, encryption, website and notification configuration, each stored and returned; tag sets are held to AWS’s 50-per-bucket, 10-per-object and key/value length limits |
| Console | The web console browses buckets, previews text and images, shows version history and uploads or downloads objects |
Addressing
Both styles work, and Overcast decides between them from the request’s Host
header alone — there is no addressing-style environment variable.
| Form | Example |
|---|---|
| Path-style | localhost:4566/demo/hello.txt |
| Virtual-hosted | demo.s3.localhost:4566/hello.txt |
| Bare virtual-hosted | demo.localhost:4566/hello.txt — what an SDK emits against a custom endpoint with path-style disabled |
The bucket is everything before the first .s3./.s3- label, or before the base
domain in the bare form. For virtual-hosted names that resolve as wildcards, set
OVERCAST_HOSTNAME to a wildcard-DNS domain — see
Hostnames that resolve for every caller. Full
precedence rules, including the reserved service labels that outrank a bucket
match:
Networking § How Overcast decides who owns a Host.
Differences from AWS
| Area | On AWS | Overcast |
|---|---|---|
| Encryption | Objects are encrypted at rest | Bucket encryption rules are stored and returned; object-level SSE request headers are ignored and not echoed back |
| ACLs and bucket policies | Both are evaluated on every request | The four ACL operations return 501; a policy is validated as JSON and stored, but S3 evaluates nothing — see IAM |
| Website endpoint | Serves index and error documents, and redirects | The website configuration round-trips faithfully, but no request is redirected or served an index document |
| S3 Select and Glacier | SelectObjectContent, RestoreObject, GetObjectAttributes and UploadPartCopy | All return 501 |
| CORS rule ids | Preserved | Rules round-trip, but a rule’s Id is not preserved |
| Access points, replication, object lock, requester pays, public access block, inventory, Transfer Acceleration | Full API | Not modelled at all. A listing accepts x-amz-request-payer and returns no x-amz-request-charged, which is what AWS does for a bucket that is not Requester Pays |
| Directory buckets (S3 Express One Zone) | Zonal endpoints with their own listing semantics | Not modelled; Overcast serves general purpose bucket paths only |
Versioning states, the lifecycle sweeper’s exact rules and the website document’s validation are in S3 limitations; the EventBridge event shape is in S3 event notifications.
Gotchas
Warning
A bucket whose keys are all delete-marked is not empty. DeleteBucket
answers BucketNotEmpty until the markers and the versions under them are
removed — the AWS behaviour that makes the SDKs’
ListObjectVersions → DeleteObjects cleanup loop necessary.
A bucket name containing the literal substring .s3. is ambiguous under the
virtual-hosted rule and will be mis-split. Real AWS has the same limitation and
recommends against dots in bucket names used with virtual-hosted addressing.
Operations
45 of 53 listed operations are implemented. Per-operation status, notes and AWS API links: S3 operations.