
# S3 — Simple Storage Service

Objects, multipart uploads, versioning, lifecycle rules and event notifications,
addressed path-style or virtual-hosted with no configuration.

**Status:** ✅ Supported

## Quick start

```bash
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](/docs/sdk-cli/index.md#credentials).

## 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](/docs/networking/hostnames/index.md). Full
precedence rules, including the reserved service labels that outrank a bucket
match:
[Networking § How Overcast decides who owns a Host](/docs/networking/host-routing/index.md#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](/docs/services/iam/index.md) |
| 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](/docs/services/s3/limitations/index.md); the
EventBridge event shape is in
[S3 event notifications](/docs/services/s3/notifications/index.md).

## 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.

<!-- BEGIN overcast:capabilities -->

## Operations

45 of 53 listed operations are implemented.
Per-operation status, notes and AWS API links: [S3 operations](/docs/services/s3/operations/index.md).

<!-- END overcast:capabilities -->

## Related

- [S3 limitations](/docs/services/s3/limitations/index.md)
- [S3 event notifications](/docs/services/s3/notifications/index.md)
- [All service pages](/docs/services/index.md)
- [Service names and state overrides](/docs/configuration/index.md#service-names)
- [Networking § How Overcast decides who owns a Host](/docs/networking/host-routing/index.md#how-overcast-decides-who-owns-a-host)
- [AWS API reference](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html)
