Mastering the S3 service in LocalStack: creating buckets and objects, versioning, presigned URLs, lifecycle, and advanced parity features like S3 Tables and replication.

In episode 4 we managed the emulator lifecycle: start, restart, and restricting services with SERVICES=s3,dynamodb. Now it's time to use real services. We start with S3, the object storage service that underpins so many modern applications.
Why S3 first? Because S3 is the perfect example of near-perfect stateful emulation in LocalStack: what you store is really stored, what you delete is really gone. Every command in this episode can be repeated exactly on real AWS — only the endpoint differs. This is the most concrete proof of the API parity philosophy we discussed in episode 1.
The most basic lifecycle: create a bucket, store an object, view its contents. Make sure the emulator is running, then run:
awslocal s3 mb s3://assets-dev
awslocal s3 ls
echo "halo dari LocalStack" > hello.txt
awslocal s3 cp hello.txt s3://assets-dev/hello.txt
awslocal s3 ls s3://assets-dev/
awslocal s3 cat s3://assets-dev/hello.txtA brief explanation of the commands:
| Command | Purpose |
|---|---|
awslocal s3 mb s3://assets-dev | Creates a bucket (make bucket) |
awslocal s3 cp hello.txt s3://assets-dev/ | Copies a local file to the bucket |
awslocal s3 ls s3://assets-dev/ | head | Lists objects, limited to the first few lines |
awslocal s3 cat s3://assets-dev/hello.txt | Displays the object contents |
Tip
The S3 commands in awslocal are identical to the real AWS CLI — the only difference is the endpoint, which automatically points to http://localhost:4566. The same code will work in production with aws and the default endpoint.
S3 versioning lets a single key store multiple object versions, so overwritten objects can be recovered. Enable it, then store two versions:
awslocal s3api put-bucket-versioning --bucket assets-dev --versioning-configuration Status=Enabled
echo "versi satu" > hello.txt
awslocal s3 cp hello.txt s3://assets-dev/hello.txt
echo "versi dua" > hello.txt
awslocal s3 cp hello.txt s3://assets-dev/hello.txt
awslocal s3api list-object-versions --bucket assets-devThe list-object-versions output will show two versions for the hello.txt key. Each version has a VersionId that can be used for version-specific operations, e.g. awslocal s3api get-object --version-id. Old versions remain retrievable at any time — that's the core value of versioning.
A presigned URL is a temporary URL that grants access to an object without credentials. A classic use case: a web application gives a file download link that expires after a few minutes. Create one with presign:
awslocal s3 presign s3://assets-dev/hello.txt --expires-in 300The output is a full URL to localhost:4566 with an authentication token. Open it in a browser or via curl and the object will download — valid for 300 seconds. After it expires, access is denied.
A lifecycle rule automates object management, for example deleting old objects after N days. The configuration is JSON:
{
"Rules": [
{
"ID": "cleanup-temp",
"Filter": { "Prefix": "tmp/" },
"Status": "Enabled",
"Expiration": { "Days": 30 }
}
]
}Apply it to the bucket:
awslocal s3api put-bucket-lifecycle-configuration \
--bucket assets-dev \
--lifecycle-configuration file://lifecycle.jsonS3 Tables is a feature that stores tabular data (Parquet) with integrated querying. In LocalStack, S3 Tables supports the create table bucket workflow and table operations, so you can build a local data lake pipeline before going to production.
Since the 2026.06+ release, S3 replication is available in LocalStack. You can configure a bucket that replicates objects to another bucket (possibly in a different region), with replication rules that mimic real AWS. Useful for testing architectures that rely on cross-region replication without creating two real AWS buckets.
Because LocalStack mimics AWS endpoints, production applications can be pointed at the emulator just by changing the endpoint. Here's an example using boto3:
import boto3
s3 = boto3.client(
"s3",
endpoint_url="http://localhost:4566",
region_name="us-east-1",
aws_access_key_id="test",
aws_secret_access_key="test",
)
print(s3.list_buckets())The only difference from production code is the endpoint_url parameter. The fake test/test credentials are enough for the emulator, and us-east-1 is the default region.
Warning
Never accidentally point an SDK with production credentials at LocalStack. Always keep development environment configuration (local endpoint, fake credentials) separate from production (AWS endpoint, real credentials).
mb, cp, ls cycle, plus versioning, presign, and lifecycle configuration.You've now mastered buckets and objects. In the next episode, episode 6, we move to the database side: mastering DynamoDB — creating tables with a primary key, put, get, query, scan, LSI and GSI secondary indexes, and data modeling best practices. Keep your emulator running!