Executive summary
DropImg is an open-source, self-hosted image hosting tool I built and use as the image backend for this site. It ships as a Docker Compose stack — React frontend, Hono API, SQLite metadata, and a bundled Garage S3 node — and installs on any Linux VPS with a single script. The result is a paste-to-URL image host you fully own, with multi-user support, per-user galleries, admin controls, and optional background removal through a Photoroom proxy.
The code is MIT-licensed and public on GitHub. The running instance at img.buildwithmatija.com serves as the live reference deployment.
The problem
Every project that needs shareable image URLs ends up in the same dead end. Third-party hosts — Imgur, Cloudinary, S3 with signed URLs — each introduce a different failure mode: rate limits, ads on the URL, per-GB billing that compounds at scale, or a 15-minute signed URL that breaks embeds. Raw S3 fixes the ownership problem but adds a pipeline every consumer has to understand.
For developers already running a VPS, the real cost is paying for something that a small Node app and a local S3 node could handle for free. The gap was a turnkey stack that handles upload, storage, serve, and share without requiring a cloud account.
The thesis
The bet was that the full image hosting loop — upload from browser, transform, store in S3, serve over proxied clean URLs, share — fits cleanly in a single Docker Compose file. Garage is a lightweight, self-hosted S3-compatible object store built for exactly this kind of single-node deployment. Hono handles API and proxied serving in one process. SQLite tracks metadata and ownership without a separate database server. Better Auth handles sessions. The result is a stack that deploys in the time it takes to run an install script.
One secondary bet: because Garage uses standard S3 protocol, the same bucket and credentials wire directly into Payload CMS via @payloadcms/storage-s3. No extra configuration, no second storage account — both systems share a bucket if you want them to.
What I built
Upload and sharing
- Drag-and-drop and clipboard paste upload with instant URL output.
- Proxied clean URLs (
/raw/:id) so the S3 bucket stays private while assets remain publicly accessible. - Support for
localors3storage drivers; defaults to Garage S3 but any S3-compatible endpoint works (AWS, R2, Backblaze, Minio). - Configurable max upload size and public vs. authenticated mode.
Image processing
- In-browser compression for JPGs, PNG-to-JPG conversion, and metadata stripping — handled before upload.
- Server-side background removal via Photoroom API proxy: the browser uploads the source image, the Hono backend proxies it to Photoroom, and the cutout is stored as the hosted asset. API key never leaves the server.
Authentication and access
- Better Auth session-based auth; optional — can be disabled for fully public/anonymous mode.
- First-signup bootstrapping: the first user to register becomes admin automatically, no manual DB update required.
- Data isolation: regular users see only their own uploads; admins have a dedicated dashboard showing all assets across all users.
Infrastructure
- Docker Compose orchestrating API, React frontend, and Garage S3 node.
install.sh: detects and installs Docker if missing, prompts for app name, port, public URL, admin token, and optional Photoroom integration, provisions the Garage bucket, writes.env, and launches containers — zero manual steps after cloning.uninstall.shwith--yes(full teardown) and--keep-data(stop services, preserve files) flags.- Automatic database migrations on API container startup via Drizzle.
Payload CMS integration
Because DropImg and Payload CMS use the same S3 environment variable names (S3_BUCKET, S3_ENDPOINT, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_REGION), connecting them is a direct pass-through:
import { s3Storage } from '@payloadcms/storage-s3'
export default buildConfig({
plugins: [
s3Storage({
collections: { media: true },
bucket: process.env.S3_BUCKET,
config: {
endpoint: process.env.S3_ENDPOINT,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
},
region: process.env.S3_REGION,
forcePathStyle: true,
},
}),
],
})
Both systems share a Garage bucket on the same VPS — images uploaded through DropImg are reachable from Payload and vice versa.
Architecture
Browser → React / Vite frontend
→ Hono API (Node.js)
→ SQLite (Drizzle) — metadata, ownership, sessions
→ Garage S3 — object storage
→ Photoroom API — background removal proxy (optional)
The API also serves the production React build as static files, so the entire app runs as a single Docker service from an external perspective. Garage runs as a sidecar in the same Compose stack.
Roadmap
The next meaningful additions, in rough priority order:
- AI image naming — LLM-generated SEO-friendly filenames and alt text, stored alongside the original, with an OpenAI-compatible provider abstraction.
- Rate limiting — IP and endpoint-based limits before AI processing makes unbounded usage expensive.
- BullMQ background queue — move AI processing and batch jobs off the request-response path into Redis-backed workers.
- Super admin controls — queue health, job retry, usage inspection, and provider configuration from an admin dashboard.
Full details are in ROADMAP.md in the repo.
Current status
Active and self-hosted. The running instance at img.buildwithmatija.com is the primary image CDN for this site. The codebase is MIT-licensed and public. Install script tested on Ubuntu/Debian VPSes. Roadmap features are planned but not yet started — the current scope is a working, installable image host, not an AI platform yet.