S3
Amazon Simple Storage Service for storing and retrieving any amount of data from anywhere.
Protocol: RestXmlSigning name: s3Persistent: Yes — bucket and object metadata persists; object bodies persist when --data-dir is supplied (see Behavior Notes)
Quick Start
Create a bucket, upload a file, and download it:
bash
# Create a bucket
curl -s -X PUT http://localhost:4566/my-bucket \
-H "Authorization: AWS4-HMAC-SHA256 Credential=test/20260421/us-east-1/s3/aws4_request, SignedHeaders=host, Signature=fake"
# Upload an object
curl -s -X PUT http://localhost:4566/my-bucket/hello.txt \
-H "Content-Type: text/plain" \
-H "Authorization: AWS4-HMAC-SHA256 Credential=test/20260421/us-east-1/s3/aws4_request, SignedHeaders=host, Signature=fake" \
-d "Hello, World!"
# Download the object
curl -s http://localhost:4566/my-bucket/hello.txt \
-H "Authorization: AWS4-HMAC-SHA256 Credential=test/20260421/us-east-1/s3/aws4_request, SignedHeaders=host, Signature=fake"
# List objects in bucket
curl -s "http://localhost:4566/my-bucket?list-type=2" \
-H "Authorization: AWS4-HMAC-SHA256 Credential=test/20260421/us-east-1/s3/aws4_request, SignedHeaders=host, Signature=fake"Configuration
S3 state is global per account — it does not vary by region. All buckets are visible regardless of the region in the request.
Use forcePathStyle: true in your SDK client to use path-style URLs (/bucket/key) instead of virtual-hosted style (bucket.localhost/key).
Operations
Bucket Operations
| Operation | Description |
|---|---|
ListBuckets | List all buckets. Returns Buckets list with Name and CreationDate. No input required |
CreateBucket | Create a new bucket. Input: bucket name in URL path, optional CreateBucketConfiguration XML (for non-us-east-1 regions). Returns empty CreateBucketResult |
DeleteBucket | Delete a bucket. Must be empty first (no objects). Input: bucket name in URL path |
HeadBucket | Check if a bucket exists. Returns 200 (exists) or 404 (not found). No body returned |
GetBucketLocation | Get the bucket's region. Returns LocationConstraint (empty string for us-east-1) |
PutBucketVersioning | Enable or suspend versioning. Input: VersioningConfiguration XML with Status: Enabled/Suspended |
GetBucketVersioning | Get versioning configuration |
PutBucketTagging | Set bucket tags. Input: Tagging XML with TagSet |
GetBucketTagging | Get bucket tags. Returns TagSet |
DeleteBucketTagging | Remove all bucket tags |
PutBucketPolicy | Set a bucket policy (JSON). Input: raw JSON policy body |
GetBucketPolicy | Get bucket policy (raw JSON) |
DeleteBucketPolicy | Remove bucket policy |
PutBucketCors | Set CORS configuration. Input: CORSConfiguration XML |
GetBucketCors | Get CORS configuration |
DeleteBucketCors | Remove CORS configuration |
PutBucketNotificationConfiguration | Configure S3 event notifications (SQS, SNS, Lambda) |
GetBucketNotificationConfiguration | Get event notification config |
ACL Operations
| Operation | Description |
|---|---|
GetBucketAcl | Get the access control list for a bucket. Returns Owner and Grants list |
PutBucketAcl | Set the access control list for a bucket. Input: AccessControlPolicy XML or a canned ACL header |
GetObjectAcl | Get the ACL for an object. Returns Owner and Grants list |
Lifecycle Operations
| Operation | Description |
|---|---|
GetBucketLifecycleConfiguration | Get lifecycle rules for a bucket. Returns Rules list |
PutBucketLifecycleConfiguration | Set lifecycle rules. Input: LifecycleConfiguration XML with Rule elements |
DeleteBucketLifecycleConfiguration | Remove all lifecycle rules from a bucket |
Encryption Operations
| Operation | Description |
|---|---|
GetBucketEncryption | Get the default server-side encryption configuration for a bucket |
PutBucketEncryption | Set the default encryption. Input: ServerSideEncryptionConfiguration XML |
DeleteBucketEncryption | Remove the default encryption configuration |
Logging Operations
| Operation | Description |
|---|---|
GetBucketLogging | Get the logging configuration for a bucket. Returns LoggingEnabled element if configured |
PutBucketLogging | Set the logging configuration. Input: BucketLoggingStatus XML |
Object Operations
| Operation | Description |
|---|---|
PutObject | Upload an object. Input: PUT /{bucket}/{key}, body is object data, Content-Type header sets MIME type. Returns: ETag header, plus x-amz-version-id when the bucket has versioning enabled |
GetObject | Download an object. Input: GET /{bucket}/{key}, optional Range header, optional ?versionId=X to fetch a historical version. Reads landing on a delete marker return 404 with x-amz-delete-marker: true |
HeadObject | Get object metadata without the body. Same ?versionId=X semantics as GetObject |
DeleteObject | Delete an object. Input: DELETE /{bucket}/{key} (optional ?versionId=X for permanent per-version delete). On versioning-enabled buckets without VersionId, pushes a delete marker; with VersionId, permanently removes that single entry |
CopyObject | Copy an object within or between buckets. Input: PUT /{dest-bucket}/{dest-key} with x-amz-copy-source: /{src-bucket}/{src-key}[?versionId=X] header. Returns the new object's VersionId in x-amz-version-id when the destination bucket has versioning enabled |
ListObjectsV2 | List objects with optional prefix and delimiter. Skips keys whose latest version is a delete marker. Input: GET /{bucket}?list-type=2&prefix=...&delimiter=/&max-keys=1000 |
ListObjectVersions | List every version + delete marker for a bucket, newest first per key with IsLatest set on the most recent. Input: GET /{bucket}?versions |
DeleteObjects | Batch delete objects. Input: POST /{bucket}?delete with Delete XML listing keys (and optional VersionId per entry). Honors versioning: writes delete markers on Enabled/Suspended buckets, permanently removes a specific version when VersionId is supplied |
PutObjectTagging | Set object tags. Input: PUT /{bucket}/{key}?tagging with Tagging XML |
GetObjectTagging | Get object tags |
DeleteObjectTagging | Remove object tags |
Multipart Upload
| Operation | Description |
|---|---|
CreateMultipartUpload | Initiate a multipart upload. Returns UploadId |
UploadPart | Upload a part. Input: PUT /{bucket}/{key}?partNumber={N}&uploadId={id}, body is part data. Returns ETag |
CompleteMultipartUpload | Complete the upload. Input: POST /{bucket}/{key}?uploadId={id} with CompleteMultipartUpload XML listing all parts and their ETags. Returns the new object's VersionId when the bucket has versioning enabled |
UploadPartCopy | Copy bytes from a source object directly into a multipart part. Distinguished from UploadPart by an x-amz-copy-source header on the same PUT /{bucket}/{key}?partNumber=&uploadId= request, with optional x-amz-copy-source-range: bytes=start-end |
AbortMultipartUpload | Cancel an in-progress upload. Input: DELETE /{bucket}/{key}?uploadId={id} |
ListMultipartUploads | List in-progress uploads for a bucket |
ListParts | List uploaded parts for a multipart upload |
SDK Example
typescript
import {
S3Client,
CreateBucketCommand,
PutObjectCommand,
GetObjectCommand,
ListObjectsV2Command,
DeleteObjectCommand,
CopyObjectCommand,
} from '@aws-sdk/client-s3';
import { Readable } from 'stream';
const s3 = new S3Client({
region: 'us-east-1',
endpoint: 'http://localhost:4566',
credentials: { accessKeyId: 'test', secretAccessKey: 'test' },
forcePathStyle: true, // Required for AWSim path-style URLs
});
// Create bucket
await s3.send(new CreateBucketCommand({ Bucket: 'my-bucket' }));
// Upload text
await s3.send(new PutObjectCommand({
Bucket: 'my-bucket',
Key: 'config/settings.json',
Body: JSON.stringify({ debug: true, version: '1.0.0' }),
ContentType: 'application/json',
}));
// Upload binary
import { readFileSync } from 'fs';
await s3.send(new PutObjectCommand({
Bucket: 'my-bucket',
Key: 'images/logo.png',
Body: readFileSync('logo.png'),
ContentType: 'image/png',
}));
// Download and read
const response = await s3.send(new GetObjectCommand({
Bucket: 'my-bucket',
Key: 'config/settings.json',
}));
const text = await response.Body?.transformToString();
console.log(JSON.parse(text!));
// List objects with prefix
const { Contents, CommonPrefixes } = await s3.send(new ListObjectsV2Command({
Bucket: 'my-bucket',
Prefix: 'config/',
Delimiter: '/',
}));
console.log('Objects:', Contents?.map(o => o.Key));
// Copy object
await s3.send(new CopyObjectCommand({
Bucket: 'my-bucket',
Key: 'config/settings-backup.json',
CopySource: '/my-bucket/config/settings.json',
}));
// Delete object
await s3.send(new DeleteObjectCommand({
Bucket: 'my-bucket',
Key: 'config/settings-backup.json',
}));CLI Example
bash
# Bucket operations
aws --endpoint-url http://localhost:4566 s3 mb s3://my-bucket
aws --endpoint-url http://localhost:4566 s3 ls
# Object operations
aws --endpoint-url http://localhost:4566 s3 cp file.txt s3://my-bucket/file.txt
aws --endpoint-url http://localhost:4566 s3 cp s3://my-bucket/file.txt ./downloaded.txt
aws --endpoint-url http://localhost:4566 s3 ls s3://my-bucket/
aws --endpoint-url http://localhost:4566 s3 rm s3://my-bucket/file.txt
# Sync a directory
aws --endpoint-url http://localhost:4566 s3 sync ./dist s3://my-bucket/dist/
# Low-level API access
aws --endpoint-url http://localhost:4566 s3api put-object \
--bucket my-bucket \
--key data/record.json \
--body record.json \
--content-type application/json
aws --endpoint-url http://localhost:4566 s3api list-objects-v2 \
--bucket my-bucket \
--prefix data/ \
--max-keys 100Event Notifications
S3 can notify SQS queues, SNS topics, or Lambda functions when objects are created or deleted. See Cross-Service Integrations.
ACL Example
bash
# Get bucket ACL
aws --endpoint-url http://localhost:4566 s3api get-bucket-acl --bucket my-bucket
# Get object ACL
aws --endpoint-url http://localhost:4566 s3api get-object-acl \
--bucket my-bucket \
--key my-object.txtLifecycle Example
bash
# Put lifecycle configuration
aws --endpoint-url http://localhost:4566 s3api put-bucket-lifecycle-configuration \
--bucket my-bucket \
--lifecycle-configuration '{"Rules":[{"ID":"expire-old","Status":"Enabled","Expiration":{"Days":30},"Filter":{"Prefix":"tmp/"}}]}'
# Get lifecycle configuration
aws --endpoint-url http://localhost:4566 s3api get-bucket-lifecycle-configuration \
--bucket my-bucketEncryption Example
bash
# Enable AES-256 default encryption
aws --endpoint-url http://localhost:4566 s3api put-bucket-encryption \
--bucket my-bucket \
--server-side-encryption-configuration \
'{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'
# Get encryption configuration
aws --endpoint-url http://localhost:4566 s3api get-bucket-encryption --bucket my-bucketBehavior Notes
- Object metadata (
key,size,ETag,content-type) is always persisted across restarts via the regular snapshot. Object bodies are persisted to disk under{data_dir}/s3/objects/<bucket>/<key>only when--data-diris supplied; without it, bodies stay in memory and are lost on restart. Multipart parts under an in-progress upload are persisted under{data_dir}/s3/multipart/<bucket>/<upload-id>/. See Persistence. - Presigned URLs are not supported.
- Bucket ACLs and object ACLs are accepted and stored but not enforced — all objects are accessible to any caller in AWSim.
- Lifecycle rules are accepted and stored but items are not automatically expired or transitioned.
- Encryption configuration is accepted and stored but objects are not actually encrypted at rest in AWSim.
- Logging configuration is accepted and stored but no log delivery occurs in AWSim.
- Object versioning is fully modelled: enabling it on a bucket retains every prior version,
GetObject/HeadObjectaccept?versionId=for historical reads,DeleteObjectwithoutVersionIdwrites a delete marker (and with aVersionIdpermanently removes that single entry), andListObjectVersionsenumerates everything including delete markers. Each version's body is persisted under its own blob key ({key}@v={version_id_or_null}), so historical reads survive snapshot+restore. ListObjectsV2supportsPrefixfiltering,Delimiterfor virtual folder grouping (CommonPrefixes),start-afterparameter, and pagination viacontinuation-token. TheKeyCountresponse field includes both Contents and CommonPrefixes entries.- The
forcePathStyle: trueSDK option is required — virtual-hosted style (bucket.localhost:4566) is not recommended for local development. - Multipart ETags follow AWS format: the ETag is the MD5 digest of the concatenated per-part MD5 binary digests, followed by
-Nwhere N is the part count (e.g.,"a54357aff...-3"). Single-part uploads use a simple MD5 ETag. - CompleteMultipartUpload only concatenates parts listed in the request body (with ETag validation), matching AWS behavior. Part numbers must be 1–10000.
- DeleteObjects returns per-key error entries (with
CodeandMessage) for any keys that fail to delete, matching AWS behavior. - HeadObject and GetObject return user metadata headers (
x-amz-meta-*) that were supplied duringPutObjectorCreateMultipartUpload. - GetObject returns
Accept-Ranges: bytesand HTTP 206 Partial Content for range requests withContent-Rangeheader. - PutObject persists
Content-Encoding,Cache-Control,Content-Disposition,Content-Language, andExpiresheaders; they are returned on subsequent GET/HEAD. - CopyObject returns
CopySourceVersionIdwhen the source object has a version ID. - Bucket/Object ACLs use the caller's actual account ID as the canonical owner ID.
- GetBucketEncryption returns
ServerSideEncryptionConfigurationNotFoundErrorwhen no encryption has been explicitly set (matching AWS). - Bucket name validation rejects IP-address format,
xn--prefix,-s3alias/--ol-s3/--x-s3suffixes, and consecutive dots. - Delete markers are properly surfaced via
x-amz-delete-marker: trueandx-amz-version-idHTTP headers on 404 responses.