Files
immich-folio/README.md
Ralksta c9036fdc94 security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore

* feat(admin): collapse older backup entries by default

* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle

* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards

* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)

* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards

* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards

* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar

* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx

* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile

* style(admin): replace all emojis with clean Leica-style vector SVG icons

* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons

* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings

* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode

* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx

* fix(types): add protection and watermark optional properties to AppConfig interface

* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)

* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator

* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules

* fix(types): import type LightboxWatermark in PhotoGrid.tsx

* fix(types): destructure watermark prop in PhotoGrid function signature

* feat(seo): add configurable subpage title template and metadata descriptions

* feat(seo): add generateMetadata to about page for title template support

* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal

* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts

* feat(subpages): add page enabled toggle to deactivate subpages without deleting them

* feat(admin): add visual essay block builder for photo essay storytelling pages

* security: add Trivy container scan to Docker publish workflow

Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.

---------

Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00

9.6 KiB

Immich Folio

A self-hosted photography portfolio powered by Immich. Turns your Immich albums into a beautiful, public-facing gallery — without ever exposing your Immich server to the internet.

Immich Folio acts as a secure reverse proxy between your visitors and your private Immich instance. Your Immich server stays on your local network, completely invisible to the outside world.

Homepage — Editorial theme Photo grid — Editorial theme

Features

  • Configurable hero layouts — split, fullbleed, minimal, stacked (image + thumbnail strip), typographic (text-only), or mosaic (multi-image grid)
  • Hero image carousel — single image or crossfade carousel of multiple Immich assets
  • Masonry photo grid — responsive layout with natural aspect ratios and configurable columns, gap, and aspect ratio
  • Uniform grid mode — switch to a fixed-aspect uniform grid per-subpage or globally
  • Showcase / filmstrip / editorial-flow layouts — featured hero + grid, horizontal scroll strips, or alternating full-width and paired images
  • Per-subpage grid overrides — each subpage can define its own columns, gap, aspect ratio, and layout mode
  • Fullscreen lightbox — keyboard and swipe navigation, EXIF panel, adjacent image preloading
  • EXIF metadata on hover — camera body, lens, focal length, aperture, shutter speed, ISO shown directly on the grid
  • ThumbHash placeholders — instant blurred previews while full images load

Content & Organization

  • Subpage grouping — organize albums into named collections (e.g. /japan/tokyo-2023)
  • Auto-generated slugs — URL slugs derived from album names automatically
  • YAML gallery config — all gallery structure defined in a single content/gallery.yaml file
  • Markdown about page — content/about.md with frontmatter for portrait, name, location, and gear list
  • Dynamic OG images — auto-generated social preview images per album

Admin Panel

  • Visual page builder — drag & drop interface to manage hero images, standalone albums, subpages, and sections
  • Album picker — browse all shared Immich albums with search, see photo counts, and add them with one click
  • Settings editor — configure theme, grid layout, footer, legal/impressum, and SEO from a visual UI
  • Live YAML sync — changes are written directly to gallery.yaml and settings.yaml with automatic backups
  • Password protected — secured with its own admin password, separate from album passwords
Security & Infrastructure
Concern Protection
Server exposure Immich URL never leaves your network — all requests proxy server-side
API key Stored only in .env.local, never in client code
Asset IDs Immich UUIDs encrypted (AES-256) into opaque tokens
Album scope Only albums in gallery.yaml are accessible
Password protection Per-subpage password support
Rate limiting Per-IP sliding-window rate limiter (configurable RPM)
Vulnerability scanning Docker image scanned with Trivy on every release, results in the GitHub Security tab
  • Health check endpoint at GET /api/health
  • In-memory caching with configurable TTL
  • Standalone Docker image — multi-stage, non-root, ~150 MB
  • Dependencies kept current via Dependabot (npm + GitHub Actions, weekly)

Requirements

  • Immich 3.0 or newer. Immich 3.0 changed how album assets are retrieved; earlier versions are not supported as of v0.9.0. On an older server, albums render with the correct title but no photos, and the map stays empty — the app logs a warning naming this as the likely cause.
  • Node.js 20+ (or just use the Docker image)

Quick Start

# Clone and install
git clone https://github.com/ralksta/immich-folio.git
cd immich-folio
npm install

# Configure
cp .env.local.example .env.local
# Edit .env.local with your Immich server URL and API key

