<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: DeployBlocks</title>
    <description>The latest articles on DEV Community by DeployBlocks (@deployblocks).</description>
    <link>https://dev.to/deployblocks</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4149085%2F6c305fa1-1a07-445a-a424-980787114a3b.png</url>
      <title>DEV Community: DeployBlocks</title>
      <link>https://dev.to/deployblocks</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/deployblocks"/>
    <language>en</language>
    <item>
      <title>Let Users Upload Files to S3 Without Giving Them AWS Access</title>
      <dc:creator>DeployBlocks</dc:creator>
      <pubDate>Wed, 30 Sep 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/deployblocks/let-users-upload-files-to-s3-without-giving-them-aws-access-2dkl</link>
      <guid>https://dev.to/deployblocks/let-users-upload-files-to-s3-without-giving-them-aws-access-2dkl</guid>
      <description>&lt;p&gt;I wanted a simple way to let people upload files to an AWS account without giving them access to AWS itself.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The requirements were fairly straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users should only need a web browser.&lt;/li&gt;
&lt;li&gt;Files should end up in an S3 bucket owned by the application owner.&lt;/li&gt;
&lt;li&gt;Users should never see AWS credentials.&lt;/li&gt;
&lt;li&gt;There shouldn't be a server to manage.&lt;/li&gt;
&lt;li&gt;The whole thing should be deployable without a complicated setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built S3EasyUpload, a serverless file upload portal running entirely on AWS.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/s3easyupload/s3easyupload" rel="noopener noreferrer"&gt;https://github.com/s3easyupload/s3easyupload&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;A common way to build a file upload feature is to send files to a backend and let the backend upload them to S3.&lt;/p&gt;

&lt;p&gt;That works, but for a simple upload portal I didn't want an application server acting as a middleman for every upload.&lt;/p&gt;

&lt;p&gt;Instead, the browser requests a presigned URL and uploads directly to S3.&lt;/p&gt;

&lt;p&gt;This keeps the architecture small and lets AWS handle the transfer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;S3EasyUpload uses a small set of managed AWS services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon S3 for the frontend and uploaded files&lt;/li&gt;
&lt;li&gt;Amazon CloudFront for HTTPS and content delivery&lt;/li&gt;
&lt;li&gt;API Gateway for the API&lt;/li&gt;
&lt;li&gt;AWS Lambda for the backend logic&lt;/li&gt;
&lt;li&gt;Amazon S3 presigned URLs for direct uploads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application is fully serverless. There are no EC2 instances, containers or databases to manage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb716d18g3ejfp10tyws0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb716d18g3ejfp10tyws0.png" alt="S3EasyUpload serverless AWS architecture showing CloudFront, API Gateway, Lambda and S3" width="601" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At a high level, the upload flow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user opens the upload portal through CloudFront.&lt;/li&gt;
&lt;li&gt;The browser requests a presigned upload URL from the API.&lt;/li&gt;
&lt;li&gt;Lambda generates and returns a temporary presigned URL.&lt;/li&gt;
&lt;li&gt;The browser uploads the file directly to S3.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why upload directly to S3?
&lt;/h2&gt;

&lt;p&gt;This was one of the key design decisions.&lt;/p&gt;

&lt;p&gt;Instead of routing large uploads through an application server, the browser uploads directly to S3 using a temporary presigned URL.&lt;/p&gt;

&lt;p&gt;The backend's only responsibility is generating that URL, while AWS handles the file transfer.&lt;/p&gt;

&lt;p&gt;Users never receive AWS credentials and only get the permissions required for a specific upload.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the user sees
&lt;/h2&gt;

&lt;p&gt;The interface is intentionally simple.&lt;/p&gt;

&lt;p&gt;Users open the CloudFront URL, drag and drop files, and monitor upload progress.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy1lbgcdytbqttpu1pixy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy1lbgcdytbqttpu1pixy.png" alt="S3EasyUpload file upload interface with drag and drop" width="749" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No AWS console access or AWS account is required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping temporary files temporary
&lt;/h2&gt;

&lt;p&gt;The public version is designed around temporary file uploads.&lt;/p&gt;

