S3

Lightweight, dependency-free fake of AWS S3 that speaks the real S3 REST API (XML wire protocol), so application code using @aws-sdk/client-s3 can run against it with zero cost and zero side effects.

KeyValue
Port4566
ProtocolAWS S3 REST API (HTTP + XML)
Compatible client@aws-sdk/client-s3 (v3)
Size~96 KB
Startup< 100ms
StateIn-memory, ephemeral, resettable

Quick Start

Start the server:

import { S3Server } from "./services/s3/src/server.js";

const server = new S3Server(4566);
await server.start();
// ... use it ...
await server.stop();

Connect with the real AWS SDK client:

import {
  S3Client,
  CreateBucketCommand,
  PutObjectCommand,
  GetObjectCommand,
} from "@aws-sdk/client-s3";

const s3 = new S3Client({
  region: "us-east-1",
  endpoint: "http://127.0.0.1:4566",
  forcePathStyle: true, // recommended for the fake
  credentials: { accessKeyId: "parlel", secretAccessKey: "parlel" },
});

await s3.send(new CreateBucketCommand({ Bucket: "my-bucket" }));
await s3.send(new PutObjectCommand({ Bucket: "my-bucket", Key: "hello.txt", Body: "hello world" }));

const obj = await s3.send(new GetObjectCommand({ Bucket: "my-bucket", Key: "hello.txt" }));
console.log(await obj.Body.transformToString()); // "hello world"

Addressing

Both addressing styles are supported:

Authentication

SigV4 signatures are accepted but not verified (any credentials work). This matches LocalStack-style local development.

Access via Parlel Sandbox

S3 is an HTTP service, so inside a Parlel sandbox it is exposed directly at its own Daytona preview URL (not through the MCP parlel_execute tool, which is for TCP database services). Point any @aws-sdk/client-s3 client — or plain HTTP — at the preview URL from the sandbox's Connect panel:

# the preview token comes from the Connect panel / GET /connection
curl -X PUT "$S3_URL/my-bucket" -H "x-daytona-preview-token: $TOKEN"
curl -X PUT "$S3_URL/my-bucket/hello.txt" -H "x-daytona-preview-token: $TOKEN" --data "hello world"
curl "$S3_URL/my-bucket/hello.txt" -H "x-daytona-preview-token: $TOKEN"   # -> hello world

Internal / Health Endpoints

These are not part of the S3 API; they support local tooling.

MethodPathDescription
GET/_parlel/healthReturns { "status": "ok", "service": "s3", "buckets": N }
POST/_parlel/resetClears all in-memory state

You can also reset programmatically via server.reset().

Implemented Operations

Service

OperationSDK CommandNotes
ListBucketsListBucketsCommandGET /

Bucket — core

OperationSDK CommandNotes
CreateBucketCreateBucketCommandValidates bucket name; idempotent re-create in us-east-1 returns 200, BucketAlreadyOwnedByYou (409) in other regions
DeleteBucketDeleteBucketCommandBucketNotEmpty if it has live objects
HeadBucketHeadBucketCommand200 / 404 + x-amz-bucket-region
GetBucketLocationGetBucketLocationCommandEmpty constraint for us-east-1

Bucket — configuration

OperationSDK Command
GetBucketVersioning / PutBucketVersioningGetBucketVersioningCommand / PutBucketVersioningCommand
GetBucketTagging / PutBucketTagging / DeleteBucketTaggingGetBucketTaggingCommand / PutBucketTaggingCommand / DeleteBucketTaggingCommand
GetBucketCors / PutBucketCors / DeleteBucketCorsGetBucketCorsCommand / PutBucketCorsCommand / DeleteBucketCorsCommand
GetBucketPolicy / PutBucketPolicy / DeleteBucketPolicyGetBucketPolicyCommand / PutBucketPolicyCommand / DeleteBucketPolicyCommand
GetBucketAcl / PutBucketAclGetBucketAclCommand / PutBucketAclCommand
GetBucketLifecycleConfiguration / PutBucketLifecycleConfiguration / DeleteBucketLifecycleGetBucketLifecycleConfigurationCommand / PutBucketLifecycleConfigurationCommand / DeleteBucketLifecycleCommand
GetBucketEncryption / PutBucketEncryption / DeleteBucketEncryptionGetBucketEncryptionCommand / PutBucketEncryptionCommand / DeleteBucketEncryptionCommand
GetBucketWebsite / PutBucketWebsite / DeleteBucketWebsiteGetBucketWebsiteCommand / PutBucketWebsiteCommand / DeleteBucketWebsiteCommand

Configuration documents (CORS, lifecycle, encryption, website) are stored and returned verbatim so the SDK round-trips them faithfully.

Object — core

OperationSDK CommandNotes
PutObjectPutObjectCommandMD5 ETag, user metadata, x-amz-tagging, Content-MD5 validation (BadDigest)
GetObjectGetObjectCommandRange requests (206 / InvalidRange), conditional headers, response-* overrides
HeadObjectHeadObjectCommandMetadata only; 404 on miss
DeleteObjectDeleteObjectCommandIdempotent; inserts a delete marker when versioning is enabled
DeleteObjectsDeleteObjectsCommandBatch delete, supports Quiet; empty/malformed body → MalformedXML (400)
CopyObjectCopyObjectCommandx-amz-copy-source, COPY/REPLACE metadata & tagging directives

Object — listing

