Zero-compression storage. Bit-for-bit preservation. AI that tags your media before you even ask.
Documentation Β· Report Bug Β· Request Feature
Note
EraVault is under active development. APIs, environment variable names, and folder structures documented here reflect the main branch as of the latest release. Always check the CHANGELOG before upgrading a production deployment.
Click to expand full documentation index
- Executive Summary
- Features
- Why EraVault
- Problems Solved
- System Architecture
- Architecture Overview
- Complete Technology Stack
- Engineering Pillars
- AI Pipeline
- Database Architecture
- Folder Structure
- Installation
- Local Development
- Environment Variables
- Docker
- Cloudflare Deployment
- Performance Optimizations
- Security
- API Overview
- UI Overview
- Image Upload Flow
- AI Metadata Flow
- Search Architecture
- Deployment Pipeline
- CI/CD Recommendations
- Monitoring
- Logging
- Error Handling
- Backup Strategy
- Scaling Strategy
- Production Checklist
- Troubleshooting
- FAQ
- Roadmap
- Contributing Guide
- License
- Credits
EraVault is a high-performance enterprise media management platform purpose-built for organizations where the fidelity of a visual asset is not negotiable β architectural studios, RAW photography houses, design agencies, digital archives, and brand asset libraries. Traditional cloud media pipelines (S3 + CDN + image proxy, or consumer tools like Google Photos) apply silent lossy compression, strip embedded EXIF/IPTC metadata, and re-encode color profiles to save bandwidth. For most consumer use cases this is invisible. For professional and enterprise use cases, it is a slow, cumulative act of data corruption.
EraVault solves this by treating zero-compression as a first-class architectural constraint, not an afterthought. Every byte a user uploads is the byte that gets served back, forever β a property we call mathematical immutability. On top of that immutable storage layer, EraVault layers an autonomous AI tagging engine powered by Google Gemini Vision, which inspects every uploaded asset and generates structured metadata β tags, dominant colors, descriptive captions β without any human intervention.
The platform is built on a modern, edge-first stack: Next.js 15 (App Router, React 19 Server Components), TypeScript end-to-end, Prisma ORM against Neon Serverless PostgreSQL, Clerk for zero-trust authentication, ImgBB as an external immutable object store, and dual deployment targets of Docker (self-hosted, Kubernetes-ready) and Cloudflare Pages/Workers (global edge distribution).
This README is the canonical technical reference for engineers, DevOps teams, and contributors working with EraVault β covering architecture, deployment, security, and operations in the depth expected of a production system serving enterprise customers.
| Category | Capability | Description |
|---|---|---|
| πΌοΈ Storage | Zero-Compression Upload | Original file bytes are preserved exactly; no server-side re-encoding |
| πΌοΈ Storage | Bit-for-Bit Preservation | Cryptographic checksum validation on ingest and retrieval |
| π€ AI | Automatic Tagging | Gemini Vision generates contextual tags per image on upload |
| π€ AI | Color Extraction | Dominant palette (hex codes) extracted and indexed for search |
| π€ AI | Metadata Generation | Auto-generated captions, object detection labels, scene context |
| π Auth | Enterprise Authentication | Clerk-based session management with edge middleware enforcement |
| π Dashboard | Secure Dashboard | Per-user asset library with real-time upload status |
| ποΈ Organization | Collections | Group assets into named, shareable collections |
| π Search | Full-Text + Tag Search | Query by AI tags, filename, color, or collection |
| π± UI | Responsive Design | Mobile-first layout using TailwindCSS + Framer Motion |
| π² PWA | Installable App | Offline shell, manifest, and service worker support |
| π³ Deployment | Docker-Ready | Multi-stage, non-root, standalone production image |
| βοΈ Deployment | Cloudflare-Native | Edge Worker deployment via @cloudflare/next-on-pages |
| β‘ Architecture | Serverless Database | Neon PostgreSQL with connection pooling for cold-start resilience |
Most media platforms are optimized for storage cost, not data fidelity. That trade-off is invisible to casual users and catastrophic for professionals whose business depends on the exact pixel, the exact color profile, the exact metadata embedded in a file at the moment of capture.
EraVault inverts the priority stack:
- Fidelity first. The file you uploaded is the file you get back β always.
- Intelligence without labor. AI tagging removes the manual cataloging tax that kills large asset libraries.
- Composable infrastructure. Every layer (auth, storage, database, AI, compute) is a swappable, independently scalable service β no monolithic vendor lock-in.
- Deploy anywhere. The same codebase runs as a self-hosted Docker container or a globally distributed Cloudflare Worker.
| Problem | Traditional Approach | EraVault Approach |
|---|---|---|
| Lossy re-compression on upload | CDNs/image services auto-optimize (WebP/AVIF re-encode) | Direct, unmodified byte storage via external object node |
| Stripped EXIF/IPTC metadata | Most CDNs discard metadata to reduce payload | Metadata extracted and preserved separately in structured DB |
| Manual asset tagging | Human cataloging, inconsistent taxonomies | Autonomous Gemini Vision tagging pipeline |
| Vendor lock-in on storage | Proprietary CDN APIs | Storage abstraction layer, swappable object provider |
| Slow cold starts on serverless DB | Traditional pooled Postgres timing out on scale-to-zero | Neon serverless driver with edge-compatible pooling |
| Fragmented dev/prod parity | "Works on my machine" | Identical standalone Docker image for dev, staging, prod |
flowchart TD
A[π€ Client Browser / PWA] -->|HTTPS| B[Next.js 15 App Router]
B --> C{Clerk Edge Middleware}
C -->|Unauthenticated| D[Sign-In / Sign-Up]
C -->|Authenticated| E[Dashboard Route Handlers]
E --> F[Prisma ORM Client]
F --> G[(Neon Serverless PostgreSQL)]
E -->|Direct Upload| H[ImgBB Object Storage]
H -->|Immutable URL| F
E -->|Async Trigger| I[Google Gemini Vision AI]
I -->|Tags / Colors / Caption| F
B -->|Static + Edge Assets| J[Cloudflare Workers / Pages]
G -->|Search Index| E
+-------------------+ +-----------------------+ +-------------------+
| | | | | |
| Client / User +------>+ Next.js 15 App +------>+ Clerk Auth |
| (Uploads Media) | | (Edge & Standalone) | | (Zero-Trust JWT) |
| | | | | |
+---------+---------+ +-----------+-----------+ +-------------------+
| |
| v
| +-----------------------+ +-------------------+
| | | | |
+---------------->+ ImgBB External | | Neon Serverless |
Direct Upload | Storage Node | | PostgreSQL |
| | | |
+-----------+-----------+ +---------+---------+
| ^
v |
+-----------------------+ |
| | |
| Gemini Edge AI +-----------------+
| (Computer Vision) | Auto-Tagging Metadata
| |
+-----------------------+
| Service | Role | Notes |
|---|---|---|
| Next.js App Router | Request orchestration, SSR/RSC rendering, API route handlers | Runs standalone (Docker) or as edge Workers (Cloudflare) |
| Clerk | Identity provider and session middleware | Validates JWTs at the edge before requests reach business logic |
| Prisma ORM | Type-safe database access layer | Generates client from schema.prisma, migrations via prisma migrate |
| Neon PostgreSQL | Primary relational data store | Serverless, autoscaling, branchable databases for preview environments |
| ImgBB | External immutable object storage | Receives raw bytes directly from the client where possible, bypassing app server memory |
| Google Gemini Vision | Computer vision & metadata generation | Invoked asynchronously post-upload via a queued trigger |
| Cloudflare Pages/Workers | Global edge compute and static delivery | Same codebase compiled via @cloudflare/next-on-pages |
Important
The application server never buffers full-resolution media in memory during upload. Large payloads are routed directly to ImgBB using signed, short-lived upload targets β keeping the Next.js compute layer thin and stateless.
| Layer | Technology | Purpose | Advantages |
|---|---|---|---|
| Framework | Next.js 15 | App Router, RSC, route handlers, hybrid rendering | Fine-grained caching, edge/node runtime flexibility |
| UI Library | React 19 | Component model, concurrent rendering | Server Components reduce client JS payload |
| Language | TypeScript | Static typing across frontend/backend | Compile-time safety, superior refactoring, self-documenting APIs |
| Styling | TailwindCSS | Utility-first CSS | Small production CSS footprint, design consistency |
| Motion | Framer Motion | Declarative animation | GPU-accelerated transitions, gesture support |
| Icons | Lucide Icons | SVG icon system | Tree-shakeable, consistent visual language |
| ORM | Prisma | Type-safe DB client & migrations | Auto-generated types, declarative schema, migration history |
| Database | Neon PostgreSQL | Serverless relational storage | Scale-to-zero, branching, connection pooling for edge |
| Auth | Clerk | Identity & session management | Prebuilt UI, edge JWT verification, MFA support |
| Storage | ImgBB API | External immutable object storage | Zero re-compression, simple REST integration |
| AI | Google Gemini Vision | Image understanding & tagging | Multi-modal reasoning, high accuracy captioning |
| Container | Docker (Alpine) | Reproducible runtime environment | ~110MB standalone image, non-root execution |
| Orchestration | Docker Compose | Local multi-service orchestration | One-command environment parity |
| Edge Deploy | Cloudflare Pages/Workers | Global edge compute | Sub-50ms cold starts, 300+ PoPs |
| CI/CD | GitHub Actions | Build/test/deploy automation | Native GitHub integration, matrix builds |
1. Mathematical Immutability
Once an asset is written to the storage layer, its byte sequence is never rewritten, re-encoded, or transformed by EraVault infrastructure. The system computes and stores a content hash at ingest time, and every retrieval path can be verified against that hash. This gives archival-grade guarantees: what you uploaded in 2024 is byte-identical to what you download in 2034.
2. Zero Compression
Most storage services apply automatic transcoding (e.g., JPEG requantization, WebP conversion) to reduce bandwidth costs. EraVault explicitly disables any such pipeline. Uploads are streamed to ImgBB in their original binary form, and the platform never generates a "web-optimized" derivative unless a user explicitly requests a resized preview β which is stored as a separate, clearly labeled derivative, never as a replacement for the source.
3. Direct Storage
Large files bypass the Next.js application server entirely where possible. The client requests a signed upload target, then streams bytes directly to ImgBB. This removes the application server from the data path for the heaviest operation in the system, keeping compute costs low and horizontal scaling trivial β the app tier stays stateless and CPU-light.
4. Edge AI
Metadata generation is decoupled from the upload request/response cycle. After a successful upload, an asynchronous job invokes Gemini Vision against the immutable asset URL. This keeps the user-facing upload latency low (bound only by storage write time) while AI enrichment happens in the background and updates the UI via polling or optimistic revalidation.
5. Metadata Pipeline
Every asset accumulates two metadata layers: (a) technical metadata (dimensions, MIME type, checksum, upload timestamp) captured deterministically at ingest, and (b) semantic metadata (tags, caption, dominant colors) generated by AI. Both layers are normalized into relational tables for efficient filtering and full-text search.
6. Security
Authentication is enforced at the edge via Clerk middleware before any route handler executes. All secrets are environment-scoped and never bundled into client JavaScript. Docker containers run as a non-root user. Rate limiting and input validation guard every mutating API route.
7. Scalability
The application tier is stateless by design β any request can be served by any instance. The database uses Neon's serverless connection pooling to survive bursty, edge-originated traffic without exhausting connection limits. Storage scales independently via the external object provider.
8. Cloud Native
EraVault has no hard dependency on any single cloud vendor. It runs identically as a Docker container on any container orchestrator, or as edge Workers on Cloudflare. Database and storage are both externalized, serverless services reachable over HTTPS from anywhere.
9. Performance
React Server Components minimize client-side JavaScript. Route-level caching, streaming SSR, and edge-deployed compute combine to deliver sub-second Time to Interactive on modern connections, even for image-heavy dashboard views.
10. Developer Experience
End-to-end TypeScript, Prisma's generated types, a single docker compose up for full-stack local development, and a well-documented environment variable contract mean new contributors can be productive within minutes, not days.
sequenceDiagram
participant U as User
participant N as Next.js API
participant S as ImgBB Storage
participant G as Gemini Vision
participant D as Neon PostgreSQL
U->>N: Upload image
N->>S: Stream raw bytes
S-->>N: Immutable asset URL
N->>D: Persist asset record (pending)
N-->>U: Upload confirmed (instant)
N->>G: Analyze asset URL (async)
G-->>N: Tags + Colors + Caption
N->>D: Update asset record (enriched)
U->>N: Poll / revalidate
N->>D: Query enriched metadata
D-->>U: Tags visible in dashboard
Pipeline stages:
Upload β Gemini Vision β Color Detection β Tag Extraction β Metadata Normalization β Database Write β Search Index
Each stage is independently retryable. If Gemini enrichment fails (rate limit, transient network error), the asset remains fully accessible with base technical metadata, and a background retry job re-attempts enrichment with exponential backoff.
erDiagram
USER ||--o{ IMAGE : uploads
USER ||--o{ COLLECTION : owns
COLLECTION ||--o{ IMAGE : contains
IMAGE ||--o{ TAG : tagged_with
IMAGE ||--o{ COLOR : has_palette
USER {
string id PK
string clerkId
string email
datetime createdAt
}
IMAGE {
string id PK
string userId FK
string collectionId FK
string url
string checksum
string caption
int width
int height
datetime uploadedAt
}
COLLECTION {
string id PK
string userId FK
string name
string description
}
TAG {
string id PK
string imageId FK
string label
float confidence
}
COLOR {
string id PK
string imageId FK
string hex
float weight
}
Prisma's schema-first workflow means this ER diagram maps directly to prisma/schema.prisma. Migrations are generated and applied via prisma migrate dev locally and prisma migrate deploy in CI/CD, keeping schema history fully versioned and auditable.
eravault/
βββ src/
β βββ app/ # Next.js App Router
β β βββ (auth)/ # Sign-in / sign-up route group
β β βββ (dashboard)/ # Authenticated dashboard routes
β β β βββ collections/ # Collection management UI
β β β βββ search/ # Search interface
β β β βββ settings/ # Account settings
β β βββ api/ # Route handlers (REST-style API)
β β β βββ upload/ # Upload orchestration endpoint
β β β βββ ai/ # Gemini enrichment trigger
β β β βββ images/ # CRUD for image records
β β β βββ collections/ # CRUD for collections
β β βββ layout.tsx
β β βββ globals.css
β βββ components/ # Reusable React components
β β βββ ui/ # Design system primitives
β β βββ upload/ # Upload widgets
β β βββ gallery/ # Image grid, lightbox
β βββ lib/
β β βββ prisma.ts # Prisma client singleton
β β βββ clerk.ts # Auth helpers
β β βββ imgbb.ts # Storage client
β β βββ gemini.ts # AI client wrapper
β βββ middleware.ts # Clerk edge middleware
βββ prisma/
β βββ schema.prisma # Database schema
β βββ migrations/ # Versioned migration history
βββ public/ # Static assets, manifest.json
βββ docker/
β βββ Dockerfile
βββ docker-compose.yml
βββ next.config.js
βββ tailwind.config.ts
βββ tsconfig.json
βββ package.json
| Folder | Purpose |
|---|---|
src/app/(auth) |
Public authentication routes, rendered without dashboard chrome |
src/app/(dashboard) |
Protected routes; guarded by Clerk middleware |
src/app/api |
Server-side route handlers; the only layer that talks to Prisma/ImgBB/Gemini |
src/components/ui |
Headless, reusable design primitives (buttons, modals, inputs) |
src/lib |
Singleton clients and integration wrappers β the seam between app code and third-party services |
prisma/ |
Single source of truth for the data model |
docker/ |
Container build definitions |
| Tool | Minimum Version | Check Command |
|---|---|---|
| Node.js | 20.x LTS | node -v |
| npm | 10.x | npm -v |
| Docker | 24.x | docker -v |
| Docker Compose | 2.x | docker compose version |
| Git | 2.40+ | git --version |
# Verify prerequisites
node -v && npm -v && docker -v && git --version# 1. Clone the repository
git clone https://github.com/your-org/eravault.git
cd eravault
# 2. Install dependencies
npm install
# 3. Configure environment
cp .env.example .env.local
# then edit .env.local with your credentials (see Environment Variables section)
# 4. Apply database migrations
npx prisma migrate dev
# 5. Start the development server
npm run devVisit http://localhost:3000.
Create .env.local at the project root:
# ββ Database (Neon Serverless PostgreSQL) ββββββββββββββββββββββββββ
DATABASE_URL="postgres://user:password@endpoint.neon.tech/neondb?sslmode=require"
# ββ ImgBB Storage API βββββββββββββββββββββββββββββββββββββββββββββββ
NEXT_PUBLIC_IMGBB_API_KEY="your_imgbb_api_key"
# ββ Clerk Authentication ββββββββββββββββββββββββββββββββββββββββββββ
CLERK_SECRET_KEY="sk_live_xxxxxxxxxxxxxxxxxxxx"
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_live_xxxxxxxxxxxxxxxxxxxx"
NEXT_PUBLIC_CLERK_SIGN_IN_URL="/sign-in"
NEXT_PUBLIC_CLERK_SIGN_UP_URL="/sign-up"
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL="/dashboard"
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL="/dashboard"
# ββ Google Gemini Vision AI βββββββββββββββββββββββββββββββββββββββββ
GEMINI_API_KEY="AIzaSyxxxxxxxxxxxxxxxxxxxxxxxxxxx"| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
β | Neon PostgreSQL connection string; must include sslmode=require for production |
NEXT_PUBLIC_IMGBB_API_KEY |
β | Public key used for direct client-to-storage uploads |
CLERK_SECRET_KEY |
β | Server-only secret; never expose to the client bundle |
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY |
β | Public key initializing Clerk's client SDK |
NEXT_PUBLIC_CLERK_SIGN_IN_URL |
β | Route Clerk redirects to for sign-in |
NEXT_PUBLIC_CLERK_SIGN_UP_URL |
β | Route Clerk redirects to for sign-up |
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL |
β | Post-authentication redirect target |
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL |
β | Post-registration redirect target |
GEMINI_API_KEY |
β | Server-only key authorizing Gemini Vision API calls |
Warning
Never commit .env.local to version control. CLERK_SECRET_KEY and GEMINI_API_KEY must remain server-side secrets β any variable without the NEXT_PUBLIC_ prefix is excluded from the client bundle by Next.js convention, but this is a convention, not a sandbox. Audit your route handlers to ensure secrets never leak into API responses.
# ββ Stage 1: Dependencies βββββββββββββββββββββββββββββββββββββββββββ
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
# ββ Stage 2: Builder βββββββββββββββββββββββββββββββββββββββββββββββββ
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ARG NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
ARG NEXT_PUBLIC_IMGBB_API_KEY
ENV NEXT_TELEMETRY_DISABLED=1
RUN npx prisma generate
RUN npm run build
# ββ Stage 3: Runner ββββββββββββββββββββββββββββββββββββββββββββββββββ
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
CMD ["node", "server.js"]version: "3.9"
services:
eravault:
build:
context: .
dockerfile: docker/Dockerfile
args:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY}
NEXT_PUBLIC_IMGBB_API_KEY: ${NEXT_PUBLIC_IMGBB_API_KEY}
ports:
- "3000:3000"
env_file:
- .env.local
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/api/health"]
interval: 30s
timeout: 5s
retries: 3# Build and start
docker compose up --build -d
# View logs
docker compose logs -f eravault
# Stop
docker compose down| Design Decision | Rationale |
|---|---|
| Multi-stage build | Final image excludes build tools and dev dependencies (~110MB vs ~1.2GB) |
output: 'standalone' |
Next.js bundles only the required server files and traced node_modules |
Non-root user (uid 1001) |
Prevents container breakout escalating to host root |
ARG for public keys |
Public Clerk/ImgBB keys are safe to bake at build time |
env_file for secrets |
Server-only secrets injected at runtime, never baked into image layers |
| Healthcheck | Enables orchestrators (Swarm, Kubernetes, ECS) to detect and restart unhealthy containers |
# Install the Cloudflare adapter
npm install -D @cloudflare/next-on-pages
# Build for Cloudflare Pages
npx @cloudflare/next-on-pages
# Deploy via Wrangler
npx wrangler pages deploy .vercel/output/static --project-name=eravault| Concept | Explanation |
|---|---|
| Workers | Compiled route handlers execute as V8 isolates at 300+ global edge locations |
| Pages | Static assets and prerendered routes served from Cloudflare's CDN |
| Edge Runtime | Route handlers declaring export const runtime = 'edge' run on Workers, not Node |
| Build | @cloudflare/next-on-pages transforms the standard Next.js build into Worker-compatible output |
| Caching | Cache-Control headers on static routes are honored by Cloudflare's edge cache automatically |
| Performance | Cold starts measured in single-digit milliseconds due to V8 isolate architecture (no container boot) |
Tip
Neon's serverless driver (@neondatabase/serverless) is required for database access from Cloudflare Workers, since the standard pg driver relies on TCP sockets unavailable in the Workers runtime.
| Technique | Layer | Impact |
|---|---|---|
| React Server Components | Rendering | Reduces client JS bundle size by rendering non-interactive UI on the server |
| Route-level caching | Next.js | revalidate tags minimize redundant database queries |
| Direct-to-storage uploads | Storage | Removes large payloads from application server memory |
| Edge middleware auth | Auth | Rejects unauthenticated requests before hitting compute-heavy handlers |
| Connection pooling | Database | Neon's pooled driver prevents connection exhaustion under edge concurrency |
| Image lazy loading | UI | Native loading="lazy" + IntersectionObserver-based gallery virtualization |
| Standalone Docker output | Deployment | Smaller image size reduces cold start and deployment time |
| Async AI enrichment | AI Pipeline | Decouples upload latency from Gemini API response time |
| Control | Implementation |
|---|---|
| Authentication | Clerk-issued JWTs validated in edge middleware on every request to protected routes |
| Session Management | Short-lived tokens with automatic silent refresh |
| Secrets | Server-only environment variables; never exposed via NEXT_PUBLIC_ prefix |
| Docker Hardening | Non-root user, minimal Alpine base, no dev dependencies in final image |
| Rate Limiting | Per-user/IP request throttling on /api/upload and /api/ai routes |
| Input Validation | Schema validation (e.g., Zod) on every mutating API route before database writes |
| Transport Security | HTTPS enforced end-to-end; sslmode=require on all database connections |
| CORS Policy | API routes restrict origins to the deployed application domain |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
POST |
/api/upload |
Initiates direct-to-storage upload and creates a pending image record | β |
POST |
/api/ai/enrich |
Triggers Gemini Vision analysis for a given asset ID | β |
GET |
/api/images |
Lists images for the authenticated user, paginated | β |
GET |
/api/images/:id |
Fetches full metadata for a single image | β |
DELETE |
/api/images/:id |
Deletes an image record and its storage reference | β |
GET |
/api/collections |
Lists collections owned by the user | β |
POST |
/api/collections |
Creates a new collection | β |
GET |
/api/search |
Full-text and tag-based search across the user's library | β |
GET |
/api/health |
Liveness/readiness probe for orchestrators | β |
| Page | Route | Description |
|---|---|---|
| Landing | / |
Marketing overview and sign-up call-to-action |
| Sign In | /sign-in |
Clerk-hosted authentication form |
| Sign Up | /sign-up |
Account creation flow |
| Dashboard | /dashboard |
Primary asset grid with upload widget |
| Collection Detail | /dashboard/collections/[id] |
Filtered view of assets within a collection |
| Search | /dashboard/search |
Query interface with tag/color filters |
| Settings | /dashboard/settings |
Account and API key management |
sequenceDiagram
participant U as User Browser
participant A as Next.js API
participant S as ImgBB
participant D as Database
U->>A: POST /api/upload (metadata only)
A->>D: Create pending record
A-->>U: Signed upload target
U->>S: PUT raw file bytes (direct)
S-->>U: Public immutable URL
U->>A: PATCH /api/images/:id (confirm URL)
A->>D: Update record β status: complete
flowchart LR
A[Image Uploaded] --> B[Async Enrichment Job Queued]
B --> C[Gemini Vision Analysis]
C --> D[Extract Tags]
C --> E[Extract Dominant Colors]
C --> F[Generate Caption]
D --> G[(PostgreSQL)]
E --> G
F --> G
G --> H[Search Index Updated]
flowchart TD
A[Search Query] --> B{Query Type}
B -->|Text| C[Postgres Full-Text Search]
B -->|Tag Filter| D[Tag Join Query]
B -->|Color Filter| E[Hex Proximity Match]
C --> F[Ranked Results]
D --> F
E --> F
F --> G[Paginated Response]
GitHub (push/PR)
β
GitHub Actions (lint, typecheck, test, build)
β
Docker Image Build & Push βββββββββββΊ Container Registry
β β
Cloudflare Pages Build Self-Hosted / K8s Pull
β β
Production (Edge) Production (Container)
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- run: npx prisma generate
- run: npm run lint
- run: npm run typecheck
- run: npm run build| Stage | Recommended Tool | Purpose |
|---|---|---|
| Lint | ESLint | Enforce code style and catch bugs early |
| Type Check | tsc --noEmit |
Verify TypeScript correctness before build |
| Test | Vitest / Jest | Unit and integration coverage |
| Build | next build |
Production compilation and route analysis |
| Deploy (Edge) | Wrangler CLI | Automated Cloudflare Pages deployment |
| Deploy (Container) | Docker Buildx | Multi-arch image build and registry push |
- Application: Integrate Vercel Analytics or an OpenTelemetry-compatible APM (e.g., Sentry Performance, Datadog RUM).
- Database: Neon's built-in dashboard exposes query latency, connection count, and storage growth.
- Edge: Cloudflare Analytics provides request volume, cache hit ratio, and Worker CPU time per route.
- Uptime: Point an external monitor (e.g., Better Uptime, UptimeRobot) at
/api/health.
- Structured JSON logs from all API route handlers, tagged with
requestId,userId, androute. - In containerized deployments, logs are written to
stdout/stderrand collected by the orchestrator's log driver (e.g.,json-file, Fluent Bit, CloudWatch Logs). - On Cloudflare, use
wrangler tailfor real-time Worker log streaming, or forward logs to Logpush.
| Layer | Strategy |
|---|---|
| API Routes | Centralized error middleware returning normalized { error, code } JSON payloads |
| Database | Prisma error codes mapped to user-friendly messages (e.g., unique constraint β "already exists") |
| AI Pipeline | Failures marked as enrichment_failed; asset remains usable with base metadata |
| Client | React Error Boundaries around gallery and upload widgets prevent full-page crashes |
- Database: Neon supports point-in-time recovery and automated daily snapshots; enable branch-based backups for pre-migration safety nets.
- Storage: Since ImgBB is the source of truth for binary assets, periodically export the
Image.url+Image.checksumtable to a secondary cold-storage bucket for disaster recovery. - Configuration: Store
.envtemplates and infrastructure-as-code (Terraform/Wrangler config) in a private, version-controlled repository.
| Dimension | Strategy |
|---|---|
| Compute | Stateless app tier scales horizontally behind a load balancer or via Cloudflare's automatic edge scaling |
| Database | Neon autoscales compute; use read replicas for search-heavy workloads at high tenant counts |
| Storage | Externalized to ImgBB; scales independently of application infrastructure |
| AI | Queue enrichment jobs (e.g., via a lightweight job table or external queue) to smooth Gemini API rate limits under burst load |
- All environment variables set via secrets manager, not
.envfiles -
DATABASE_URLusessslmode=require - Clerk production instance configured with correct redirect URLs
- Docker image built with
NODE_ENV=production - Non-root user verified (
docker exec <container> whoami) - Health check endpoint returning
200 - Rate limiting enabled on upload and AI endpoints
- Database migrations applied via
prisma migrate deploy - Monitoring and alerting wired to
/api/health - Backup schedule confirmed on Neon dashboard
Prisma client fails to generate in Docker build
Ensure npx prisma generate runs before npm run build in the builder stage, and that prisma/schema.prisma is copied into the image prior to that step.
Clerk redirects to the wrong URL after sign-in
Verify NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL matches an allowed redirect URL configured in your Clerk dashboard instance settings.
Uploads succeed but AI tags never appear
Check that GEMINI_API_KEY is valid and that the enrichment route is not being blocked by rate limiting. Inspect logs for enrichment_failed status on the affected image record.
Cloudflare deployment fails with a Node API error
Route handlers using Node-only APIs (e.g., fs, native pg) must be excluded from edge runtime routes or refactored to use edge-compatible equivalents (e.g., @neondatabase/serverless).
1. Does EraVault compress my images?
Never. Original bytes are preserved exactly as uploaded.2. Can I self-host EraVault entirely?
Yes, via the provided Docker Compose setup with no dependency on Cloudflare.3. What happens if Gemini's API is down?
Uploads still succeed; AI enrichment is retried asynchronously with backoff.4. Is Neon PostgreSQL required, or can I use standard Postgres?
Standard Postgres works for Docker deployments; Neon's serverless driver is required for Cloudflare Workers.5. How is authentication handled?
Clerk issues and validates JWTs at the edge middleware layer.6. Can I swap ImgBB for S3 or R2?
Yes β the storage client in `src/lib/imgbb.ts` is designed as a swappable interface.7. Does EraVault support video files?
The current release targets images; video support is on the roadmap.8. What image formats are supported?
JPEG, PNG, WebP, GIF, and TIFF are supported for zero-compression storage.9. Is there a file size limit?
Limits are configurable and depend on your ImgBB plan tier.10. Can multiple users share a collection?
Collection sharing is planned; current release scopes collections to a single owner.11. How do I rotate the Gemini API key?
Update `GEMINI_API_KEY` in your secrets manager and redeploy; no code changes required.12. Does EraVault support SSO?
Yes, via Clerk's enterprise SSO connections (SAML/OIDC) on supported plans.13. Is the AI tagging accuracy configurable?
Confidence thresholds for stored tags are configurable in `src/lib/gemini.ts`.14. Can I run this on AWS instead of Cloudflare?
Yes β the Docker image runs on any container platform, including ECS and EKS.15. Does the PWA work offline?
The app shell and previously viewed assets are cached for offline browsing; uploads require connectivity.16. How are database migrations handled in production?
Via `prisma migrate deploy`, run as a pre-deploy CI step.17. What's the recommended Node.js version?
Node 20 LTS, matching the Docker base image.18. Can I disable AI tagging entirely?
Yes, by omitting `GEMINI_API_KEY`; uploads proceed without enrichment.19. How is rate limiting implemented?
Via middleware checking per-user request counts against a sliding window, backed by the database or an edge KV store.20. Is EraVault open source?
Yes, released under the MIT License.21. How do I report a security vulnerability?
Please email the maintainers directly rather than opening a public issue.22. Does EraVault support multi-tenancy?
Data is isolated per Clerk user ID at the database query layer today; organization-level tenancy is on the roadmap.| Status | Item |
|---|---|
| β | Zero-compression image upload pipeline |
| β | Gemini Vision AI tagging |
| β | Docker + Cloudflare dual deployment |
| π§ | Video asset support |
| π§ | Collection sharing & team workspaces |
| π | Organization-level tenancy (Clerk Organizations) |
| π | Pluggable storage backends (S3, R2, Backblaze B2) |
| π | Public API with scoped API keys |
| π | Advanced search with vector similarity |
We welcome contributions of all sizes.
# Fork, then clone your fork
git clone https://github.com/<your-username>/eravault.git
cd eravault
# Create a feature branch
git checkout -b feat/your-feature-name
# Install dependencies and run tests
npm install
npm run lint && npm run typecheck && npm test
# Commit using Conventional Commits
git commit -m "feat: add collection sharing endpoint"
# Push and open a Pull Request
git push origin feat/your-feature-nameTip
Please open an issue to discuss significant architectural changes before submitting a large pull request β it saves everyone review time.
Released under the MIT License. See LICENSE for full terms.
Enterprise-Grade, Zero-Compression Media Vault & Edge AI Tagging Engine
Engineered with precision by Osman Ahmed Khan
"Your pixels, exactly as they were."
Made with π€ using Next.js, React, TypeScript, Prisma, Gemini AI, Docker, and Cloudflare.



