A small serverless app that exercises S3 + Lambda + DynamoDB on LocalStack. Drag a file in, see its metadata.
┌──────────────┐ ┌─────────────────┐
│ Browser UI │ ── GET ?filename=… ─────────► │ presign Lambda │
│ (S3 site) │ ◄── presigned POST URL ─────── │ (Function URL)│
│ │ └─────────────────┘
│ │ ── POST file ─────────────────► ┌─────────────┐
│ │ ◄── 204 ────────────────────── │ S3 bucket │
│ │ │file-uploads │
│ │ └──────┬──────┘
│ │ │ s3:ObjectCreated
│ │ ▼
│ │ ┌────────────────────┐
│ │ │ process-upload Lmd │
│ │ │ head_object → │
│ │ │ PutItem │
│ │ └─────────┬──────────┘
│ │ ▼
│ │ ┌────────────────────┐
│ │ ◄── GET (JSON list) ──────── │ list-metadata Lmd │
│ │ │ (Function URL) │
│ │ │ scan DynamoDB │
└──────────────┘ └─────────┬──────────┘
▼
┌──────────────┐
│ DynamoDB │
│file-metadata │
└──────────────┘
Lambda Function URLs are captured during deploy and templated into index.html before it's synced to the website bucket — no manual configuration after deploy.
| Path | What's in it |
|---|---|
lambda/presign.py |
Generates presigned S3 POST. Uses host-reachable endpoint + static creds (see notes). |
lambda/process_upload.py |
S3 event handler. head_object for content-type, writes a metadata row to DynamoDB. |
lambda/list_metadata.py |
Lambda-Function-URL endpoint. Scans the table, returns sorted JSON. |
deployment/deploy.ps1 |
Primary deploy script (Windows PowerShell 5.1). |
deployment/deploy.sh |
Equivalent bash deploy (Linux/Mac/WSL/Git Bash + jq). |
deployment/bucket-cors.json |
CORS config applied to file-uploads. |
website/index.html |
Single-page UI. LocalStack-purple palette. |
- LocalStack running locally (
localstack start -d) awslocalCLI in PATH- Python 3.11+ (used by the Lambda runtime; not strictly required on the host)
- For
deploy.shonly:bash,zip,jq
Windows:
.\deployment\deploy.ps1Linux / Mac / Git Bash:
./deployment/deploy.shThe script prints a Web UI URL at the end. Open it in a browser.
Open the UI and drop a file. Or smoke-test from a terminal:
# get URLs from the deploy script output
curl -s "$PRESIGN_URL?filename=hello.txt" | python -m json.tool
curl -s "$LIST_URL" | python -m json.tool
awslocal s3 ls s3://file-uploads
awslocal dynamodb scan --table-name file-metadataawslocal s3 rb s3://file-uploads --force
awslocal s3 rb s3://file-manager-webapp --force
awslocal dynamodb delete-table --table-name file-metadata
awslocal lambda delete-function --function-name presign
awslocal lambda delete-function --function-name process-upload
awslocal lambda delete-function --function-name list-metadataA handful of LocalStack-specific subtleties this code handles — if you're modifying it, don't undo them.
- Presign endpoint. Inside a LocalStack Lambda, the SDK auto-configures the S3 endpoint to the Docker bridge IP, which a browser on the host can't reach.
presign.pyexplicitly passesendpoint_url=http://localhost.localstack.cloud:4566so the returned URL is host-reachable. The other two Lambdas leaveendpoint_urlunset because they're talking to AWS from inside the container. - Presign credentials. Lambda role credentials include an STS session token. boto3 bakes that into the POST policy as
x-amz-security-token; LocalStack's S3 then rejects uploads ("Policy Condition failed").presign.pyuses statictest/testcreds for presigning to avoid this. - CORS.
bucket-cors.jsonis applied tofile-uploads. Without it, browser presigned-POST is preflighted and blocked. - PowerShell 5.1 BOM.
Set-Content -Encoding utf8writes UTF-8 with BOM. AWS CLI'sfile://JSON parser rejects it.deploy.ps1writes notification JSON via[System.IO.File]::WriteAllTextwith explicit no-BOM encoding. - Handler reference. Every Python file's entry point is
lambda_handler, and the deploy script registers--handler <module>.lambda_handler. (v1 hit a bug where this was registered as<module>.handler.)
MIT