Learn how to upload photos and videos from your phone directly to an AWS EC2 server using Python and FastAPI.
๐ Overview
One of the most common requirements in modern applications is the ability to upload files from a browser to a server.
Whether you’re building:
- A media management platform
- A document portal
- A machine learning application
- An image processing workflow
- A video processing pipeline
- A backup solution
The first step is always the same:
Get the file from the user’s device to your server.
In this tutorial, we’ll build a lightweight file upload application using:
- AWS EC2
- Amazon Linux 2023
- Python
- FastAPI
- HTML
- JavaScript
The result is a mobile-friendly web page that allows users to upload photos and videos directly to an EC2 instance.
Table of Contents
- ๐ Overview
- ๐๏ธ Final Architecture
- โ๏ธ Step 1: Launch an AWS EC2 Instance
- ๐ Step 2: Connect to the Server
- ๐ Step 3: Update the Server
- ๐ Step 4: Create the Project Directory
- ๐ Step 5: Create a Python Virtual Environment
- ๐ฆ Step 6: Install Dependencies
- ๐ Step 7: Create Required Directories
- โ๏ธ Step 8: Build the Backend
- ๐จ Step 9: Build the Frontend
- ๐ Step 10: Start the Application
- ๐ฅ Step 11: Configure AWS Security Groups
- ๐ Real-World Debugging Lesson
- ๐ฑ Testing the Application
- ๐ Verify Uploaded Files
- ๐ธ Suggested Screenshots
- ๐ Source Code
- ๐ Next Improvements
- Final Thoughts
๐๏ธ Final Architecture
Phone / Laptop
โ
โผ
FastAPI Web UI
โ
โผ
AWS EC2 Instance
โ
โผ
uploads/
The workflow is intentionally simple:
- User selects a file.
- Browser uploads the file.
- FastAPI receives the file.
- File is saved to disk.
No database required.
No external storage required.
No frontend framework required.
โ๏ธ Step 1: Launch an AWS EC2 Instance
Launch a new EC2 instance.
Instance Configuration
- Amazon Linux 2023
- t2.nano
- Public IPv4 Enabled
Create and download a new key pair.
Example:
MediaUploadServer.pem
๐ Step 2: Connect to the Server
Connect using SSH:
ssh -i .\MediaUploadServer.pem ec2-user@13.220.113.175
Fixing “Unprotected Private Key” Errors on Windows
If Windows reports that your PEM file permissions are too open, run:
icacls .\MediaUploadServer.pem /inheritance:r
icacls .\MediaUploadServer.pem /grant:r "$($env:USERNAME):(R)"
icacls .\MediaUploadServer.pem /remove "Authenticated Users" "BUILTIN\Users" "Everyone"
Then reconnect.
๐ Step 3: Update the Server
After logging in:
sudo yum update -y
sudo dnf update -y
Install Python:
sudo yum install python3 -y
sudo yum install python3-pip -y
Verify installation:
python3 --version
๐ Step 4: Create the Project Directory
Create a project folder:
mkdir uploader
cd uploader
๐ Step 5: Create a Python Virtual Environment
Create a virtual environment:
python3 -m venv venv
Activate it:
source venv/bin/activate
Your terminal should now display:
(venv)
indicating that the virtual environment is active.
Why Use a Virtual Environment?
A virtual environment keeps project dependencies isolated from the operating system.
Without a virtual environment:
System Python
โโโ FastAPI
โโโ Django
โโโ NumPy
โโโ Other Projects
With a virtual environment:
uploader/
โโโ venv/
Everything stays self-contained.
Verify the Virtual Environment
Before activation:
which python3
Example:
/usr/bin/python3
After activation:
which python
Example:
/home/ec2-user/uploader/venv/bin/python
This confirms that Python commands are now using the virtual environment.
Leaving the Virtual Environment
When finished:
deactivate
Do You Need to Activate It Every Time?
For development:
cd uploader
source venv/bin/activate
Yes.
For production services, you can directly reference the virtual environment’s Python executable:
ExecStart=/home/ec2-user/uploader/venv/bin/uvicorn app:app --host 0.0.0.0 --port 8000
No manual activation required.
๐ฆ Step 6: Install Dependencies
Install FastAPI and related packages:
pip install fastapi uvicorn python-multipart
Packages:
FastAPI
Python web framework.
Uvicorn
ASGI web server.
python-multipart
Required for file uploads.
Where Are Packages Installed?
Because we activated the virtual environment, packages are installed into:
uploader/venv/lib/python3.x/site-packages/
instead of:
/usr/lib/python3.x/
Save Dependencies
Generate a requirements file:
pip freeze > requirements.txt
Project structure:
uploader/
โโโ app.py
โโโ requirements.txt
โโโ uploads/
โโโ static/
โโโ venv/
๐ Step 7: Create Required Directories
Create storage folders:
mkdir uploads
mkdir static
โ๏ธ Step 8: Build the Backend
Create:
nano app.py
Paste:
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import FileResponse
import os
app = FastAPI()
UPLOAD_DIR = "uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)
@app.get("/")
def home():
return FileResponse("static/index.html")
@app.post("/upload")
async def upload(file: UploadFile = File(...)):
filepath = os.path.join(UPLOAD_DIR, file.filename)
with open(filepath, "wb") as f:
f.write(await file.read())
return {
"success": True,
"filename": file.filename
}
๐ Nano Editor Tips
To save:
CTRL + O
Enter
To exit:
CTRL + X
Save and exit sequence:
CTRL + O
Enter
CTRL + X
๐จ Step 9: Build the Frontend
Create:
nano static/index.html
Paste:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Uploader</title>
</head>
<body>
<h2>Upload Photo or Video</h2>
<input type="file" id="file">
<br><br>
<button onclick="uploadFile()">
Upload
</button>
<p id="status"></p>
<script>
async function uploadFile() {
const file = document.getElementById("file").files[0];
if (!file) {
alert("Select a file");
return;
}
const formData = new FormData();
formData.append("file", file);
document.getElementById("status").innerText = "Uploading...";
const response = await fetch("/upload", {
method: "POST",
body: formData
});
const result = await response.json();
document.getElementById("status").innerText =
"Uploaded: " + result.filename;
}
</script>
</body>
</html>
๐ Step 10: Start the Application
Run:
uvicorn app:app --host 0.0.0.0 --port 8000
Expected output:
Uvicorn running on http://0.0.0.0:8000
๐ฅ Step 11: Configure AWS Security Groups
This step is critical.
Open the EC2 Security Group.
Add:
Type: Custom TCP
Port: 8000
Source: 0.0.0.0/0
๐ Real-World Debugging Lesson
The application initially failed to load from the browser.
Everything appeared correct:
โ FastAPI running
โ Uvicorn listening
โ Public IP assigned
โ Route Table configured
โ Network ACL configured
Yet the browser timed out.
After investigating, the issue turned out to be:
Security Group Port: 8080
Application Port: 8000
The Security Group was opening port 8080 while FastAPI was running on port 8000.
Once port 8000 was added to the Security Group, the application became accessible immediately.
A great reminder that networking issues are often caused by small configuration mismatches.
๐ฑ Testing the Application
Open:
http://YOUR_PUBLIC_IP:8000
From:
- Android
- iPhone
- Laptop
- Tablet
Select a photo or video.
Press Upload.
Success!
๐ Verify Uploaded Files
On the server:
ls -lh uploads
Example:
reel+aud.mp4
The uploaded file is now stored on the EC2 instance.
๐ธ Suggested Screenshots
Add screenshots for:
AWS EC2 Instance
[Insert Screenshot]
SSH Connection
[Insert Screenshot]
Virtual Environment
[Insert Screenshot]
FastAPI Running
[Insert Screenshot]
Security Group Configuration
[Insert Screenshot]
Upload Page
[Insert Screenshot]
Successful Upload
[Insert Screenshot]
๐ Source Code
GitHub Repository:
[Insert GitHub Link]
๐ Next Improvements
Once the upload pipeline is working, you can extend it in many ways:
- Upload progress bars
- Multiple file uploads
- Drag-and-drop support
- Authentication
- User accounts
- HTTPS with Nginx
- Domain names
- Cloud storage integration
- Image optimization
- Video processing
- AI-powered file analysis
The possibilities are endless.
Final Thoughts
Building a file upload server doesn’t need to be complicated.
With a small EC2 instance, FastAPI, and a few lines of HTML and JavaScript, it’s possible to create a functional upload system in less than an hour.
The final result is a lightweight, mobile-friendly upload service that can serve as the foundation for countless applications and workflows.
Sometimes the best approach is to start with the simplest working version and build from there.