cp content/gallery.yaml.example content/gallery.yaml
# Edit gallery.yaml with your album UUIDs

# Run
npm run dev

Configuration

Environment Variables (.env.local)

# Required
IMMICH_API_URL=http://your-immich-server:2283
IMMICH_API_KEY=your-api-key

# Optional
SITE_TITLE=My Photography            # default: "Gallery"
SITE_SUBTITLE=A visual journal        # default: empty
CACHE_TTL=300                          # seconds, default: 300
STALE_MAX_AGE=86400                    # seconds an expired cache entry survives during an outage, default: 86400 (24h), 0 disables
IMMICH_TIMEOUT_MS=15000                # Immich response wait, default: 15000
IMAGE_CACHE_VERSION=1                  # bump to bust browser image caches, default: off
RATE_LIMIT_RPM=1500                    # requests/min/IP, default: 1500
AUTH_SECRET=long-random-string        # required in production
TRUSTED_PROXY_HOPS=1                   # reverse proxies in front, default: 0
ADMIN_PASSWORD=your-secure-password   # enables /admin panel

Behind a reverse proxy, set TRUSTED_PROXY_HOPS to the number of proxies in front of the app (nginx/Traefik/Caddy = 1; Cloudflare in front of nginx = 2). Without it the client IP is read from a header the client itself can set, which defeats the brute-force limits on the password endpoints. See Trusted Proxies.

Immich API Key Permissions

Create a dedicated API key in Immich under Account Settings → API Keys. Immich Folio only needs read access — it never modifies your library.

Permission Required Used for
album.read ✅ Yes List and fetch album metadata & photo lists
asset.read ✅ Yes Fetch asset metadata, EXIF data, thumbnails, previews, and originals
asset.view ✅ Yes Stream image/video files (thumbnail, preview, video playback)

No write permissions needed. album.create, asset.upload, asset.delete, etc. can all be left off.

Tip (Admin Panel): The Admin Panel also uses POST /search/metadata to browse your full library for the hero image picker. This is covered by asset.read — no additional permission required.

All gallery structure — hero images, albums, subpages, grid layout, footer — is defined in content/gallery.yaml.

→ Gallery Configuration Guide

Theming

Six built-in presets with distinct visual identities — or mix and match with fine-grained control over colors, fonts, corners, photo frames, hero layout, and grid style.

theme: studio # or: minimal, editorial, classic, noir, monograph

→ View all Themes & Configuration Guide

Docker

services:
  lightbox:
    build: .
    container_name: immich-folio
    restart: unless-stopped
    ports:
      - '7211:7211'
    env_file:
      - .env.local
    volumes:
      - ./content:/app/content:ro

Run with:

docker compose up -d

The gallery will be available at http://localhost:7211.

Advanced Docker (Standalone, Health Check, Proxy)

Standalone Docker

# Build
docker build -t immich-folio .

# Run
docker run -d \
  --name immich-folio \
  --restart unless-stopped \
  -p 7211:7211 \
  --env-file .env.local \
  -v ./content:/app/content:ro \
  immich-folio

Note: The content/ volume mount lets you update gallery.yaml and about.md without rebuilding the image.

Health Check

The container includes a built-in health check at /api/health:

curl http://localhost:7211/api/health

Behaviour when Immich is unreachable

Immich Folio buffers your gallery rather than merely proxying it:

  • Album and asset pages keep serving the last known good data for up to STALE_MAX_AGE, so a restarting or briefly unreachable Immich does not take the public site down with it.
  • Once nothing cached is left, they return 503, never 404 — a 404 would tell search engines to drop a URL for content that still exists.
  • Outages are never cached, so the gallery recovers as soon as Immich does.

The cache lives in the process, so it is empty right after a container restart.

Reverse Proxy

Put Immich Folio behind nginx / Caddy / Traefik with TLS. Example Caddy config:

photos.example.com {
    reverse_proxy localhost:7211
}

Admin Panel

A built-in visual editor at /admin lets you manage your gallery without editing YAML files manually.

Enable it by setting ADMIN_PASSWORD in your environment:

ADMIN_PASSWORD=your-secure-admin-password

Then navigate to http://your-site/admin and log in.

→ Admin Panel Guide

Tech Stack

  • Next.js 16 (App Router, standalone output)
  • React 19
  • TypeScript
  • Vanilla CSS (no framework)

License

MIT License — free to use and modify for the Immich community.