Afilmory: a self-hosted photo gallery that builds a static manifest from your own storage
Modern photo gallery for photographers, with S3/GitHub sync, EXIF details, maps, and a WebGL viewer.
At a glance
- What is it?
- Afilmory is a TypeScript monorepo that turns photos sitting in S3, GitHub, Eagle or a local disk into a gallery with EXIF, maps and a WebGL viewer. The build step decides everything, which is both its selling point and its main constraint.
- Who is it for?
- Adopt Afilmory if you already keep originals in S3-compatible storage or a Git repository and you want a gallery whose content is produced by a build command you control. Do not adopt it if you need per-photo uploads from a browser, a database-backed CMS you host yourself, or a gallery that updates without a rebuild.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem Afilmory targets: a gallery that is a build artefact, not a database
Most self-hosted gallery software assumes you upload photos into it. The application owns the files, stores rows about them, and serves them back. Afilmory inverts that. The repository describes a pipeline where your photos already live somewhere (an S3-compatible bucket, a GitHub repository, an Eagle library, or a local directory), and a build step reads them, extracts metadata, generates thumbnails, and writes a manifest. The web app then renders that manifest.
The audience is narrow and specific. It is a photographer who is comfortable with a terminal, already has a storage bucket or a Git repo full of exports, and wants a portfolio site rather than a photo management tool. The README frames the project as combining "Auto Focus (AF), Aperture, Film, and Memory", which is branding rather than architecture. The architecture is the interesting part: a monorepo with apps/web as a Vite and React Router 7 SPA, apps/ssr as a Next.js wrapper for SEO and OpenGraph, and a packages/builder that does the actual image work.
That split matters when you evaluate it. If you want to drag a JPEG into a browser and see it appear, Afilmory is the wrong shape. If you want a folder of exports to become a site through one command, it is the right shape.
How the build pipeline and storage adapters fit together
The repository layout shows three distinct layers. The builder package handles image processing: Sharp for decoding and resizing, EXIF-Reader for metadata, AWS SDK for S3 operations, and Worker Threads or Cluster for parallelism. The backend under be/ is a Hono-based set of services (core API, dashboard, oauth-gateway) with Drizzle ORM over PostgreSQL and Redis for caching and pub/sub. The frontend consumes a generated manifest.
Storage is handled through an adapter pattern, and the README names four adapters: S3-compatible (AWS S3, MinIO, Backblaze B2, Alibaba Cloud OSS), GitHub, Eagle, and the local file system. Because these are adapters rather than separate code paths, the same build command should work against any of them, which is the design claim worth checking against your own bucket before you plan a deployment.
The processing features listed are concrete: conversion of HEIC/HEIF and TIFF, multi-size thumbnail generation, blurhash placeholders, Live Photo detection, HDR support, and display of Fujifilm film simulation settings. Incremental sync is described as "smart change detection" that processes only new or modified photos. The README does not specify how change detection is implemented (timestamps, hashes, or a stored index), so treat incremental behaviour as something to confirm on a second build rather than assume.
On the client side, the viewer is a separate package, packages/webgl-viewer, and the layout uses Masonic for the masonry grid. Maps are rendered with MapLibre from GPS coordinates in EXIF. The SSR app exists because a Vite SPA alone gives poor OpenGraph previews; the README lists "OpenGraph metadata for rich social media previews" as a feature, and the Next.js wrapper is how that is delivered.
Getting it running: the commands the README actually gives
The documented manual path is short. Clone the repository, install with pnpm, copy two configuration files, edit them, build the manifest, and start the dev server:
git clone https://github.com/Afilmory/Afilmory.git cd Afilmory pnpm install cp config.example.json config.json cp builder.config.default.ts builder.config.ts pnpm run build:manifest pnpm dev
Prerequisites are stated as Node.js 18+, pnpm 10+, and TypeScript 5.9+. The README also mentions copying .env.temp for environment variables, though the excerpt is truncated at that point, so the exact env file name and its contents are not verifiable from the material here.
The two config files are the real interface. config.json and builder.config.ts are separate: one is JSON, one is a TypeScript module, and the README says to edit both. The builder config is where storage adapters and processing options would be declared, but the README does not enumerate the keys. That is a genuine gap. You will be reading builder.config.default.ts to learn the schema rather than reading documentation.
Docker is presented as the recommended self-hosting route, but the README points to a separate repository, github.com/Afilmory/docker, instead of documenting it inline. There is also an official SaaS at afilmory.art and a TestFlight build for iOS. The README recommends the SaaS for people who want "zero setup", which is an honest framing: self-hosting here means operating a build pipeline, not clicking install.
Where the build-artefact model breaks down
The manifest is a snapshot. If a photo is added to the bucket after the build, the gallery does not know about it until the build runs again. There is no described mechanism for the running app to discover new files on its own. For a portfolio that changes a few times a month this is fine and arguably preferable to a database. For anything that needs uploads from a phone at the moment of shooting, it is the wrong architecture.
The backend complicates the picture. The monorepo contains a Hono core API, a dashboard backend, an OAuth gateway, PostgreSQL schemas via Drizzle, and Redis. Those exist to support the SaaS and the dashboard, not necessarily the static self-hosted path. A self-hoster following the manual instructions runs pnpm run build:manifest and pnpm dev; nothing in the README says PostgreSQL or Redis are required for that flow. But the presence of a full backend means the repository is larger than a gallery needs, and the boundary between "required for self-hosting" and "required for the hosted product" is not drawn explicitly in the README. That is the first thing to establish before you plan infrastructure.
Concurrency is another area where the documentation is thin. The README advertises "multi-process/multi-thread support for fast builds" and lists Worker Threads and Cluster in the build pipeline. It does not say how parallelism is configured, whether it is automatic, or what happens under memory pressure on a small VPS. Sharp decoding large TIFF or HEIC files in parallel workers is exactly the workload that will exhaust RAM on a cheap instance, and the material gives no guidance on limits.
Finally, the licence. The repository metadata reports NOASSERTION, which means GitHub could not match the licence file to a known identifier. The README does not discuss licensing terms. For a self-hosted deployment this needs to be resolved by reading the LICENSE file directly, not inferred.
Afilmory versus a database-backed gallery such as Immich
The natural comparison is Immich, which is also TypeScript, also self-hosted, and also aimed at people who care about their own photo library. The difference in approach is fundamental rather than cosmetic.
Immich is an application that owns your library. You upload photos into it, it stores them, indexes them in PostgreSQL, and provides a mobile app for backup. Its state lives in a database that the running server reads and writes continuously. Afilmory does not own your photos. It reads from storage you already control and emits a manifest; the originals stay in your bucket or repository. There is no upload endpoint described in the README, and no mobile backup client (the TestFlight build is for the gallery, not for ingest).
The practical consequence is what each tool is good at. Immich is for consolidating and searching a lifetime of photos across devices, with face recognition and albums that change constantly. Afilmory is for publishing a curated set of photographs as a fast, visually specific site with EXIF panels, maps and a WebGL viewer. If your goal is a portfolio URL that you can rebuild from a bucket, Afilmory's model is simpler: no database to back up, no migration path, no server that must stay up for the content to exist. If your goal is a personal library you browse and search, Afilmory does not attempt that and you should not try to make it.
A second, lighter alternative is a static site generator with a photo plugin. That gets you the same build-artefact property but without the WebGL viewer, the Live Photo handling, the Fujifilm recipe display, or the MapLibre integration. Afilmory's value sits in those processing features plus the viewer, not in the idea of generating a gallery at build time.
Maintenance, upgrades and what the licence file decides
Upgrade cost depends on which surface you touch. The web app is a Vite SPA on React 19 with Tailwind, Radix UI, Jotai, TanStack Query and i18next. That is a conventional modern stack, and version churn in it is the ordinary kind: React 19 and React Router 7 are both recent majors, and the README notes the SSR app is a separate Next.js wrapper, so a routing or rendering change can require work in two places.
The heavier maintenance burden is the builder. It depends on Sharp, EXIF-Reader and the AWS SDK, and it runs image decoding. Sharp ships prebuilt binaries per platform, so container images and Node version upgrades are the friction points rather than API changes. If you deploy via the separate Docker repository, you inherit its release cadence rather than the main repository's.
There are no retrieved releases for this repository, which means there is no changelog to read before upgrading. You are tracking the main branch or pinning a commit. For a self-hosted gallery that is acceptable if you rebuild deliberately and keep the previous manifest, but it is worth knowing before you wire an automatic deploy to every push.
On licensing, the only fact available is that GitHub reports NOASSERTION. That is not a licence, it is the absence of a recognised one. Whether you can use Afilmory commercially, modify it, or redistribute a modified version depends on the actual text in the repository's licence file, and the README does not summarise it. Read that file before you build a client site on it. This is not legal advice; it is a statement that the material needed to answer the question is not in the README.
Editorial conclusion
Adopt Afilmory if you already keep originals in S3-compatible storage or a Git repository and you want a gallery whose content is produced by a build command you control. Do not adopt it if you need per-photo uploads from a browser, a database-backed CMS you host yourself, or a gallery that updates without a rebuild. Before committing, verify one thing: that pnpm run build:manifest completes against your real storage and produces a manifest whose entries match the files you expect, because everything downstream depends on that artefact rather than on a live database.
Community notes