OperationSDK CommandNotes
ListObjects (v1)ListObjectsCommandprefix, delimiter, marker, max-keys, CommonPrefixes
ListObjectsV2ListObjectsV2Commandcontinuation-token, start-after, fetch-owner, KeyCount
ListObjectVersionsListObjectVersionsCommandVersions + delete markers, IsLatest

Object — tagging / ACL / attributes

OperationSDK Command
GetObjectTagging / PutObjectTagging / DeleteObjectTaggingGetObjectTaggingCommand / PutObjectTaggingCommand / DeleteObjectTaggingCommand
GetObjectAcl / PutObjectAclGetObjectAclCommand / PutObjectAclCommand
GetObjectAttributesGetObjectAttributesCommand

Multipart uploads

OperationSDK CommandNotes
CreateMultipartUploadCreateMultipartUploadCommandReturns UploadId
UploadPartUploadPartCommandPer-part MD5 ETag; part number 1–10000
CompleteMultipartUploadCompleteMultipartUploadCommandMultipart ETag md5(part-md5s)-N; validates order & parts
AbortMultipartUploadAbortMultipartUploadCommandDiscards the upload
ListPartsListPartsCommandLists uploaded parts
ListMultipartUploadsListMultipartUploadsCommandLists in-progress uploads

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureStatus
Buckets: create / delete / list / head / location✅ Supported
CreateBucket idempotent re-create (us-east-1 → 200, other regions → 409)✅ Supported
Objects: put / get / head / delete / copy✅ Supported
Batch delete (DeleteObjects)✅ Supported
Batch delete validation (empty/malformed body → MalformedXML)✅ Supported
Listing v1 + v2 with prefix/delimiter/pagination✅ Supported
Range + conditional GET (If-Match, If-None-Match, If-(Un)Modified-Since)✅ Supported
User metadata (x-amz-meta-*)✅ Supported
Object & bucket tagging✅ Supported
Object & bucket ACL (canned, static owner)◐ Accepted (canned grant returned; ACLs not enforced)
Versioning + delete markers + version listing✅ Supported
Multipart uploads (full lifecycle)✅ Supported
Multipart EntityTooSmall enforcement (non-final part < 5 MiB)✓ By design — not enforced so tests can use tiny parts
Bucket config: CORS / policy / lifecycle / encryption / website◐ Stored & round-tripped (not validated)
GetObjectAttributes✅ Supported
Virtual-hosted & path-style addressing✅ Supported
Error envelope (Code / Message / Resource / RequestId / HostId)✅ Supported — RequestId/HostId match the x-amz-request-id / x-amz-id-2 headers
List pagination for versions / multipart-uploads / parts✓ By design — single page (IsTruncated=false)
SigV4 signature verification◐ Accepted but not enforced
Server-side encryption (actual crypto)✓ By design — config stored, data is not encrypted
POST form (browser) uploads⟳ Roadmap — NotImplemented (501)
Replication, logging, notification, inventory, analytics, metrics, object-lock, request-payment, accelerate, public-access-block, ownership-controls, intelligent-tiering, S3 Select⟳ Roadmap — NotImplemented (501)
Presigned URL signature validation◐ URL routing works; signature not checked

Error Codes & Shapes

Errors use the standard S3 XML envelope:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
  <Code>NoSuchKey</Code>
  <Message>The specified key does not exist.</Message>
  <Key>missing.txt</Key>
  <Resource>/my-bucket/missing.txt</Resource>
  <RequestId>…</RequestId>
  <HostId>…</HostId>
</Error>

RequestId and HostId in the body match the x-amz-request-id and x-amz-id-2 response headers, just like the real S3 API.

CodeHTTPWhen
NoSuchBucket404Bucket does not exist
NoSuchKey404Object/version does not exist
NoSuchUpload404Multipart upload id not found
NoSuchTagSet404Bucket tagging requested but unset
NoSuchCORSConfiguration404Bucket CORS requested but unset
NoSuchBucketPolicy404Bucket policy requested but unset
NoSuchLifecycleConfiguration404Lifecycle requested but unset
NoSuchWebsiteConfiguration404Website config requested but unset
ServerSideEncryptionConfigurationNotFoundError404Encryption requested but unset
InvalidBucketName400Bucket name fails S3 naming rules
BucketAlreadyOwnedByYou409Re-creating an existing bucket outside us-east-1 (in us-east-1 re-create is idempotent → 200)
MalformedXML400DeleteObjects body is empty or has no <Object> entries
BucketNotEmpty409Deleting a bucket with live objects
BadDigest400Content-MD5 does not match the body
InvalidRange416Unsatisfiable Range header
InvalidPart400Completing multipart with a missing/mismatched part
InvalidPartOrder400Parts not in ascending order
PreconditionFailed412If-Match / If-Unmodified-Since failed
NotModified304If-None-Match / If-Modified-Since matched
NotImplemented501Operation/sub-resource not implemented
MethodNotAllowed405Method not valid for the resource
InternalError500Unexpected server error

Environment Variables

The manifest advertises these defaults for clients/tooling:

VariableDefault
AWS_ACCESS_KEY_IDparlel
AWS_SECRET_ACCESS_KEYparlel
AWS_REGIONus-east-1
AWS_ENDPOINT_URL_S3http://127.0.0.1:4566
AWS_S3_FORCE_PATH_STYLEtrue
<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

AWS_ACCESS_KEY_ID=parlel
AWS_SECRET_ACCESS_KEY=parlel
AWS_REGION=us-east-1
AWS_ENDPOINT_URL_S3=http://parlel-bridge:4566
AWS_S3_FORCE_PATH_STYLE=true
<!-- parlel:testenv:end -->