I wanted a simple way to let people upload files to an AWS account without giving them access to AWS itself.
More specifically, I wanted users to upload files into an S3 bucket using nothing more than a browser, without AWS credentials and without a traditional backend handling the file transfer.
The requirements were fairly straightforward:
- Users should only need a web browser.
- Files should end up in an S3 bucket owned by the application owner.
- Users should never see AWS credentials.
- There shouldn't be a server to manage.
- The whole thing should be deployable without a complicated setup.
So I built S3EasyUpload, a serverless file upload portal running entirely on AWS.
I decided to make the project publicly available so anyone can deploy it, experiment with the architecture, or use it as a starting point for their own implementation.
GitHub: https://github.com/s3easyupload/s3easyupload
The problem
A common way to build a file upload feature is to send files to a backend and let the backend upload them to S3.
That works, but for a simple upload portal I didn't want an application server acting as a middleman for every upload.
Instead, the browser requests a presigned URL and uploads directly to S3.
This keeps the architecture small and lets AWS handle the transfer.
The architecture
S3EasyUpload uses a small set of managed AWS services:
- Amazon S3 for the frontend and uploaded files
- Amazon CloudFront for HTTPS and content delivery
- API Gateway for the API
- AWS Lambda for the backend logic
- Amazon S3 presigned URLs for direct uploads
The application is fully serverless. There are no EC2 instances, containers or databases to manage.
At a high level, the upload flow looks like this:
- The user opens the upload portal through CloudFront.
- The browser requests a presigned upload URL from the API.
- Lambda generates and returns a temporary presigned URL.
- The browser uploads the file directly to S3.
Why upload directly to S3?
This was one of the key design decisions.
Instead of routing large uploads through an application server, the browser uploads directly to S3 using a temporary presigned URL.
The backend's only responsibility is generating that URL, while AWS handles the file transfer.
Users never receive AWS credentials and only get the permissions required for a specific upload.
What the user sees
The interface is intentionally simple.
Users open the CloudFront URL, drag and drop files, and monitor upload progress.
No AWS console access or AWS account is required.
Keeping temporary files temporary
The public version is designed around temporary file uploads.
Uploaded objects are automatically removed after 24 hours.
This makes the public version useful for things like:
- testing file collection workflows
- temporary file transfers
- proof-of-concept deployments
- development environments
- internal experiments
The retention period is intentionally limited to keep the project simple and lightweight.
Some use cases require longer retention periods, finer access control, or additional operational capabilities. Those are areas I explored separately while expanding the project beyond the public edition.
Security considerations
There are a few important security properties in the architecture.
The upload bucket is private, and users don't receive AWS credentials.
Uploads use S3 presigned URLs, which provide temporary access to the required S3 operation.
The application is served over HTTPS through CloudFront.
The main idea is to keep the permissions and responsibilities separated:
CloudFront serves the application.
API Gateway and Lambda handle the API request.
S3 handles the file transfer and storage.
This is a relatively small security surface compared with running a traditional application server that receives and processes every uploaded file.
Of course, the right security model depends on the use case. A public-facing or business-critical upload service needs to be reviewed according to its specific requirements.
Deploying the infrastructure
One of the things I wanted to keep simple was the initial deployment.
The project uses a single CloudFormation template.
The basic deployment is:
- Download the CloudFormation template.
- Open the AWS CloudFormation console.
- Create a stack using
main.yaml. - Configure the parameters.
- Deploy the stack.
- Open the generated CloudFront URL.
Once the stack finishes deploying, the upload portal is ready to use.
Because everything is defined in CloudFormation, it's easy to experiment in a separate AWS account without manually creating resources.
What it costs
Because the application is entirely serverless, there is no always-on server running in the background.
You pay the normal AWS charges for the resources you actually use.
The main cost drivers are:
- S3 storage
- CloudFront data transfer
- API Gateway requests
- Lambda invocations
For small personal or internal projects, costs can remain relatively modest, although actual pricing depends entirely on traffic, storage volume and upload activity.
Try it yourself
Everything shown in this article is available in the public repository.
You can review the code, deploy the stack in your own AWS account, and adapt it to your own requirements.
Lessons learned and next steps
While building the project, I ran into a few things that are easy to overlook when moving from a proof of concept to a real-world upload service.
For example:
- controlling who can upload files
- limiting abuse and automated uploads
- managing retention policies
- monitoring usage and failures
- supporting custom domains
Those aren't necessarily required for experimentation, which is why the public version stays intentionally lightweight.
But they become increasingly important once an upload portal starts handling real users and real workloads.
That is ultimately what led to the Professional Edition, which expands on the same serverless architecture while adding operational and security-focused capabilities.
If you're curious about the architecture, the public version is probably the best place to start.
One thing I wanted to avoid was creating two completely different products. The more advanced version follows the same architecture and deployment philosophy, with additional capabilities aimed at production-oriented environments.
For readers interested in that version, more details are available here:
Final thoughts
The goal was simply to let users upload files into an AWS account without needing AWS access or a traditional backend server.
CloudFront, API Gateway, Lambda, S3 and presigned URLs turned out to be enough to make that possible while keeping the architecture small and easy to deploy.
If you're interested in serverless patterns, direct-to-S3 uploads, or CloudFormation-based deployments, feel free to explore the public version and adapt it to your own needs.
I'd also be interested to hear how you would extend the architecture or what features you would add for your own use cases.
If you end up deploying it, I'd love to hear how you're using it.


Top comments (0)