{"id":47284459,"url":"https://github.com/foldergram/foldergram","last_synced_at":"2026-04-01T20:31:31.522Z","repository":{"id":344594129,"uuid":"1181781009","full_name":"foldergram/foldergram","owner":"foldergram","description":"Self-hosted folder-based Instagram-style photo and video gallery app.","archived":false,"fork":false,"pushed_at":"2026-03-31T19:46:37.000Z","size":5765,"stargazers_count":195,"open_issues_count":10,"forks_count":9,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-03-31T21:35:15.898Z","etag":null,"topics":["docker","express","ffmpeg","folder-based","instagram","local-first","media-gallery","nodejs","photo-gallery","pwa-app","self-hosted","sqlite","typescript","video-gallery","vite","vue"],"latest_commit_sha":null,"homepage":"https://foldergram.github.io","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/foldergram.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"docs/security.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-14T16:06:07.000Z","updated_at":"2026-03-31T20:47:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/foldergram/foldergram","commit_stats":null,"previous_names":["foldergram/foldergram"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/foldergram/foldergram","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foldergram%2Ffoldergram","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foldergram%2Ffoldergram/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foldergram%2Ffoldergram/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foldergram%2Ffoldergram/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/foldergram","download_url":"https://codeload.github.com/foldergram/foldergram/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foldergram%2Ffoldergram/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31291664,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","express","ffmpeg","folder-based","instagram","local-first","media-gallery","nodejs","photo-gallery","pwa-app","self-hosted","sqlite","typescript","video-gallery","vite","vue"],"created_at":"2026-03-16T05:28:42.599Z","updated_at":"2026-04-01T20:31:31.515Z","avatar_url":"https://github.com/foldergram.png","language":"TypeScript","readme":"\u003cdiv align=\"center\"\u003e\n\n\u003ca href=\"https://foldergram.intentdeep.com/\"\u003e\n  \u003cimg src=\"client/public/github_banner.png\" alt=\"Foldergram\" /\u003e\n\u003c/a\u003e\n\u003cbr /\u003e\u003cbr /\u003e\n\u003ca href=\"https://foldergram.intentdeep.com/\"\u003e\n  \u003cimg src=\"client/public/favicon.svg\" width=\"80\" alt=\"Foldergram Logo\" /\u003e\n\u003c/a\u003e\n\n# Foldergram\n\n**Local-only photo and video gallery for folders, with an Instagram-inspired browsing pattern.**\n\n[![Available on GHCR](https://img.shields.io/badge/GHCR-foldergram-blue?style=flat-square\u0026logo=docker)](https://github.com/foldergram/foldergram/pkgs/container/foldergram)\n[![LIVE DEMO](https://img.shields.io/badge/LIVE%20DEMO-FOLDERGRAM-a951ab?style=flat-square\u0026logo=googlechrome\u0026logoColor=white)](https://foldergram.intentdeep.com/)\n[![Node.js Version](https://img.shields.io/badge/Node.js-22%20LTS-green?style=flat-square\u0026logo=nodedotjs)](https://nodejs.org/)\n[![Vue 3](https://img.shields.io/badge/Vue.js-3-4fc08d?style=flat-square\u0026logo=vuedotjs)](https://vuejs.org/)\n[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg?style=flat-square)](https://www.gnu.org/licenses/agpl-3.0)\n\n[Live Demo](https://foldergram.intentdeep.com/) • [Features](#features) • [Installation](#installation) • [Configuration](#configuration) • [Tech Stack](#tech-stack) • [Contributing](CONTRIBUTING.md)\n\n**Try the public demo:** [foldergram.intentdeep.com](https://foldergram.intentdeep.com/)\n\n\u003c/div\u003e\n\n---\n\nFoldergram is a self-hosted web application that turns your local folders into a beautiful, instagram-style feed and profile. It turns your local folder to app folders (profiles), and serves a lightning-fast Progressive Web App (PWA).\n\nFoldergram indexes supported media from a configured `GALLERY_ROOT`, stores metadata in SQLite, generates thumbnails and previews, and serves a fast feed-style web app for local browsing. Derivatives can be generated during scans or lazily on first request, and image detail pages can be configured to use generated previews or originals. The current app includes Home, Reels, Explore, Library, Likes, Moments or Highlights, App Folder pages, post detail views, original-media download controls, delete actions, scan controls, and rebuild tools.\n\n## Features\n\n- **Instagram-Inspired UI:** Enjoy a familiar feed layout, dedicated app folders (profiles), and a media viewer.\n- Home feed with `Recent`, `Rediscover`, and `Random` modes.\n- A dedicated `/reels` route with a video-only queue. Settings can default it to `Recommended`, `Recent`, or `Random`.\n- A top rail that shows `Moments` when capture-date coverage is strong, or `Highlights` when it is not.\n- Library browsing with App Folder search, sorting, and delete actions.\n- App Folder pages with a posts grid and a folder-specific `Reels` tab when videos exist.\n- Shared likes in SQLite for signed-in admin/viewer sessions, plus browser-local favorites in public mode.\n- Image and video support with configurable eager or lazy derivative generation for fast browsing.\n- Original-media download controls on home feed cards, post detail, and stories, alongside open-original actions.\n- Optional role-based local access with admin, viewer, and public browse modes.\n- Settings actions for Home/Reels default modes, manual scan, thumbnail rebuild, and library-index rebuild.\n- A web app manifest plus production service worker registration.\n- A debounced filesystem watcher in development mode only.\n- No multi-user accounts, cloud sync, uploads, comments, messaging, notifications, or remote APIs.\n\n## How It Works\n\nFoldergram maps directly to your filesystem:\n\n1. **App Folders:** Any non-hidden folder under `GALLERY_ROOT` that directly contains supported media becomes one indexed App Folder.\n2. **Posts:** Each supported image or video directly inside that folder becomes one indexed post.\n3. **Nested folders stay separate:** Nested local folders are not merged into their parent App Folder. If a nested folder directly contains supported media, it becomes its own App Folder with parent folder name in the route (e.g. /folder/parent-nested).\n4. **Root files are ignored:** Files placed directly in `GALLERY_ROOT` are ignored.\n\nRuntime reads come from SQLite and generated derivatives, not from live filesystem scans on every request.\n\n### Supported Formats\n\n- **Images:** `.jpg`, `.jpeg`, `.png`, `.webp`, `.gif`\n- **Videos:** `.mp4`, `.mov`, `.m4v`, `.webm`, `.mkv`\n\nAnimated image files keep animation in the post viewer preview and home feed cards. Folder/profile grids and other thumbnail surfaces remain static.\n\nFor source installs, video support requires `ffmpeg` and `ffprobe`. The Docker image installs them inside the container.\n\n## Installation\n\n### 🐳 The Easy Way (Docker - Recommended)\n\nThis is the recommended path for most users. It uses the pre-built GitHub Container Registry (GHCR) image.\n\n1. Create a folder for Foldergram and move into it:\n\n```bash\nmkdir foldergram\ncd foldergram\n```\n\n2. Download the Compose file:\n\n```bash\nwget -O docker-compose.yml https://raw.githubusercontent.com/foldergram/foldergram/main/docker-compose.yml\n```\n\n3. Create your first gallery folder:\n\n```bash\nmkdir -p data/gallery/example-album\n```\n\n4. Move a few photos or videos into `data/gallery/example-album/` to create your first indexed App Folder.\n5. Start the container:\n\n```bash\ndocker compose up -d\n```\n\n6. Open `http://localhost:4141`.\n\nIn Docker, Foldergram runs in production mode and the app inside the container listens on `4141`. If you need a different host port, change the left side of `4141:4141` in [`docker-compose.yml`](docker-compose.yml).\n\nFor the default Docker Compose setup, the container uses the image's built-in\nproduction defaults plus the mounted `./data/...` volumes. The source-install\n`.env` file is not read inside the container unless you wire that in yourself.\n\nThe shipped Compose file includes `IMAGE_DETAIL_SOURCE: preview` and\n`DERIVATIVE_MODE: eager`.\n\nIf you want lazy derivatives or original-backed image detail pages in Docker,\nedit those values in `docker-compose.yml` before starting the container.\n\nFor an optional read-only public demo in Docker, add `PUBLIC_DEMO_MODE: \"1\"`\nunder the Compose `environment:` block. If the browser-visible origin differs\nfrom the upstream Node host, also set `CSRF_TRUSTED_ORIGINS` to that public\norigin.\n\n### If You Already Cloned This Repository\n\nThe repository includes:\n\n- [`docker-compose.yml`](docker-compose.yml) for the GHCR image\n- [`docker-compose.local.yml`](docker-compose.local.yml) as a local-build override\n\nTo build locally from source instead of pulling from GHCR, run:\n\n```bash\ndocker compose -f docker-compose.yml -f docker-compose.local.yml up -d --build\n```\n\nThis command uses the same runtime settings and volumes, but builds the image locally from the repository `Dockerfile`.\n\n### Run from Source\n\n\u003e **Note:** This repository is set up as a workspace. **`pnpm` is preferred** for the best development experience, but standard `npm` is also supported if you prefer the default Node toolchain.\n\nRequirements:\n\n- Node.js 22\n- `npm` or `pnpm`\n- `ffmpeg` and `ffprobe` if you want video support outside Docker\n\n1. Clone the repository:\n\n```bash\ngit clone https://github.com/foldergram/foldergram.git\ncd foldergram\n```\n\n2. Create your local env file:\n\n```bash\ncp .env.example .env\n```\n\n3. Install dependencies:\n\n```bash\npnpm install\n# or\nnpm install\n```\n\n4. Start the development workspace:\n\n```bash\npnpm dev\n# or\nnpm run dev\n```\n\nDevelopment ports:\n\n- Client: prefers `http://localhost:4141` and automatically uses the next free port up to `4144`\n- API: `http://localhost:4140`\n- Docs: `http://localhost:4145`\n\nThe Vite client stays within the reserved `4141-4144` range in development, so it can move off `4141` without colliding with the API or docs ports.\n\nIf you only want part of the workspace, use:\n\n- `pnpm dev:server`\n- `pnpm dev:client`\n- `pnpm dev:docs`\n\nFor a production build from source:\n\n```bash\npnpm build\npnpm start\n# or\nnpm run build\nnpm start\n```\n\nThen open `http://localhost:4141`.\n\n## Configuration\n\nDefault paths come from [`.env.example`](.env.example):\n\n```text\ndata/\n  ├─ gallery/       # Original source media\n  ├─ db/\n  │   └─ gallery.sqlite\n  ├─ thumbnails/    # Generated thumbnails and poster images\n  └─ previews/      # Generated previews\n```\n\n| Variable                      | Default             | Description                                                               |\n| ----------------------------- | ------------------- | ------------------------------------------------------------------------- |\n| `NODE_ENV`                    | `development`       | Runtime mode.                                                             |\n| `SERVER_PORT`                 | `4141`              | Production Express port.                                                  |\n| `DEV_SERVER_PORT`             | `4140`              | Express server port during `pnpm dev`.                                    |\n| `DEV_CLIENT_PORT`             | `4141`              | Base Vite client port during `pnpm dev`. The client may use up to `4144`. |\n| `DATA_ROOT`                   | `./data`            | Root directory for app-managed storage.                                   |\n| `GALLERY_ROOT`                | `./data/gallery`    | Root directory scanned for App Folders.                                   |\n| `DB_DIR`                      | `./data/db`         | SQLite database directory.                                                |\n| `THUMBNAILS_DIR`              | `./data/thumbnails` | Generated thumbnail output directory.                                     |\n| `PREVIEWS_DIR`                | `./data/previews`   | Generated preview output directory.                                       |\n| `IMAGE_DETAIL_SOURCE`         | `preview`           | For image detail pages, use generated previews or stream originals.       |\n| `DERIVATIVE_MODE`             | `eager`             | Generate derivatives during scans or lazily on first request.             |\n| `LOG_VERBOSE`                 | `0`                 | Truthy values are `1`, `true`, `yes`, and `on`.                           |\n| `SCAN_DISCOVERY_CONCURRENCY`  | `4`                 | Folder discovery concurrency.                                             |\n| `SCAN_DERIVATIVE_CONCURRENCY` | `4`                 | Derivative generation concurrency.                                        |\n| `PUBLIC_DEMO_MODE`            | `0`                 | When enabled, all API mutations become read-only and return `403`.        |\n| `CSRF_TRUSTED_ORIGINS`        | empty               | Comma-separated extra browser origins allowed for mutating API requests.  |\n\n`DATA_ROOT` is the base path for the app's local storage layout. If you set\nonly `DATA_ROOT`, Foldergram will default the other storage paths to\n`\u003cDATA_ROOT\u003e/gallery`, `\u003cDATA_ROOT\u003e/db`, `\u003cDATA_ROOT\u003e/thumbnails`, and\n`\u003cDATA_ROOT\u003e/previews`. Set `GALLERY_ROOT`, `DB_DIR`, `THUMBNAILS_DIR`, or\n`PREVIEWS_DIR` separately only when you need a non-standard layout.\n\nDocker uses the fixed internal container port `4141`, and other production\nruntimes continue to use `SERVER_PORT`, which defaults to `4141` in the Docker\nimage.\n\nFor the default Docker Compose setup, runtime variables are defined in\n[`docker-compose.yml`](docker-compose.yml). The source-install `.env` file is\nnot read directly by the container.\n\n### Detail Media and Derivative Timing\n\n- `IMAGE_DETAIL_SOURCE=preview` keeps image detail pages on generated previews.\n- `IMAGE_DETAIL_SOURCE=original` makes image detail pages stream `/api/originals/:id`.\n- `DERIVATIVE_MODE=eager` generates thumbnails and previews during scans.\n- `DERIVATIVE_MODE=lazy` indexes metadata during scans, then generates missing files the first time `/thumbnails/...` or `/previews/...` is requested and caches them on disk.\n\nThese flags are independent:\n\n- feed cards, folder grids, avatars, and other list surfaces still use generated derivatives\n- `IMAGE_DETAIL_SOURCE` affects images only; videos still default to preview playback\n- `Rebuild Library Index` refreshes the SQLite-backed index; in lazy mode it does not pre-generate missing derivatives\n- `Regenerate Thumbnails` remains a manual thumbnail and video-poster rebuild only; it does not rebuild previews\n\n### Access Protection\n\nAccess protection is configured from the Settings page, not from `.env`.\n\nThe current implementation supports:\n\n- `admin` sessions with full access\n- `viewer` sessions with shared likes but no Settings, Trash, scans, rebuilds, or delete actions\n- `anonymous` public sessions with browse-only access and browser-local favorites\n- `viewer_access_mode=off` for admin-only access\n- `viewer_access_mode=password` for a separate viewer password\n- `viewer_access_mode=public` for anonymous browsing plus admin unlock from `More`\n\nFoldergram stores one-way password hashes plus signed session metadata in SQLite. In public mode, anyone who can reach the app can browse immediately, favorites stay in the current browser only, and admins can elevate back into full access from `More` with the admin password.\n\n### Public Demo Deployments\n\nTo run Foldergram as a public read-only demo, set the following in `.env`:\n\n```env\nNODE_ENV=production\nPUBLIC_DEMO_MODE=1\nCSRF_TRUSTED_ORIGINS=https://foldergram.intentdeep.com\n```\n\n`PUBLIC_DEMO_MODE=1` blocks every `POST`, `PUT`, `PATCH`, and `DELETE` request\nunder `/api`, including future routes. `CSRF_TRUSTED_ORIGINS` is\nonly needed when the browser-visible origin differs from the upstream Node host seen by Express, such as behind a reverse proxy or HTTPS terminator.\n\n## Tech Stack\n\n**Backend**\n\n- Node.js 22 + Express 5 + TypeScript\n- SQLite via `node:sqlite`\n- Sharp for image derivatives\n- FFmpeg and FFprobe for video processing\n- Chokidar for the development watcher\n- Zod for runtime validation\n\n**Frontend**\n\n- Vue 3\n- Vite\n- Vue Router 4\n- Pinia\n- UnoCSS\n\n**Workspace**\n\n- `pnpm` monorepo\n\n## Scripts\n\n- `pnpm dev`\n- `pnpm dev:server`\n- `pnpm dev:client`\n- `pnpm dev:docs`\n- `pnpm build`\n- `pnpm start`\n- `pnpm test`\n- `pnpm rescan`\n\n## Contributing\n\nFoldergram welcomes small, clearly aligned pull requests for bug fixes, documentation, tests, and focused polish.\n\nBefore working on major features, architectural changes, or changes to core behavior such as scanning, indexing, routing, auth or access flow, and storage strategy, open an issue or discussion first. Pull requests for large changes that were not discussed in advance will not be accepted.\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for the full contribution policy, local setup notes, branch naming guidance, and pull request expectations.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoldergram%2Ffoldergram","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffoldergram%2Ffoldergram","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoldergram%2Ffoldergram/lists"}