Files
picpeak/README.md
T
Paul Nothaft 8994901e4a chore(release): promote beta → stable (v3.83.0 line)
Merge main (v3.83.0-beta.0) into stable to cut the next stable release.
Conflicts resolved toward main (the promoted code); stable release-control
files (manifest, CHANGELOG) restored separately.
2026-07-08 19:59:55 +02:00

30 KiB
Raw Blame History

PicPeak

Important

PicPeak has moved to its own GitHub organization.

  • Docker images are now published at ghcr.io/picpeak/picpeak/{backend,frontend}. The old path (ghcr.io/the-luap/picpeak/...) is no longer served — update your docker-compose.yml.
  • Branches: active development is now on main (was beta); the curated stable channel is now stable (was main). Existing PRs and clones auto-redirect via GitHub.

See docs/migration-to-org.md for the one-line docker-compose.yml edit and full details.

PicPeak Logo

Self-hosted photo sharing for event photographers.

License: MIT Docker Node.js React

Live Demo · Deployment Guide · Homepage


PicPeak lets you create password-protected, time-limited photo galleries for your clients — hosted on your own server. No subscriptions, no storage limits, no third-party access to your photos.

PicPeak Gallery Preview

Demo

Try it out at demo.picpeak.app.

Admin panel: demo.picpeak.app/admin — login with demo@picpeak.app / Demo2026!

The demo resets periodically.

Features

Gallery Management — Create galleries, upload photos via drag & drop, set passwords and expiration dates. Galleries auto-archive when they expire. Events start as drafts so you can upload and prepare before notifying the client.

Client Experience — Responsive galleries that look great on any device. Guests can browse, download individual photos or everything at once. Optional guest uploads and feedback (likes, comments, ratings).

Themes & Branding — 11 built-in theme presets, custom CSS templates, configurable colors/fonts/layouts. White-label your admin panel and login page with your own logo and company name.

For Photographers

  • 📁 Drag & Drop Upload - Simply drop photos into folders
  • 🔗 External Media (Reference Mode) - Browse and import from a readonly external folder library without copying originals
  • Auto-Expiring Galleries - Set expiration dates (default: 30 days)
  • 🔐 Password Protection - Secure client galleries
  • 📧 Automated Emails - Creation confirmations and expiration warnings
  • 📊 Analytics Dashboard - Track views, downloads, and engagement
  • 📽️ Live Slideshow - A separate fullscreen "Diashow" link per event for projectors at live events — auto-picks-up new uploads while it runs, with transitions, a logo watermark, and image-fit/colour options (guide)
  • 🎨 Custom Themes - Match your brand perfectly
  • 🌐 Public Landing Page - Publish a curated marketing page when guests visit your root URL

Photo Protection — Watermarking, right-click prevention, canvas rendering, DevTools detection. Configurable per gallery.

External Media — Reference photos from a mounted folder instead of uploading. PicPeak reads originals in place and generates thumbnails on demand.

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, scopable to income-only or cost-only
  • 🌍 VAT & Multi-currency - Single VAT-code registry snapshotted onto each document; data-driven per-country rates
  • ⚠️ Verify locally - Feature-flagged off by default. Seeded contracts, QR/IBAN and tax defaults are examples only — review your own legal and tax regulations first (see disclaimers below)

🚀 Quick Start

Analytics — Built-in view/download tracking plus optional Umami integration for privacy-focused analytics.

Video Support — Upload and stream MP4, WebM, MOV alongside photos. FFmpeg bundled via npm.

Multiple Admins — Role-based access control with super admin, admin, and editor roles.

Quick Start

# 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 (see below). Edit .env only to
# customise (domain, SMTP, storage paths, …) — nothing is required.
cp .env.example .env

# Start with Docker Compose
docker compose up -d

First run — create your admin account

