
Bun.js + S3 + Sharp: an image upload and optimization pipeline
Build a secure Bun.js image pipeline with presigned S3 URLs, Sharp resizing, WebP variants, and storage-safe validation.
Bun validates the requested file and returns a short-lived presigned PUT URL. The browser uploads directly to S3-compatible storage. A worker reads the original, creates WebP and thumbnail variants with Sharp, and publishes only verified objects.
Architecture without proxying file bytes
Bun is the control plane; object storage is the data plane.
01
Prepare upload
The client sends filename, MIME type, and size. The API checks the allowlist and returns a key plus presigned URL.
02
Upload directly
The browser performs PUT to S3. Your Bun process does not hold the file in memory.
03
Process
A worker reads the original and Sharp creates a thumbnail and WebP preview.
04
Publish
Store only verified keys, dimensions, MIME type, and processing status in the database.
The file goes directly to storage, so Bun handles access control and metadata instead of proxying large bytes.
Section architecture screenshotStep 1: install the SDK and Sharp
Add dependencies
bun add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner sharpSharp has native components; verify optional dependencies for the target Docker platform.
Set the bucket
S3_REGION=eu-central-1
S3_BUCKET=media
S3_ENDPOINT=https://s3.example.com
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...S3_ENDPOINT also supports R2 and MinIO. Never expose the secret key to the frontend.
Step 2: issue a presigned PUT URL
Keep the URL short-lived, use a random object key, and bind it to the expected Content-Type. Verify the actual object size after upload.
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const s3 = new S3Client({ region: Bun.env.S3_REGION!, endpoint: Bun.env.S3_ENDPOINT || undefined, forcePathStyle: Boolean(Bun.env.S3_ENDPOINT) });
const allowed = new Set(["image/jpeg", "image/png", "image/webp"]);
export async function createUploadUrl(contentType: string) {
if (!allowed.has(contentType)) throw new Error("Unsupported image type");
const key = `originals/${crypto.randomUUID()}`;
const command = new PutObjectCommand({ Bucket: Bun.env.S3_BUCKET!, Key: key, ContentType: contentType, ServerSideEncryption: "AES256" });
return { key, url: await getSignedUrl(s3, command, { expiresIn: 300 }) };
}Prepare uploads and track processing
Do not mark a file ready when the URL is issued. Create a pending record, then let a callback or worker move it to ready after verifying the object.
import { Elysia, t } from "elysia";
import { createUploadUrl } from "../storage";
export const uploadRoutes = new Elysia({ prefix: "/uploads" }).post("/prepare", async ({ body, set }) => {
const upload = await createUploadUrl(body.contentType);
// Persist upload.key and userId with status=pending.
set.status = 201;
return { ...upload, status: "pending" };
}, { body: t.Object({ contentType: t.Union([t.Literal("image/jpeg"), t.Literal("image/png"), t.Literal("image/webp")]) }) });The worker should verify object metadata again before calling Sharp.
Security and cost controls
Upload endpoints need limits at every stage.
Limit size
Limit API payloads and verify the actual object size after PUT.
Generate keys server-side
Use UUIDs and a user/project namespace instead of accepting arbitrary keys.
Scan or moderate
For public content, add antivirus or moderation before status becomes ready.
Add lifecycle rules
Automatically delete abandoned uploads and temporary originals.
Move heavy work to a worker
Keep the request API responsive by separating Sharp processing.
Test the image pipeline
Test decoded output, not just an HTTP status. This catches quality regressions and broken native dependencies after a Docker update.
import { expect, test } from "bun:test";
import sharp from "sharp";
import { makeVariants } from "./images";
test("creates a bounded WebP thumbnail", async () => {
const input = await sharp({ create: { width: 1200, height: 800, channels: 3, background: "#f97316" } }).png().toBuffer();
const { thumbnail } = await makeVariants(input);
const meta = await sharp(thumbnail).metadata();
expect(meta.format).toBe("webp"); expect(meta.width).toBe(480); expect(meta.height).toBe(480);
});Run bun test --coverage; use MinIO or a dedicated test bucket for storage integration tests.
FAQ
Yes, but Sharp has native dependencies. Install them for the target OS and architecture and run a production Docker smoke test.
For large files, no. Issue a presigned URL and upload directly to S3-compatible storage; Bun should manage permissions, metadata, and processing state.
Conclusion
Describe the task — first 15 minutes of consultation are free.
Related Articles

AI Assistant Development Cost in 2026: RAG Chatbots, CRM Integrations, Guardrails, and Support
A practical buyer guide to AI assistant development cost in 2026: prototypes, RAG chatbots, knowledge-base assistants, CRM and website integrations, guardrails, evaluations, monitoring, and support.

AI for landing page development: where it speeds up launches and where it hurts conversion
A practical research piece on using AI for landing page development: v0, Webflow AI, Builder.io, Framer-like builders, UX generation, copy, SEO, personalization, A/B testing, template risk, accessibility, security and technical debt.

AI SEO / GEO in 2026: Your Next Customers Aren’t Humans — They’re Agents
Search is shifting from clicks to answers. Bots and AI agents crawl, cite, recommend, and increasingly buy. Learn what AI SEO / GEO means, why classic SEO is no longer enough, and how PAS7 Studio helps brands win visibility in the agentic web.

The most powerful Apple chip yet? M5 Pro and M5 Max are breaking records
A data-backed March 2026 analysis of Apple M5 Pro and M5 Max. We break down why these chips can credibly be called Apple's most powerful pro laptop silicon, how they compare with M4 Pro, M4 Max, M1 Pro, M1 Max, and how they stack up against Intel and AMD laptop rivals.
Professional development for your business
We create modern web solutions and bots for businesses. Learn how we can help you achieve your goals.