BuildWithMatija
Back to Builds
ToolActiveOpen source

DropImg

Self-host your own image CDN in minutes. No cloud account required — just a VPS, Docker, and a one-click install script.

  • React 19
  • Vite
  • Hono
  • Node.js
  • TypeScript
  • Tailwind CSS 4
  • SQLite
  • Drizzle ORM
  • Garage S3
  • Docker
  • Better Auth
  • Cloudflare
GitHub
Problem
Teams and developers using Payload CMS, internal dashboards, or custom apps repeatedly reach for third-party image hosts (Imgur, Cloudinary, S3 directly) to get shareable URLs, losing control over storage, cost, and access rules. Public image hosts have rate limits, inject ads, or disappear. Cloudinary gets expensive at scale. Raw S3 requires a signed URL pipeline for every consumer.
Thesis
A single Docker Compose stack with a React frontend, Hono backend, SQLite metadata store, and self-hosted Garage S3 can cover the full image hosting loop — upload, transform, serve, share — on hardware you already own, with zero vendor dependency and a one-command install path.
Validation
Live and self-hosted at img.buildwithmatija.com. MIT-licensed and publicly available on GitHub. Actively used as the image backend for this site. Payload CMS S3 integration documented and tested — the same environment variables wire both systems together.
Proof points
  • Live at https://img.buildwithmatija.com
  • MIT license — open source, forkable, no strings
  • One-command install: git clone + ./install.sh on any Ubuntu VPS
  • Tested with Payload CMS via @payloadcms/storage-s3 against shared Garage bucket
  • Built-in background removal via Photoroom API proxy
Audience
  • Developers self-hosting Payload CMS or similar stacks who want a matching image host
  • Teams replacing Cloudinary or Imgur with a VPS-hosted alternative they fully control
  • Anyone who needs paste-to-URL image sharing without a cloud subscription

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 local or s3 storage 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.sh with --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:

typescript
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

code
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:

  1. AI image naming — LLM-generated SEO-friendly filenames and alt text, stored alongside the original, with an OpenAI-compatible provider abstraction.
  2. Rate limiting — IP and endpoint-based limits before AI processing makes unbounded usage expensive.
  3. BullMQ background queue — move AI processing and batch jobs off the request-response path into Redis-backed workers.
  4. 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.

Related services

  • Internal tools
  • Next.js developer

Working through something similar?

If your company has a workflow, content system, or internal process that needs to become real software, this is the kind of work I can help with.

Get in touch

Related builds

You might also find these useful

ToolActiveOpen source

StreamSpeaker

A system that turns any Android TV into a wireless speaker for a Mac or Android phone, using a custom Core Audio HAL driver and a binary UDP audio protocol over the local network.

  • Swift 6
  • SwiftUI
  • Core Audio (HAL Audio Server Plugin)
  • C (lock-free POSIX shared memory)
  • Kotlin
  • Ktor
  • Android AudioTrack
  • UDP / TCP
  • mDNS / Android NSD
  • SwiftPM
View buildGitHub
Build with Matija logo

Build with Matija

Modern websites, content systems, and AI workflows built for long-term growth.

Services

  • Headless CMS Websites
  • Next.js & Headless CMS Advisory
  • AI Systems & Automation
  • Website & Content Audit

Resources

  • Case Studies
  • How I Work
  • Blog
  • CMS Hub
  • E-commerce Hub
  • Dashboard

Headless CMS

  • Payload CMS Developer
  • CMS Migration
  • Multi-Tenant CMS
  • Payload vs Sanity
  • Payload vs WordPress
  • Payload vs Contentful

Get in Touch

Ready to modernize your stack? Let's talk about what you're building.

Book a discovery callContact me →
© 2026Build with Matija•All rights reserved•Privacy Policy•Terms of Service
BuildWithMatija
Get In Touch