On first start with no ADMIN_PASSWORD set, PicPeak has no admin account yet and greets you with an in-browser setup screen — no credentials in .env:

  1. Open http://localhost:3000/admin — you'll be redirected to /setup.
  2. Grab the one-time setup token from the backend logs (it's also saved to data/SETUP_TOKEN):
    docker compose logs backend | grep -i "setup token"
    
  3. Paste the token, set your admin email + password, and you're in. The token is single-use, and the setup screen closes permanently once an admin exists.

Prefer the old behaviour? Set ADMIN_PASSWORD in .env and PicPeak auto-creates the admin on first boot instead (credentials written to data/ADMIN_CREDENTIALS.txt).

Note on Docker file permissions

  • The backend container starts as root, chowns bind-mounted host directories (./storage, ./data, ./logs) to UID 1001 (nodejs), then drops privileges via su-exec before running the app. No host-side setup needed for fresh installs.
  • If you pin user: in a compose override (e.g. to map a specific host UID), the self-chown is skipped and you must pre-chown the host directories to that UID — see docs.picpeak.app/deployment/docker#permissions.

Permissions: Set PUID and PGID in .env to match your host user (id -u / id -g) so Docker volumes are writable.

See the Deployment Guide for reverse proxy setup, SSL, external media, and production configuration.

PicPeak offers two release channels for different needs. Stable promotions are cut from a known-good beta point every 46 weeks — see RELEASING.md for the maintainer's promotion criteria and cadence policy.

Admin Dashboard Admin Dashboard
Event Management Event Management
Analytics Analytics

Comparison

PicPeak PicDrop Scrapbook.de
Self-hosted Yes No No
Monthly cost $0 $29-199 19-99 EUR
Storage Unlimited 50-500 GB 100-1000 GB
Custom branding Full Limited Limited
Open source Yes No No
API Yes Paid No

Tech Stack

  • Backend: Node.js, Express, PostgreSQL (or SQLite)
  • Frontend: React, TypeScript, Tailwind CSS
  • Infrastructure: Docker, Nginx, Redis
  • Processing: Sharp (images), FFmpeg (video)

Release Channels

Stable (stable / latest) — Production-ready. Use this for real deployments.

Beta (beta) — Early access to new features. May have rough edges.

# Set in .env
PICPEAK_CHANNEL=stable  # or beta

# Update
docker compose -f docker-compose.production.yml pull
docker compose -f docker-compose.production.yml up -d

The admin dashboard notifies you when updates are available.

Contributing

We welcome contributions — bug fixes, features, translations, documentation. See CONTRIBUTING.md for setup instructions.

Documentation

Thanks to the people whose code, reports, and feedback have shaped PicPeak:

  • @Luca-Timo — native Apple Silicon multi-arch images, external-URL toggle for legal CMS pages, lazy-loaded folder tree picker, admin-email picker, self-hosted webfont system, gallery header/banner decoupling, and several typed-API refactors.
  • @Rekoo-PS — sharp-eyed bug reporter and product feedback. Filed the issues that drove the login-loop fix, gallery-loading skeleton work, mobile-lightbox overhaul, admin-events search-counter fix, photo-count column, and bulk-delete workflow. Also a BuyMeACoffee supporter.

If you've contributed and aren't listed here, please open a PR.

License

🎯 Use Cases

Perfect for:

  • 💒 Wedding Photographers - Share ceremony photos securely
  • 🎂 Event Photography - Birthday parties, corporate events
  • 📸 Portrait Studios - Client galleries with download limits
  • 🏢 Corporate Events - Internal photo sharing with branding
  • 🎓 School Photography - Secure parent access with expiration
  • 📽️ Live Events - Put a Live Slideshow on the venue projector that updates as you shoot

🏗️ 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

💾 Storage Backends

PicPeak supports two storage backends for photos, thumbnails, hero images, watermarks, and archive zips. Both are configured via environment variables; no code change is required to switch.

Capability STORAGE_BACKEND=local (default) STORAGE_BACKEND=s3
Photo / thumbnail / hero storage Local filesystem under STORAGE_PATH Bucket on any S3-compatible service
Admin UI upload
Filesystem auto-import (chokidar watcher) — disabled (use the upload API)
Watermarks, fingerprinting, fragmentation (materialized to a tmp file just-in-time)
Bulk download zips (cached + on-the-fly)
Backups
External media reference mode (EXTERNAL_MEDIA_ROOT) (always local) (still local — not migrated)

Switching to an S3-compatible backend

  1. Provision a bucket and credentials. The minimum IAM policy is documented in .env.example.
  2. Set STORAGE_BACKEND=s3 plus STORAGE_S3_BUCKET, STORAGE_S3_REGION, STORAGE_S3_ACCESS_KEY, STORAGE_S3_SECRET_KEY. For non-AWS providers (MinIO, R2, B2, …) also set STORAGE_S3_ENDPOINT.
  3. If you have existing local content, copy it first: node backend/scripts/migrate-storage.js --dry-run then node backend/scripts/migrate-storage.js. The script is idempotent and writes a failures CSV.
  4. Restart the backend. The startup check pings the bucket and refuses to boot on misconfig.

Note: presigned-URL serving (zero-bandwidth direct downloads from S3) is intentionally not in v1 — every request still streams through the backend so watermarks, devtools-detection, and access logging keep working.

🔔 Webhooks

PicPeak POSTs event/photo lifecycle notifications to URLs you configure under Settings → Webhooks. Each delivery is signed HMAC-SHA256 with a per-webhook secret in the X-PicPeak-Signature header so receivers can verify the request really came from your PicPeak instance.

Event types

Event Fires when
event.created Gallery created (admin or API)
event.published Draft becomes live (is_draft: true → false) — also fires when an event is created with is_draft=false
event.archived Bulk-archive, manual archive, or auto-archive on expiry
event.expired Expiration checker marks the gallery inactive (fires before event.archived in the cascade)
photo.uploaded Admin upload, API upload, guest upload, or auto-import
photo.deleted Single delete, bulk delete (NOT fired per-photo when an event is archived — receivers infer from event.archived to avoid flooding)

Payload shape

{
  "id": "delivery-uuid",
  "type": "event.published",
  "created_at": "2026-04-28T05:25:00.000Z",
  "data": {
    "event": { "id": 123, "slug": "wedding-smith", "share_url": "https://..." }
  }
}

Also sent on every request:

  • X-PicPeak-SignatureHMAC-SHA256(secret, raw_body) as hex
  • X-PicPeak-Event — the event type (handy for routing without parsing the body)
  • X-PicPeak-Delivery — UUID for idempotency on the receiver side
  • User-Agent: PicPeak-Webhooks/1.0

Verifying signatures

Node.js

const crypto = require('crypto');
function verify(secret, rawBody, signature) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(signature, 'hex');
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

Python

import hmac, hashlib
def verify(secret: str, raw_body: bytes, signature: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

curl + openssl (one-liner for a quick replay)

SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
[ "$SIG" = "$RECEIVED_SIG" ] && echo OK || echo MISMATCH

Retries + observability

  • 2xx → success, recorded with latency
  • Non-2xx or network error → exponential backoff: 1m → 5m → 30m → 2h → 12h, max 5 attempts
  • After max attempts: status failed, surfaces in Settings → Webhooks → Deliveries with a "Replay" button
  • Up to 5 deliveries in flight at once; one slow consumer can't block others (configurable via WEBHOOK_DELIVERY_CONCURRENCY)
  • Response body truncated to 1KB before storage so chatty receivers don't bloat the audit log

The deliveries page (/admin/webhooks/:id/deliveries) shows every attempt with timestamp, status, HTTP code, latency, payload sent, signature, and response. Click "Send test event" to fire a synthetic delivery for any event type.

SSRF protection

Webhook URLs are validated against the same private-IP blocklist used elsewhere in the app — loopback, private RFC1918 ranges, link-local, .local/.internal hostnames, cloud metadata endpoints. The check runs both at create time and per-delivery (DNS-rebinding mitigation).

For local development with a receiver on the same machine or docker network, set WEBHOOK_ALLOW_PRIVATE_URLS=true. Production deployments must leave this OFF.

💻 System Requirements

Minimum Requirements

  • CPU: 2 CPU cores
  • RAM: 4 GB minimum for a normal photo-upload workload — sharp/libvips decodes the full uncompressed frame before resize, and the default two worker loops at sharp-concurrency 2 can push peak RSS past 1.5 GB on a batch of 20-MP+ photos. On a 2 GB VPS that's enough to OOM-kill the backend mid-batch (surfaces as 503s on thumbnails — see Low-memory hosts below for the recipe to run on 2 GB).
  • Storage: 20GB minimum (plus photo storage needs)
  • OS: Linux (Ubuntu 20.04+), macOS, or Windows with WSL2
  • Node.js: v18.0.0 or higher
  • Database: SQLite (included) or PostgreSQL 12+
  • Docker: v20.10.0+
  • Docker Compose: v2.0.0+

Low-memory hosts

Running on 2 GB RAM (e.g. an entry-level VPS) is workable but requires tuning the upload-processor concurrency down. The backend auto-detects total RAM at startup via os.totalmem() — on a host that reports < 3 GB, it defaults UPLOAD_PROCESSOR_CONCURRENCY to 1 instead of 2 and logs a one-shot warning. You can pin the value explicitly in .env:

# Single worker loop — slower batch processing, lower peak RSS
UPLOAD_PROCESSOR_CONCURRENCY=1

The trade-off is throughput: a single worker processes one photo at a time, so a 100-photo batch takes ~2× as long but won't OOM. Health-check note: if the backend dies under memory pressure, the gallery serves 503 Service Unavailable on thumbnails until Docker's restart: unless-stopped brings the container back. Persistent 503s during/after an upload batch on a low-memory host are almost always this.

Video Support Requirements

When enabling video uploads, consider these additional resources:

Resource Recommendation Notes
RAM 4GB+ recommended FFmpeg processing requires more memory
Storage Plan for 10-100x more Videos are significantly larger than images
CPU Additional cores help Video thumbnail extraction is CPU-intensive
Bandwidth Higher throughput Video streaming requires more bandwidth

Technical Notes:

  • FFmpeg is bundled via npm (@ffmpeg-installer/ffmpeg) - no system installation required
  • Maximum upload size: 10GB per video file
  • Chunked upload support for files >100MB (resumable uploads)
  • Supported formats: MP4, WebM, MOV, AVI
  • Video thumbnails are automatically generated from the first few seconds

For Nginx/Reverse Proxy: If using Nginx, increase the client max body size:

client_max_body_size 10G;
proxy_read_timeout 3600;
proxy_send_timeout 3600;

🤝 Contributing

We love contributions! PicPeak is built by photographers, for photographers. Whether you're fixing bugs, adding features, or improving documentation, your help is welcome.

See our Contributing Guide for details.

📊 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 3GBUnlimited***
Client Uploads Limited
API Access Paid
Open Source
Customer Accounts
Quotes / Contracts / Invoices 🧪 Beta
Incoming Invoices & Accounting 🧪 Beta

*You still bring your own server (own hardware or a VPS) and, if you want one, a domain. **Limited only by your server storage. ***Pixieset's "unlimited" is photos only; video is capped by plan (roughly 010 h depending on tier). 🧪 Beta = built but feature-flagged off by default (see Beta Features).

🛡️ Security

PicPeak takes security seriously:

  • 🔐 Password hashing with bcrypt
  • 🎫 JWT-based authentication
  • 🚦 Rate limiting on all endpoints
  • 🛡️ CORS protection
  • 📝 Activity logging
  • 🔒 Secure file access

Found a security issue? Please open a security issue on GitHub

📸 Screenshots

🎛️ Admin Dashboard

Get a complete overview of your photo galleries, analytics, and system status.

PicPeak Admin Dashboard

📊 Analytics & Insights

Track gallery performance, view statistics, and monitor user engagement.

PicPeak Analytics Dashboard

📁 Event Management

Organize and manage your photo galleries with intuitive event management tools.

PicPeak Events Management

Key Interface Highlights

👆 Click to see more interface details

What makes PicPeak's interface special:

  • 🎨 Clean Design: Modern, photographer-friendly interface
  • 📱 Responsive: Perfect on desktop, tablet, and mobile
  • Fast Loading: Optimized for quick photo browsing
  • 🔒 Secure Access: Password-protected galleries with expiration
  • 📤 Easy Uploads: Drag & drop functionality for effortless photo management
  • 🎯 Client-Focused: Intuitive gallery experience for your clients

🗺️ Roadmap

We're constantly improving PicPeak and welcome contributions from our community! If you have ideas for new features or want to help implement existing ones, please open an issue or submit a pull request. Your contributions help make PicPeak better for everyone.

🚧 Beta Features (Use at your own risk)

These features are currently in beta testing and may have limited functionality or stability:

Feature Description Status
CRM & Accounting Module Quotes, contracts, invoices (+ Storno), hours logging, calendar, and tax report — plus inbound supplier-invoice capture, internal expenses, and a Treuhänder/Banana (Swiss/LI) accountant-journal export. Feature-flagged off by default. Seeded contract blocks, payment terms, IBAN / QR-bill and tax defaults are examples only and need legal / financial / tax review before customer-facing use. See docs.picpeak.app/features/crm. 🧪 Beta
Simple Deployment Script One-click deployment script for quick server setup with automated configuration and dependency installation 🧪 Beta

📋 Future Enhancements

Feature Description Priority Status
Backup & Restore Comprehensive backup system with S3/MinIO support, automated scheduling, and safe restore functionality High Implemented
External Media Library (Reference Mode) Use an external folder library as a readonly source with import and ondemand thumbnail generation High Implemented
Download Protection Advanced image protection system with canvas rendering, invisible watermarking, right-click prevention, and DevTools detection to protect photos from unauthorized downloads High Implemented
Gallery Templates Multiple gallery layouts (grid, masonry, carousel, timeline, hero, mosaic) with custom CSS styling support. Includes starter templates like Apple Liquid Glass for complete visual customization Medium Implemented
Face Recognition AI-powered face detection to help guests find their photos and create automatic person-based albums Low 🔄 Open
Gallery Feedback Allow guests to like, rate, and comment on photos with admin notifications and moderation Medium Implemented
Video Support Upload and display videos alongside photos in galleries with streaming support Low Implemented
Multiple Administrators Support for multiple admin accounts with role-based permissions and activity tracking Low Implemented
Filtering & Export Options Filter photos by likes, ratings, comments, or favorites. Search by filename. Sort by date, name, size, or rating. Export filtered selections as ZIP or generate Capture One/Lightroom-compatible file lists for professional workflows Medium Implemented

Status Legend: Implemented | 🚧 In Progress | 🔄 Open | 📋 Planned

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 the time spent on new features, bug fixes, and keeping the demo + docs running.

Buy Me A Coffee

Other ways to support without spending anything: star the repo, share it with photographer friends, file good bug reports, or open a PR.

🙏 Acknowledgments

PicPeak is inspired by the best features of commercial platforms while remaining completely open source. Special thanks to all contributors who make this project possible.

👥 Contributors

A huge thank you to the people whose code, reports, and feedback have shaped PicPeak:

  • @the-luap — creator and lead maintainer. Started the project and built PicPeak's foundation and the entire gallery experience (events, galleries, uploads, sharing, download protection, templates), plus backup & restore, analytics, system health, branding/theming, and WhatsApp notifications — and the architecture every later feature builds on.
  • @Luca-Timo — native Apple Silicon multi-arch images, external-URL toggle for legal CMS pages, the lazy-loaded folder tree picker, the admin-email picker on event creation, the data-driven self-hosted webfont system, the gallery header/banner decoupling, several typed-API refactors, and the CRM + accounting suite (quotes/contracts/invoices, hours logging, calendar, tax report, inbound supplier-invoice capture, expenses, and the Treuhänder/Banana export). Consistently raises the bar with thoughtful PRs.
  • @Rekoo-PS — sharp-eyed bug reporter and product feedback. Filed the issues that drove the login-loop fix, the gallery-loading skeleton work, the redirection cleanup, the mobile-lightbox overhaul, the admin-events search-counter fix, the photo-count column, and the bulk-delete workflow. Also a BuyMeACoffee supporter — the kind of feedback loop that keeps the project useful for real deployments.

If you've contributed and aren't listed here, please open a PR — this list is meant to grow.

🤖 AI-Assisted Development

This project was generated with the assistance of AI technology, but has been:

  • Fully tested end-to-end by human developers
  • 🔒 Security audited with comprehensive security checks
  • 👨‍💻 Human-reviewed for code quality and best practices
  • 🧪 Production-tested in real-world scenarios

We believe in transparent development practices and the responsible use of AI as a tool to accelerate development while maintaining high standards of quality and security.

⚠️ CRM & Accounting disclaimers — examples only, verify locally

The CRM & accounting modules (contracts, invoices, QR-bills, the tax report and the accountant exports) ship seeded content and computed figures that are intended as a starting point only:

  • Contract blocks (image rights, NDA, model release, cancellation, jurisdiction, …) are written by the maintainer, not by a lawyer. Every operator must have their lawyer review and adapt them before sending any contract to a customer.
  • QR-bills and SEPA EPC payloads are rendered from the data you typed. Picpeak is open source — please scan a test invoice with your bank's app to check the QR actually works. We are not responsible for any mistakes that come from sending an invoice with bad data on it.
  • Tax, VAT & accounting figures (the tax report, VAT-payable, the per-rate breakdown, the Treuhänder / Banana export, etc.) are computed from the data you enter and the defaults you configure. They are guidance only and jurisdiction-specific — tax rules, VAT rates, deduction schemes (e.g. the Liechtenstein 20 % Gewinnungskosten flat rate) and filing duties differ by country and change over time. Every operator must check their own tax / VAT regulations and verify the numbers with their accountant / Treuhänder / tax authority before relying on any figure or export. Picpeak makes no warranty that the output is correct for your jurisdiction or situation.

Read docs/crm-disclaimers.md before enabling the Contracts, Invoices or Accounting features.

📄 License

PicPeak is released under the MIT License. Use it freely for personal or commercial projects.

🚀 Ready to Get Started?

  1. Star this repository to show your support
  2. 📖 Read the docs at docs.picpeak.app
  3. 🐛 Report issues or request features
  4. 🤝 Join our community and contribute!

Made with ❤️ by photographers, for photographers
HomepageLive DemoGitHubDocumentationSupport