s3-file-storage
cohen-liel/hivemind/.claude/skills/s3-file-storage/SKILL.md
AWS S3 (and compatible) file storage patterns. Use when implementing file uploads, image storage, document management, CDN delivery, or any cloud file storage.
Skill108 starsChanged 7 months ago
- Sends data out
What's in it
- S3 File Storage Patterns
- Setup (boto3 + Python)
- Upload Patterns
- Direct upload from server
- Presigned URL (client uploads directly — no server bottleneck)
- Client-side direct upload
- Delete
- Image Resizing (on upload)
- Bucket Policy (public read for CDN)
- Rules
---
name: s3-file-storage
description: AWS S3 (and compatible) file storage patterns. Use when implementing file uploads, image storage, document management, CDN delivery, or any cloud file storage.
---
# S3 File Storage Patterns
## Setup (boto3 + Python)
```python
# storage.py
import boto3
from botocore.exceptions import ClientError
import uuid, mimetypes
s3 = boto3.client(
"s3",
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
region_name=settings.AWS_REGION,
# For S3-compatible services (Cloudflare R2, MinIO):
# endpoint_url=settings.S3_ENDPOINT_URL,
)
BUCKET = settings.S3_BUCKET
CDN_URL = settings.CDN_URL # e.g. "https://cdn.example.com"
```
## Upload Patterns
### Direct upload from server
```python
async def upload_file(file: UploadFile, folder: str = "uploads") -> str:
"""Upload file, return public URL."""
# Validate
MAX_SIZE = 10 * 1024 * 1024 # 10MB
ALLOWED_TYPES = {"image/jpeg", "image/png", "image/webp", "application/pdf"}
content = await file.read()
if len(content) > MAX_SIZE:
raise ValueError(f"File too large (max {MAX_SIZE // 1024 // 1024}MB)")
if file.content_type not in ALLOWED_TYPES:
raise ValueError(f"File type not allowed: {file.content_type}")
# Generate unique key
ext = mimetypes.guess_extension(file.content_type) or ".bin"
key = f"{folder}/{uuid.uuid4()}{ext}"
s3.put_object(
Bucket=BUCKET,
Key=key,
Body=content,
ContentType=file.content_type,
CacheControl="max-age=31536000", # 1 year cache for immutable files
)
return f"{CDN_URL}/{key}"
```
### Presigned URL (client uploads directly — no server bottleneck)
```python
def generate_upload_url(filename: str, content_type: str, folder: str = "uploads") -> dict:
"""Client uploads directly to S3 — bypasses your server entirely."""
ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else "bin"
key = f"{folder}/{uuid.uuid4()}.{ext}"
url = s3.generate_presigned_url(
"put_object",
Params={
"Bucket": BUCKET,
"Key": key,
"ContentType": content_type,
"ContentLength": 5 * 1024 * 1024, # Max 5MB
},
ExpiresIn=300, # URL valid for 5 minutes
)
return {
"upload_url": url,
"key": key,
"public_url": f"{CDN_URL}/{key}",
}
# FastAPI endpoint
@router.post("/upload-url")
async def get_upload_url(
filename: str = Query(...),
content_type: str = Query(...),
user: User = Depends(get_current_user),
):
return generate_upload_url(filename, content_type, folder=f"users/{user.id}")
```
### Client-side direct upload
```typescript
async function uploadFile(file: File): Promise<string> {
// 1. Get presigned URL from your API
const { upload_url, public_url } = await fetch('/api/upload-url?' + new URLSearchParams({
filename: file.name,
content_type: file.type,
})).then(r => r.json())
// 2. Upload directly to S3 (no server in the middle)
await fetch(upload_url, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type },
})
return public_url
}
```
## Delete
```python
async def delete_file(key: str):
try:
s3.delete_object(Bucket=BUCKET, Key=key)
except ClientError as e:
logger.error(f"S3 delete failed for {key}: {e}")
def extract_key_from_url(url: str) -> str:
"""Extract S3 key from CDN URL."""
return url.replace(f"{CDN_URL}/", "")
```
## Image Resizing (on upload)
```python
from PIL import Image
import io
def resize_image(content: bytes, max_size: tuple[int, int] = (1920, 1080)) -> bytes:
img = Image.open(io.BytesIO(content))
img.thumbnail(max_size, Image.LANCZOS)
if img.mode in ("RGBA", "P"):
img = img.convert("RGB")
output = io.BytesIO()
img.save(output, format="JPEG", optimize=True, quality=85)
return output.getvalue()
```
## Bucket Policy (public read for CDN)
```json
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "PublicRead",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket/*"
}]
}
```
## Rules
- NEVER store files on local disk in production (not portable, not scalable)
- Validate file type server-side (don't trust Content-Type from client)
- Validate file size before uploading (not after reading all bytes into RAM)
- Use presigned URLs for large files (avoids server memory/bandwidth)
- Generate unique keys (UUID) — never use original filenames (path traversal risk)
- Set CacheControl headers for immutable files (images don't change after upload)
- Delete from S3 when deleting the record in DB (no orphaned files)
- Use lifecycle rules to auto-delete tmp/ folder after 24h
More agent context in cohen-liel/hivemind
75 other files this repository gives its agents, the first 60 shown.
CLAUDE.md
Skill
- algorithmic-art.claude/skills/algorithmic-art/SKILL.md
- api-design.claude/skills/api-design/SKILL.md
- apple-notes.claude/skills/apple-notes/SKILL.md
- apple-reminders.claude/skills/apple-reminders/SKILL.md
- article-writing.claude/skills/article-writing/SKILL.md
- async-python.claude/skills/async-python/SKILL.md
- brand-guidelines.claude/skills/brand-guidelines/SKILL.md
- camsnap.claude/skills/camsnap/SKILL.md
- canvas-design.claude/skills/canvas-design/SKILL.md
- celery-tasks.claude/skills/celery-tasks/SKILL.md
- claude-api.claude/skills/claude-api/SKILL.md
- coding-agent.claude/skills/coding-agent/SKILL.md
- content-engine.claude/skills/content-engine/SKILL.md
- diffs.claude/skills/diffs/SKILL.md
- doc-coauthoring.claude/skills/doc-coauthoring/SKILL.md
- docker-deployment.claude/skills/docker-deployment/SKILL.md
- docx.claude/skills/docx/SKILL.md
- e2e-testing.claude/skills/e2e-testing/SKILL.md
- email-service.claude/skills/email-service/SKILL.md
- fastapi-backend.claude/skills/fastapi-backend/SKILL.md
- frontend-design.claude/skills/frontend-design/SKILL.md
- frontend-slides.claude/skills/frontend-slides/SKILL.md
- gh-issues.claude/skills/gh-issues/SKILL.md
- github.claude/skills/github/SKILL.md
- git-workflow.claude/skills/git-workflow/SKILL.md
- graphql-api.claude/skills/graphql-api/SKILL.md
- healthcheck.claude/skills/healthcheck/SKILL.md
- internal-comms.claude/skills/internal-comms/SKILL.md
- investor-materials.claude/skills/investor-materials/SKILL.md
- jwt-authentication.claude/skills/jwt-authentication/SKILL.md
- market-research.claude/skills/market-research/SKILL.md
- mcp-builder.claude/skills/mcp-builder/SKILL.md
- mermaid-diagrams.claude/skills/mermaid-diagrams/SKILL.md
- microservices.claude/skills/microservices/SKILL.md
- mobile-react-native.claude/skills/mobile-react-native/SKILL.md
- model-usage.claude/skills/model-usage/SKILL.md
- nextjs-fullstack.claude/skills/nextjs-fullstack/SKILL.md
- nodejs-express.claude/skills/nodejs-express/SKILL.md
- obsidian.claude/skills/obsidian/SKILL.md
- openai-whisper.claude/skills/openai-whisper/SKILL.md
- oracle.claude/skills/oracle/SKILL.md
- pdf.claude/skills/pdf/SKILL.md
- peekaboo.claude/skills/peekaboo/SKILL.md
- planning-with-files.claude/skills/planning-with-files/SKILL.md
- postgres-database.claude/skills/postgres-database/SKILL.md
- pptx.claude/skills/pptx/SKILL.md
- prisma-orm.claude/skills/prisma-orm/SKILL.md
- prose.claude/skills/prose/SKILL.md
- pytest-patterns.claude/skills/pytest-patterns/SKILL.md
- react-typescript.claude/skills/react-typescript/SKILL.md
- redis-caching.claude/skills/redis-caching/SKILL.md
- security-review.claude/skills/security-review/SKILL.md
- session-logs.claude/skills/session-logs/SKILL.md
- skill-creator.claude/skills/skill-creator/SKILL.md
- slack-gif-creator.claude/skills/slack-gif-creator/SKILL.md
- sqlalchemy-orm.claude/skills/sqlalchemy-orm/SKILL.md
- state-management.claude/skills/state-management/SKILL.md
- strategic-compact.claude/skills/strategic-compact/SKILL.md
- stripe-payments.claude/skills/stripe-payments/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
No reports yet. Be the first to say whether it worked.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool public_context_discussion, action report. How to connect one.