&lt;p&gt;Uploaded objects are automatically removed after 24 hours.&lt;/p&gt;

&lt;p&gt;This makes the public version useful for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;testing file collection workflows&lt;/li&gt;
&lt;li&gt;temporary file transfers&lt;/li&gt;
&lt;li&gt;proof-of-concept deployments&lt;/li&gt;
&lt;li&gt;development environments&lt;/li&gt;
&lt;li&gt;internal experiments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The retention period is intentionally limited to keep the project simple and lightweight.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security considerations
&lt;/h2&gt;

&lt;p&gt;There are a few important security properties in the architecture.&lt;/p&gt;

&lt;p&gt;The upload bucket is private, and users don't receive AWS credentials.&lt;/p&gt;

&lt;p&gt;Uploads use S3 presigned URLs, which provide temporary access to the required S3 operation.&lt;/p&gt;

&lt;p&gt;The application is served over HTTPS through CloudFront.&lt;/p&gt;

&lt;p&gt;The main idea is to keep the permissions and responsibilities separated:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudFront serves the application.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API Gateway and Lambda handle the API request.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S3 handles the file transfer and storage.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a relatively small security surface compared with running a traditional application server that receives and processes every uploaded file.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying the infrastructure
&lt;/h2&gt;

&lt;p&gt;One of the things I wanted to keep simple was the initial deployment.&lt;/p&gt;

&lt;p&gt;The project uses a single CloudFormation template.&lt;/p&gt;

&lt;p&gt;The basic deployment is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download the CloudFormation template.&lt;/li&gt;
&lt;li&gt;Open the AWS CloudFormation console.&lt;/li&gt;
&lt;li&gt;Create a stack using &lt;code&gt;main.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Configure the parameters.&lt;/li&gt;
&lt;li&gt;Deploy the stack.&lt;/li&gt;
&lt;li&gt;Open the generated CloudFront URL.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once the stack finishes deploying, the upload portal is ready to use.&lt;/p&gt;

&lt;p&gt;Because everything is defined in CloudFormation, it's easy to experiment in a separate AWS account without manually creating resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;Because the application is entirely serverless, there is no always-on server running in the background.&lt;/p&gt;

&lt;p&gt;You pay the normal AWS charges for the resources you actually use.&lt;/p&gt;

&lt;p&gt;The main cost drivers are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;S3 storage&lt;/li&gt;
&lt;li&gt;CloudFront data transfer&lt;/li&gt;
&lt;li&gt;API Gateway requests&lt;/li&gt;
&lt;li&gt;Lambda invocations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For small personal or internal projects, costs can remain relatively modest, although actual pricing depends entirely on traffic, storage volume and upload activity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;Everything shown in this article is available in the public repository.&lt;/p&gt;

&lt;p&gt;You can review the code, deploy the stack in your own AWS account, and adapt it to your own requirements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/s3easyupload/s3easyupload" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons learned and next steps
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;controlling who can upload files&lt;/li&gt;
&lt;li&gt;limiting abuse and automated uploads&lt;/li&gt;
&lt;li&gt;managing retention policies&lt;/li&gt;
&lt;li&gt;monitoring usage and failures&lt;/li&gt;
&lt;li&gt;supporting custom domains&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those aren't necessarily required for experimentation, which is why the public version stays intentionally lightweight.&lt;/p&gt;

&lt;p&gt;But they become increasingly important once an upload portal starts handling real users and real workloads.&lt;/p&gt;

&lt;p&gt;That is ultimately what led to the Professional Edition, which expands on the same serverless architecture while adding operational and security-focused capabilities.&lt;/p&gt;

&lt;p&gt;If you're curious about the architecture, the public version is probably the best place to start.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;For readers interested in that version, more details are available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://deployblocks.gumroad.com/l/s3easyupload" rel="noopener noreferrer"&gt;S3EasyUpload Professional&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The goal was simply to let users upload files into an AWS account without needing AWS access or a traditional backend server.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I'd also be interested to hear how you would extend the architecture or what features you would add for your own use cases.&lt;/p&gt;

&lt;p&gt;If you end up deploying it, I'd love to hear how you're using it.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>s3</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
