* fix(admin): make "Storage used" report storage used (#1164) The tile summed photos.size_bytes — the catalogued size of the ORIGINALS, which has no relationship to the disk PicPeak runs on. In reference mode those files are never copied and sit on the NAS; duplicate rows counted the same file twice (#1162); and it ignored everything PicPeak genuinely does write locally: thumbnails, previews, hero renditions, watermarks and the per-event download cache. The reporter's tile read ~80 GB against 21 GB of real usage. Worse than the label: the same number drove the storage soft-limit warning bar and, via /storage/info, the recommended soft limit — so a reference-mode install got a disk-capacity recommendation computed from bytes that are not on the disk. - new localStorageUsage service walks the storage root and reports the total plus a breakdown. Walking rather than summing DB columns is the point: thumbnail/preview/hero rows record a key and never a byte count, and orphans from a deleted event or an interrupted import are real bytes. Symlinks are not followed, so a link into the media mount cannot put the NAS back in the total. Cached for 5 minutes, since the dashboard polls. - the dashboard tile and /storage/info now report that, with the catalogued figure kept and labelled as such next to it. A failed measurement reads as "unavailable" rather than substituting a number that means something else. On the local rig: 64.37 MB used against 15.75 MB catalogued, of which 27.9 MB is watermarks and 6.8 MB is download cache — none of which the old figure could see. Not addressed here: `.download-cache/all.zip` still has no TTL or size cap. It is now at least visible in the breakdown, which is what makes the case for capping it. * fix(admin): exclude the media share from local storage usage (#1164) External review found the walk could reintroduce the exact over-count it replaces. EXTERNAL_MEDIA_ROOT's compose default is `<storage>/external-media`, where the NAS is bind-mounted. That is a plain directory, not a symlink, so the symlink guard did not cover it and the walk descended into the share — putting every referenced original back into a figure whose whole purpose is to leave them out, and comparing NAS bytes against statfs() of the local disk. On the reference-mode installs this issue is about, that is the failure mode reappearing inside its own fix. The configured root is now skipped when it lies inside the storage root, and the result reports which path was excluded. A directory that merely shares the name is still counted, because those really are local bytes. Also from the review: - concurrent cold-cache callers now share one walk. /dashboard/stats, /storage/info and the sidebar are routinely requested together, and each was starting its own stat-per-file traversal of the whole library. - storage_partial is surfaced in the StorageInfo type and the sidebar tile, not just the dashboard and analytics cards. An unreadable subtree makes the total a floor, and a floor silently compared against a soft limit reads as "safely under". * fix(admin): do not report a disk walk on an S3 backend (#1164) Second review round. S3 installs were regressed. With STORAGE_BACKEND=s3 the originals, renditions, archives and download caches are objects in the bucket and STORAGE_PATH holds only incidental local files — so the walk reported near-zero and the soft-limit recommendation was derived from it. Those installs now keep the catalogued figure, which is the approximation they had before this PR, and the response says which measurement it is (`storage_measurement: 'disk' | 'catalog'`) so the UI labels it instead of implying a disk measurement that never happened. The Settings → Status storage card ignored storage_partial, formatting a lower bound as exact and deriving the limit percentage from it — so an unreadable subtree could read as safely under the limit. It now carries the same `+` marker as the sidebar and dashboard. * fix(admin): stop rendering an absent measurement as zero usage (#1164) Third review round, two findings. The analytics storage bar coerced a null measurement to 0, drawing an empty bar labelled "0% of limit" and suppressing the over-limit state — reading as plenty of room at exactly the moment nothing is known. It now shows the catalogued figure on S3, where that IS the available answer, and says "no measurement available" rather than inventing a percentage when there is none. /storage/info walked the filesystem before checking the backend and then threw the result away on S3. The sidebar polls that endpoint, so a migrated install still holding a large local tree paid a full stat-per-file traversal on every cold cache for nothing. Gated before the walk, as the dashboard route already was. * fix(admin): tell "no disk to measure" apart from "the measurement failed" (#1164) External review of the stable twin. Both were reported as `storage_measurement: 'catalog'`, so a failed local walk made the dashboard claim the objects live in S3. They are different things — one is a fact about the install, the other is a fault — and there is now an `unavailable` state for the second. The analytics percentage could reach the billions. `safeSoftLimit` fell back to `storageUsed || 1`, and on S3 that is null → 1, while the figure beside it came from `catalogedBytes`. An editor or viewer holds `analytics.view` but not `settings.view`, so `/storage/info` 403s for them and `storageInfo` is undefined — which is exactly when that fallback fires. It now falls back to the measured figure, and suppresses the percentage entirely when there is no real limit rather than dividing usage by itself and always reading 100%. Also lands the AnalyticsPage half of the previous round, which the commit message claimed but the commit did not contain — only its backend counterpart was staged. The stable twin has carried it since it was written, so this is the parity gap in the unusual direction. --------- Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local>
PicPeak is a powerful, self-hosted open-source alternative to commercial photo-sharing platforms like PicDrop.com and Scrapbook.de. Built for photographers and event organizers, it makes it simple to share beautiful, time-limited photo galleries with clients while keeping full control over your data and branding.
Important
PicPeak has moved to its own GitHub organization. Docker images are now at
ghcr.io/picpeak/picpeak/{backend,frontend,aio,ml}(and on Docker Hub aspicpeak/{backend,frontend,aio,ml}) and active development is onmain. The oldghcr.io/the-luap/...path still responds but its tags are frozen at 2026-05-27 — if updates never arrive, check your image path first. Seedocs/migration-to-org.mdfor the one-linedocker-compose.ymledit.
Contents
- Live Demo
- Quick Start
- Why PicPeak?
- Features
- Documentation
- Comparison
- Tech Stack
- Contributing & Support
- License
🎮 Live Demo
Try PicPeak without installing anything — demo.picpeak.app · admin panel
| Password | |
|---|---|
demo@picpeak.app |
Demo2026! |
The demo resets periodically. Uploaded content may be removed without notice.
🚀 Quick Start
Get PicPeak running in under 5 minutes:
# Clone the repository
git clone https://github.com/PicPeak/picpeak.git
cd picpeak
# Copy the environment template — the defaults work out of the box.
# Machine secrets (JWT, DB, Redis) are auto-generated on first run, and the
# admin account is created in the browser. Edit .env only to customise
# (domain, SMTP, storage paths, …) — nothing is required.
cp .env.example .env
# Start with Docker Compose
docker compose up -d
# Access at http://localhost:3000
On first start, open http://localhost:3000/admin and follow the in-browser setup to create your admin account. Full details — the one-time setup token, Docker file permissions, and ARM64 notes — are in First-run setup.
Updating / release channels: set
PICPEAK_CHANNEL(stabledefault, orbeta) in.env, thendocker compose pull && docker compose up -d. See RELEASING.md for the promotion cadence.
Or: one container, no compose file
For a home server, a NAS, or a single small studio, the all-in-one image runs the whole app as one process with SQLite — no compose file, no separate database, no reverse proxy to wire up:
docker run -d --name picpeak -p 3000:3000 \
-v picpeak:/data \
ghcr.io/picpeak/picpeak/aio:main
No environment variables to set — the JWT secret is generated on first start and kept on the volume.
Then open http://localhost:3000/admin and read the setup token with docker exec picpeak cat /data/db/SETUP_TOKEN, or open db/SETUP_TOKEN on the volume with any file manager if the host has no shell.
:main is the active-development tag, and today it is the only one the all-in-one image has — Dockerfile.aio landed after the current stable release, so :stable and :latest first appear for this image once the aio build reaches the stable branch. Switch to :stable then, or pin a published version tag if you would rather not track main.
The compose stack above is still the right choice for anything busier — SQLite takes one writer at a time, and Postgres is what scales. You can move to it later without reinstalling: take a .picpeak backup and restore it into the full stack. See Single-container install for the volume layout, the external-Postgres variant, TLS, and the limits.
Docker images
| GHCR | Docker Hub | |
|---|---|---|
| Backend | ghcr.io/picpeak/picpeak/backend |
picpeak/backend |
| Frontend | ghcr.io/picpeak/picpeak/frontend |
picpeak/frontend |
| All-in-one | ghcr.io/picpeak/picpeak/aio |
picpeak/aio |
| ML sidecar (optional) | ghcr.io/picpeak/picpeak/ml |
picpeak/ml |
Both registries get the same digests and the same tags — stable/latest, a pinned x.y.z, and beta/main for the active development channel — for linux/amd64 and linux/arm64. Keep every image in one install on the same tag.
🌟 Why PicPeak?
Unlike expensive SaaS solutions, PicPeak gives you:
- 💰 No Monthly Fees — one-time setup, unlimited galleries
- 🔒 Complete Data Control — your photos stay on your server
- 🎨 White-Label Ready — full branding customization
- 📱 Mobile-First Design — beautiful on all devices
- 🌍 Multi-Language — built-in i18n (EN, DE)
✨ Features
For photographers — drag & drop upload, auto-expiring & password-protected galleries, automated emails, an analytics dashboard, custom themes, a public landing page, and a Live Slideshow projector view that auto-picks-up new uploads during live events.
For clients — clean mobile-optimized galleries, one-click bulk downloads, smart search, People in this gallery face grouping (opt-in per gallery, needs the optional ML sidecar), optional guest uploads, and download protection (watermarking + right-click prevention).
Technical — Docker-ready, automatic thumbnail generation, external media reference mode, smart archiving of expired galleries, S3-compatible storage backends, webhooks, and security-first defaults (JWT, rate limiting, CORS).
🧾 For studios — CRM & Accounting (Beta, off by default)
- 📝 Quotes → Contracts → Invoices — one deal lineage; cancel-and-reissue (Storno) keeps issued invoices immutable
- ⏱️ Hours Logging & Calendar — per-customer time tracking; admin calendar of events, logged hours, and pending quotes/contracts
- 🧾 Inbound Supplier Invoices & Expenses — capture received invoices (upload/camera, rasterised server-side), categorise, and re-bill costs to clients
- 📊 Tax Report & Accountant Export — period-scoped income/cost report with VAT breakdown; PDF/CSV plus a Treuhänder/Banana (Swiss/LI) journal export
- 🌍 VAT & Multi-currency — single VAT-code registry snapshotted onto each document
Warning
CRM & Accounting — examples only, verify locally. Feature-flagged off by default. Seeded contract blocks are written by the maintainer, not a lawyer; QR-bills/SEPA payloads and every tax, VAT and Treuhänder/Banana figure are computed from your input and defaults and are jurisdiction-specific guidance only. Have your lawyer review contracts, scan a test QR with your bank's app, and verify all numbers with your accountant / Treuhänder / tax authority before customer-facing use. Read the CRM disclaimers first.
📖 Documentation
Full documentation lives at docs.picpeak.app — deployment, admin settings, API, branding, and more.
| Topic | Link |
|---|---|
| 🚀 Deployment (Docker, env, reverse proxy, SSL) | docs.picpeak.app/deployment |
📦 Single-container install (one docker run, SQLite) |
docs.picpeak.app/deployment/single-container |
| ⚙️ Admin settings reference | docs.picpeak.app/guides/admin-settings |
| 🎯 Creating events | docs.picpeak.app/guides/creating-events |
| 📽️ Live Slideshow | docs.picpeak.app/features/live-slideshow |
| 🙂 People in galleries (face grouping) | docs.picpeak.app/features/face-recognition |
| 💾 Backup & Restore | docs.picpeak.app/guides/backup-restore |
| 🔌 API reference | docs.picpeak.app/api |
| 🪝 Webhooks | docs.picpeak.app/features/webhooks |
| 💾 Storage backends (local / S3) | docs.picpeak.app/features/storage-backends |
| 💻 System requirements & tuning | docs.picpeak.app/deployment/system-requirements |
| 🧾 CRM & Accounting | docs.picpeak.app/features/crm · disclaimers |
| 🗺️ Roadmap | GitHub Issues |
Project meta: Contributing · License · Security · Code of Conduct
📊 Comparison with Alternatives
| Feature | PicPeak | PicDrop | Scrapbook.de | Pixieset |
|---|---|---|---|---|
| Self-Hosted | ✅ | ❌ | ❌ | ❌ |
| Custom Branding | ✅ Full | Limited | Limited | ✅ (paid) |
| Monthly Cost | $0* | $29-199 | €19-99 | ~$60 |
| Storage Limit | Unlimited** | 50-500GB | 100-1000GB | 3GB–Unlimited*** |
| Client Uploads | ✅ | ✅ | ✅ | Limited |
| API Access | ✅ | Paid | ❌ | ❌ |
| Open Source | ✅ | ❌ | ❌ | ❌ |
| Customer Accounts | ✅ | ❌ | ❌ | ✅ |
| Quotes / Contracts / Invoices | 🧪 Beta | ❌ | ❌ | ✅ |
| Incoming Invoices & Accounting | 🧪 Beta | ❌ | ❌ | ❌ |
*You bring your own server and, optionally, a domain. **Limited only by your server storage. ***Pixieset's "unlimited" is photos only; video is capped by plan. 🧪 Beta = built but feature-flagged off by default.
🏗️ Tech Stack
- Backend: Node.js, Express, SQLite/PostgreSQL
- Frontend: React, Tailwind CSS, Framer Motion
- Storage: Local filesystem (default) or S3-compatible object store (AWS S3, MinIO, R2, B2, Wasabi, Spaces) — see Storage Backends
- Email: SMTP with customizable templates
- Analytics: Privacy-focused with Umami integration
- External media: point PicPeak at
EXTERNAL_MEDIA_ROOTto reference existing originals read-only, index quickly, and generate thumbnails on demand
📸 Screenshots
Click to see the admin dashboard, analytics, and event management
🎛️ Admin Dashboard
📊 Analytics & Insights
📁 Event Management
🤝 Contributing
We love contributions! PicPeak is built by photographers, for photographers — whether you're fixing bugs, adding features, or improving docs. See the Contributing Guide to get started.
Found a security issue? Please open a security issue. See SECURITY.md for the policy.
☕ Support the Project
PicPeak is free, open source, and self-hostable forever. If it saves you time or replaces a paid subscription, consider buying me a coffee — it directly funds new features, bug fixes, and keeping the demo + docs running. You can also ⭐ star the repo, share it, file good bug reports, or open a PR.
🙏 Acknowledgments
PicPeak is inspired by the best features of commercial platforms while remaining completely open source. It's developed with AI assistance, but human-tested end-to-end, security-audited, and human-reviewed for quality.
👥 Contributors
A huge thank you to the people whose code, reports, and feedback have shaped PicPeak:
@the-luap — creator and lead maintainer
- Gallery foundation (events, uploads, sharing, download protection, templates)
- Backup & restore, analytics, branding/theming
- The architecture every later feature builds on
- Native Apple Silicon multi-arch images
- CRM & accounting suite (quotes/contracts/invoices)
- Hours logging & Treuhänder/Banana tax export
- Gallery header/banner decoupling
@Rekoo-PS — bug reports & product feedback
- Login-loop fix, mobile-lightbox overhaul, bulk-delete workflow
- Also a BuyMeACoffee supporter
If you've contributed and aren't listed here, please open a PR — this list is meant to grow.
📄 License
PicPeak is released under the MIT License. Use it freely for personal or commercial projects.
Made with ❤️ by photographers, for photographers
Homepage ·
Live Demo ·
Documentation ·
Support

