A lightweight Python Flask service that proxies HTTP requests to AWS S3 pre-signed URLs with automatic 302 redirects. Perfect for local development environments where you need quick access to S3-stored files without implementing full AWS SDK integration.
This service intercepts HTTP requests and converts them to time-limited AWS S3 pre-signed URLs. Simply pass the S3 object key as a query parameter, and get a secure, temporary URL that expires after 4 hours (configurable).
URL Format: http://localhost:8087/?tenant=TENANT_ID&objectkey=OBJECT_KEY
Use Cases:
- Local development environments
- Quick prototyping with S3-stored files
- Testing file access without complex AWS integration
- Development servers that need temporary S3 access
- π Simple & Fast - Single Flask container, < 100ms response time
- π Automatic Redirects - HTTP 302 to S3 pre-signed URLs
- π³ Fully Containerized - Docker Compose for easy deployment
- π Comprehensive Logging - Track all requests and errors
- β
Health Check - Built-in
/healthendpoint with automatic Docker health monitoring - π Secure - Pre-signed URLs with 4-hour expiration, read-only filesystem
- π Platform Independent - Works on Windows, Linux, and macOS
- π‘οΈ Hardened Security - Read-only container filesystem with tmpfs for temporary files
- Docker & Docker Compose installed
- AWS S3 bucket access credentials
Pull the pre-built image from Docker Hub:
docker pull ugurcandede/s3-presigned-url-proxy:latestThen use the provided docker-compose.example.yml or create your own docker-compose.yml:
# Option 1: Use the example file directly
docker-compose -f docker-compose.example.yml up -d
# Option 2: Copy and customize
cp docker-compose.example.yml docker-compose.yml
# Edit docker-compose.yml with your AWS credentials
nano docker-compose.yml # or use your preferred editor
# Then start the service
docker-compose up -dNote: Make sure to update the AWS credentials in the compose file before starting the service.
git clone https://github.com/ugurcandede/s3-presigned-url-proxy.git
cd s3-presigned-url-proxyCreate a .env file from the example:
cp .env.example .envEdit .env and set your AWS credentials:
AWS_REGION=us-east-1
AWS_BUCKET=your-bucket-name
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
PRESIGNED_URL_EXPIRATION=14400Note: Never commit the .env file to version control!
docker-compose up -dcurl http://localhost:8087/healthExpected response:
{"status":"ok"}curl -I "http://localhost:8087/?tenant=test&objectkey=files/document.pdf"Expected: HTTP 302 redirect to AWS S3 pre-signed URL
http://localhost:8087/?tenant=TENANT_ID&objectkey=OBJECT_KEY
Parameters:
tenant(required) - Tenant identifierobjectkey(required) - S3 object key (file path in bucket)
# Using curl
curl -I "http://localhost:8087/?tenant=mycompany&objectkey=attachments/2024/12/report.pdf"
# Response
HTTP/1.1 302 FOUND
Location: https://your-bucket.s3.amazonaws.com/attachments/2024/12/report.pdf?...Java/Spring Boot:
# application-dev.yml
user-content:
base-url: http://localhost:8087/Node.js/Express:
const userContentBaseUrl = 'http://localhost:8087/';
const url = `${userContentBaseUrl}?tenant=${tenantId}&objectkey=${objectKey}`;Python:
USER_CONTENT_BASE_URL = 'http://localhost:8087/'
url = f'{USER_CONTENT_BASE_URL}?tenant={tenant_id}&objectkey={object_key}'Health check endpoint to verify service status.
Response:
{"status":"ok"}Main proxy endpoint that generates S3 pre-signed URL and returns 302 redirect.
Success Response:
- Status:
302 Found - Header:
Location: https://...s3.amazonaws.com/...
Error Responses:
Missing parameter:
{
"error": "Missing required parameter: tenant"
}AWS/S3 error:
{
"error": "Failed to generate S3 URL",
"details": "Error message"
}Application β http://localhost:8087/?tenant=X&objectkey=Y
β
Docker Container (Flask App)
- Port 8088 internal
- Port mapping: 8087:8088
β
AWS S3 Pre-signed URL Generation
β
HTTP 302 Redirect
β
Browser/Client β Direct S3 Download
- Host Port: 8087 (external access)
- Container Port: 8088 (Flask internal)
- Why 8087? Port 8088 was already in use on the development machine
For detailed architecture diagrams, see ARCHITECTURE.md
# Start service
docker-compose up -d
# Stop service
docker-compose down
# View logs
docker-compose logs -f
# Check status
docker-compose ps
# Restart service
docker-compose restart
# Rebuild after code changes
docker-compose up -d --buildAll configuration is done via environment variables in docker-compose.yml:
| Variable | Default | Description |
|---|---|---|
AWS_REGION |
- | AWS region (e.g., us-east-1) |
AWS_BUCKET |
- | S3 bucket name |
AWS_ACCESS_KEY_ID |
- | AWS access key |
AWS_SECRET_ACCESS_KEY |
- | AWS secret key |
PRESIGNED_URL_EXPIRATION |
14400 |
URL validity in seconds (4 hours) |
The container has the following resource constraints for optimal performance:
- CPU Limit: 0.25 cores (25% of one CPU)
- Memory Limit: 256 MB maximum
- Memory Reservation: 128 MB guaranteed minimum
- Read-Only Filesystem: Yes (with tmpfs for /tmp and /run)
- Security:
no-new-privilegesenabled
The container includes automatic health monitoring:
- Check Interval: Every 30 seconds
- Timeout: 10 seconds per check
- Retries: 3 failed checks before marking unhealthy
- Start Period: 40 seconds grace period on startup
- Method: Python urllib health endpoint test
View health status: docker ps (shows "healthy" or "unhealthy")
The service logs all requests with the following format:
[TIMESTAMP] [LEVEL] message
Example logs:
[2024-12-19 10:30:45] [INFO] Incoming request: tenant=test objectkey=file.pdf path=
[2024-12-19 10:30:45] [INFO] tenant=test objectkey=file.pdf url_generated=True
[2024-12-19 10:30:45] [INFO] 172.25.0.1 - - [19/Dec/2024 10:30:45] "GET /?tenant=test&objectkey=file.pdf HTTP/1.1" 302 -
-
Check if Docker is running:
docker ps
-
Check logs for errors:
docker-compose logs
-
Verify port 8087 is not in use:
# Linux/Mac lsof -i :8087 # Windows netstat -ano | findstr :8087
- Ensure the container is running:
docker-compose ps - Restart the service:
docker-compose restart - Check firewall settings
- Verify credentials in
docker-compose.yml - Ensure IAM user has
s3:GetObjectpermission - Check logs:
docker-compose logs s3-presigned-url-proxy
- Verify the
objectkeymatches the actual S3 file path - Check S3 bucket permissions
- Test with AWS CLI:
aws s3 ls s3://your-bucket/path/
- LOCAL DEVELOPMENT ONLY - Do not use in production/staging
- Hardcoded Credentials - For dev convenience only
- No Authentication - Service has no auth layer
- No HTTPS - Uses plain HTTP
- Port Exposure - Only expose to localhost, never to internet
For Production:
- Use AWS SDK directly in your application
- Implement proper authentication/authorization
- Use environment variables or secrets management
- Deploy behind HTTPS/TLS
- Use production WSGI server (not Flask dev server)
- Language: Python 3.11
- Framework: Flask 3.0.0
- AWS SDK: boto3 1.34.0
- Container Base: python:3.11-slim
- Port Mapping: 8087 (host) β 8088 (container)
- Pre-signed URL Expiration: 14400 seconds (4 hours)
- Memory Usage: ~50-100 MB
- Response Time: < 100ms (URL generation only)
s3-presigned-url-proxy/
βββ app.py # Flask application
βββ Dockerfile # Docker image definition
βββ docker-compose.yml # Docker Compose configuration
βββ docker-compose.example.yml # Example configuration
βββ requirements.txt # Python dependencies
βββ .env.example # Example environment variables
βββ .gitignore # Git ignore rules
βββ LICENSE # MIT License
βββ ARCHITECTURE.md # Detailed architecture diagrams
βββ README.md # This file
Use the included test script:
Windows:
test.batLinux/Mac:
# Health check
curl http://localhost:8087/health
# Proxy request (show headers)
curl -I "http://localhost:8087/?tenant=test&objectkey=test.pdf"
# Full redirect test
curl -L "http://localhost:8087/?tenant=test&objectkey=test.pdf"
# Error test (missing parameter)
curl "http://localhost:8087/?tenant=test"If you want to use a custom domain instead of localhost:
Linux/Mac:
sudo sh -c 'echo "127.0.0.1 usercontent.development.local" >> /etc/hosts'Windows:
Edit C:\Windows\System32\drivers\etc\hosts as administrator and add:
127.0.0.1 usercontent.development.local
Then access via: http://usercontent.development.local:8087/
Q: Why port 8087 instead of 8088?
A: Port 8088 was already in use on the development machine.
Q: How long are pre-signed URLs valid?
A: Default is 14400 seconds (4 hours). Configure via PRESIGNED_URL_EXPIRATION.
Q: Can I use this in production?
A: No! This is for local development only. Use AWS SDK directly in production.
Q: Does this work with multiple tenants?
A: Yes, the tenant parameter is logged but all tenants use the same S3 bucket.
Q: What S3 permissions are needed?
A: Minimum required: s3:GetObject on the bucket.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.