A self-hosted image hosting platform built for ShareX. Fast uploads, private image sharing, and a clean dashboard to manage everything.
Live at https://formality.life
- Upload images via API or drag-and-drop in the dashboard
- Supports PNG, JPG, GIF, WebP, and SVG (up to 10MB)
- Short URLs for easy sharing (
/i/abc123) - Private images with token-based access links
- Custom domain support ;)
API (Cloudflare Workers)
- Hono - Web framework
- Cloudflare R2 - Object storage
- Neon - Serverless Postgres
- Drizzle ORM - Database ORM
- better-auth - Authentication
- Resend - Email delivery
Web
- Next.js - React framework
- TanStack Query - Data fetching
- Tailwind CSS - Styling
- Motion - Animations
- Node.js 18+
- Bun
- Cloudflare account (for R2 and Workers)
- Neon account (or any Postgres)
- Resend account (for magic link emails)
API (api/.env)
DATABASE_URL=postgresql://...
R2_ACCOUNT_ID=...
R2_ACCESS_KEY_ID=...
R2_SECRET_ACCESS_KEY=...
R2_BUCKET_NAME=...
BASE_URL=https://formality.life
TOKEN_SECRET=your-token-secret
RESEND_API_KEY=re_...
BETTER_AUTH_SECRET=your-auth-secretWeb (web/.env.local)
NEXT_PUBLIC_API_URL=https://formality.life# Clone the repo
git clone https://github.com/nirlep5252/host.git
cd host
# API
cd api
bun install
bun run dev
# Web (in another terminal)
cd web
bun install
bun run devcd api
bun run db:check # Validate migration files
bun run db:generate # Generate a migration after schema changes
bun run db:migrate # Apply pending migrations to DATABASE_URLBackend deployment is handled by GitHub Actions in .github/workflows/api-deploy.yml.
- Pull requests that touch
api/**run API validation. - Pushes to
mainthat touchapi/**validate and deploy the Worker. - Manual runs deploy only when started from the
mainbranch. - Production deploys are serialized with an
api-productionconcurrency group. - The deploy job targets the GitHub
productionenvironment. Configure required reviewers there if production should require manual approval.
Add these GitHub Actions secrets before enabling the workflow:
CLOUDFLARE_ACCOUNT_ID=...
CLOUDFLARE_API_TOKEN_DEPLOY=...
DATABASE_URL=...CLOUDFLARE_API_TOKEN_DEPLOY is only for CI deployment. Keep the runtime Worker CLOUDFLARE_API_TOKEN binding separate because the app uses it for custom-domain Cloudflare API calls.
The deploy token should be scoped to the Cloudflare account that owns the Worker. Start with Cloudflare's Workers edit permissions, plus R2 permissions if Wrangler requires them for the R2 binding. Do not use a global API key.
DATABASE_URL is used by the deploy workflow to run production migrations before the Worker deploys. Store it as a GitHub production environment secret if that environment is protected.
The runtime Worker variables/secrets still need to exist in Cloudflare:
DATABASE_URL=...
TOKEN_SECRET=...
RESEND_API_KEY=...
BETTER_AUTH_SECRET=...
CLOUDFLARE_ZONE_ID=...
CLOUDFLARE_API_TOKEN=...
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
FRONTEND_URL=...
DNS_TARGET=... # optionalAdmin access is account-based. The 0007_add-user-roles migration adds users.role and promotes hello@nirlep.dev to admin.
The deploy script uses wrangler deploy --keep-vars because several runtime bindings are currently managed outside wrangler.toml. After all non-secret runtime variables are moved into wrangler.toml, --keep-vars can be removed and the config file can become the full source of truth.
API (Cloudflare Workers)
cd api
bun run deployWeb (Vercel)
cd web
vercel- Log in to the dashboard
- Download ShareX config from sidebar