Purpose
Step-by-step implementation
PROMPT
Purpose
He wants all videos moved from the EC2 server’s local disk to one common private AWS S3 bucket.
The purpose is to:
Reduce EC2 disk usage.
Prevent the EC2 disk from becoming full.
Store large numbers of videos reliably.
Keep every organization’s videos separated.
Organize videos by albums.
Secure videos from unauthorized access.
Preserve videos even when EC2 is restarted or replaced.
Store each uploaded video’s S3 location in the database.
Required structure:
S3 bucket: videos.wizbrand.com
└── organizations
└── {organization_id}
└── albums
└── {album_id}
└── {video_file}
Example:
videos.wizbrand.com
└── organizations
└── mymedicplus
└── albums
├── rajesh
│ ├── video1.mp4
│ └── video2.mp4
└── rajesh1
└── video3.mp4
Step-by-step implementation
Step 1: Verify the S3 bucket
Confirm that this bucket already exists:
videos.wizbrand.com
Also confirm its AWS region, for example:
ap-south-1
Keep S3 Block Public Access enabled because the videos should remain private.
Step 2: Create an IAM role for EC2
Create an IAM role:
WizBrandVideosEC2Role
Select:
Trusted entity: AWS service
Service: EC2
This role allows the application running on EC2 to access S3 without storing permanent AWS access keys.
Step 3: Add S3 permissions to the IAM role
Attach this custom policy to the role:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListVideoBucket",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": "arn:aws:s3:::videos.wizbrand.com",
"Condition": {
"StringLike": {
"s3:prefix": [
"organizations",
"organizations/*"
]
}
}
},
{
"Sid": "ManageVideoObjects",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:AbortMultipartUpload"
],
"Resource": "arn:aws:s3:::videos.wizbrand.com/organizations/*"
}
]
}
This allows the EC2 application to:
Upload videos.
Read videos.
Delete videos.
List uploaded videos.
Perform multipart uploads for large videos.
Step 4: Attach the role to EC2
Open:
AWS Console
→ EC2
→ Instances
→ Select your instance
→ Actions
→ Security
→ Modify IAM role
Attach:
WizBrandVideosEC2Role
If EC2 already has an IAM role, add the S3 policy to that existing role instead of replacing it unnecessarily.
Step 5: Apply a bucket policy
Replace the AWS account ID and role name:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowWizBrandEC2Role",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/WizBrandVideosEC2Role"
},
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": "arn:aws:s3:::videos.wizbrand.com",
"Condition": {
"StringLike": {
"s3:prefix": [
"organizations",
"organizations/*"
]
}
}
},
{
"Sid": "AllowVideoObjectOperations",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/WizBrandVideosEC2Role"
},
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:AbortMultipartUpload"
],
"Resource": "arn:aws:s3:::videos.wizbrand.com/organizations/*"
},
{
"Sid": "DenyInsecureConnection",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::videos.wizbrand.com",
"arn:aws:s3:::videos.wizbrand.com/*"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}
Apply it at:
S3
→ videos.wizbrand.com
→ Permissions
→ Bucket policy
Step 6: Configure Laravel .env
Add:
FILESYSTEM_DISK=s3
AWS_DEFAULT_REGION=ap-south-1
AWS_BUCKET=videos.wizbrand.com
AWS_USE_PATH_STYLE_ENDPOINT=false
AWS_URL=
AWS_ENDPOINT=
When using the EC2 IAM role, do not add permanent credentials:
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
The AWS SDK automatically obtains temporary credentials from the EC2 IAM role.
Clear Laravel configuration cache:
/opt/lampp/bin/php artisan optimize:clear
Step 7: Create the S3 object path
When a video is uploaded, the backend should generate:
$path = "organizations/{$organization->id}/albums/{$album->id}";
Generate a unique filename:
$filename = (string) Str::uuid() . '.' . $file->getClientOriginalExtension();
Complete object key:
$objectKey = "{$path}/{$filename}";
Example:
organizations/25/albums/89/550e8400-e29b-41d4-a716.mp4
S3 automatically presents prefixes as folders. You do not need to create empty folders beforehand.
Step 8: Validate organization and album ownership
Before uploading, verify:
$album = Album::where('id', $albumId)
->where('organization_id', $currentUser->organization_id)
->firstOrFail();
Never accept organization_id directly from the upload request without authorization.
Use the authenticated user’s organization:
$organizationId = auth()->user()->organization_id;
This prevents users from uploading into another organization’s folder.
Step 9: Upload the video to S3
Laravel example:
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
$organizationId = auth()->user()->organization_id;
$album = Album::where('id', $request->album_id)
->where('organization_id', $organizationId)
->firstOrFail();
$file = $request->file('video');
$filename = Str::uuid() . '.' . $file->getClientOriginalExtension();
$objectKey = "organizations/{$organizationId}/albums/{$album->id}/{$filename}";
Storage::disk('s3')->putFileAs(
"organizations/{$organizationId}/albums/{$album->id}",
$file,
$filename,
['visibility' => 'private']
);
Step 10: Store video information in the database
Recommended database columns:
id
organization_id
album_id
disk
bucket
object_key
original_name
mime_type
file_size
created_by
created_at
Example record:
organization_id: 25
album_id: 89
disk: s3
bucket: videos.wizbrand.com
object_key: organizations/25/albums/89/uuid-video.mp4
original_name: introduction.mp4
Example:
Video::create([
'organization_id' => $organizationId,
'album_id' => $album->id,
'disk' => 's3',
'bucket' => config('filesystems.disks.s3.bucket'),
'object_key' => $objectKey,
'original_name' => $file->getClientOriginalName(),
'mime_type' => $file->getMimeType(),
'file_size' => $file->getSize(),
'created_by' => auth()->id(),
]);
Store the stable object_key in the database. Do not store an expiring presigned URL as the permanent video URL.
Step 11: Generate a temporary viewing URL
Because the bucket is private:
$url = Storage::disk('s3')->temporaryUrl(
$video->object_key,
now()->addMinutes(30)
);
This URL expires after 30 minutes.
Alternatively, use CloudFront with signed URLs when video traffic becomes large.
Step 12: Handle album creation
Creating an album should primarily create a database record:
albums
├── id
├── organization_id
├── name
├── created_by
└── timestamps
You do not have to create an empty S3 folder. The album prefix appears automatically when its first video is uploaded:
organizations/{organization_id}/albums/{album_id}/{video}
Step 13: Handle video deletion
Verify ownership before deleting:
$video = Video::where('id', $videoId)
->where('organization_id', auth()->user()->organization_id)
->firstOrFail();
Storage::disk('s3')->delete($video->object_key);
$video->delete();
Delete the S3 object first, then remove or soft-delete its database record.
Step 14: Test the complete workflow
Test these cases:
User can upload a video to their organization and album.
The object appears in the correct S3 prefix.
The object key is stored in the database.
Authorized users can watch the video.
Unauthorized users cannot access it.
A user cannot upload to another organization.
Video deletion removes both the S3 object and database record.
Large multipart video uploads work correctly.
EC2 does not retain unnecessary temporary video files.
Final architecture:
User
↓
videos.wizbrand.com
↓
Authenticate and identify organization
↓
Validate album ownership
↓
Upload using EC2 IAM role
↓
Private S3 bucket
↓
organizations/{org-id}/albums/{album-id}/{video}
↓
Store S3 object key in database
↓
Generate temporary URL for authorized viewing
PROMPT
Requirement of use of videos storage to aws s3 as below for videos.wizbrand.com;
- Mind it - This server is aws ec2 already.
- For videos.wizbrand.com, there is one S3 Bucket name is configured in .env file. I.e videos.wizbrand.com
- That bucket, You must used to create org specific folder inside a bucket. I.e, for each org there would be 1 folder dedicated.
- Each videos uploaded by any users as part of that org, must should be uploaded to their dedicated folder in the common s3 bucket.
- EACH ALBUM CREATED By users would become a folder under org.
- Files would belong to album == respective folder of that org.
- each file uploaded to s3 bucket via videos.wizbrand.com, its Object URL must be stored in db.
Eg.
Org name - mymedicplus
Album name - rajesh
Album name - rajesh1
Q1 - Now research requirement and suggest me the Bucket policy which is need for this bucket to be applied on S3.
Q2 - Also, Do we need to set any IAM permission for this as well? Or Do we need to set any IAM roles for it as part of requirement. Plz suggest me.
Top comments (0)