{"id":13587029,"url":"https://github.com/gbbirkisson/spis","last_synced_at":"2026-01-21T11:03:33.050Z","repository":{"id":62907830,"uuid":"555513326","full_name":"gbbirkisson/spis","owner":"gbbirkisson","description":"Simple private image server 🖼️","archived":false,"fork":false,"pushed_at":"2026-01-15T19:51:13.000Z","size":4511,"stargazers_count":187,"open_issues_count":8,"forks_count":11,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-01-17T17:26:29.703Z","etag":null,"topics":["gallery","images","pwa","raspberry-pi","self-hosted","video"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gbbirkisson.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2022-10-21T18:27:23.000Z","updated_at":"2026-01-15T20:04:06.000Z","dependencies_parsed_at":"2026-01-16T19:02:41.744Z","dependency_job_id":null,"html_url":"https://github.com/gbbirkisson/spis","commit_stats":{"total_commits":260,"total_committers":4,"mean_commits":65.0,"dds":0.6076923076923078,"last_synced_commit":"f408d672f2e9c9261e001a12ac126a2697958fa9"},"previous_names":[],"tags_count":52,"template":false,"template_full_name":null,"purl":"pkg:github/gbbirkisson/spis","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbbirkisson%2Fspis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbbirkisson%2Fspis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbbirkisson%2Fspis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbbirkisson%2Fspis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gbbirkisson","download_url":"https://codeload.github.com/gbbirkisson/spis/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbbirkisson%2Fspis/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28632267,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-21T04:47:28.174Z","status":"ssl_error","status_checked_at":"2026-01-21T04:47:22.943Z","response_time":86,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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":["gallery","images","pwa","raspberry-pi","self-hosted","video"],"created_at":"2024-08-01T15:05:58.598Z","updated_at":"2026-01-21T11:03:33.026Z","avatar_url":"https://github.com/gbbirkisson.png","language":"Rust","funding_links":["https://www.buymeacoffee.com/gbbirkisson"],"categories":["Rust","Software","self-hosted"],"sub_categories":["Photo Galleries"],"readme":"\u003ch1\u003e\n  \u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/gbbirkisson/spis\"\u003e\n      \u003cimg src=\"assets/favicon.png\" alt=\"Logo\" height=\"128\"\u003e\n    \u003c/a\u003e\n    \u003cbr\u003espis\n  \u003c/p\u003e\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cb\u003eS\u003c/b\u003eimple \u003cb\u003eP\u003c/b\u003erivate \u003cb\u003eI\u003c/b\u003emage \u003cb\u003eS\u003c/b\u003eerver\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://github.com/gbbirkisson/spis/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/gbbirkisson/spis\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/gbbirkisson/spis/commits/main\"\u003e\u003cimg src=\"https://img.shields.io/github/last-commit/gbbirkisson/spis/main\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/gbbirkisson/spis/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/gbbirkisson/spis/actions/workflows/ci.yml/badge.svg?branch=main\"\u003e\u003c/a\u003e\n\u003ca href=\"https://codecov.io/github/gbbirkisson/spis\"\u003e\u003cimg src=\"https://codecov.io/github/gbbirkisson/spis/branch/main/graph/badge.svg?token=5VQHEBQ7JV\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/gbbirkisson/spis/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/gbbirkisson/spis\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nThis project is called \"Simple Private Image Server\" or `SPIS` for short. It's purpose is to be\na lightweight and fast server to display media hosted on a private server. This project came\nabout when I was searching for a solution like this and found nothing. Everything seemed way too\nfeature heavy and slow, requiring you to setup databases and other unnecessary components.\n\nThe goals for this project are:\n* Simple to setup 🏝️\n* Flexible to operate ➰\n* Lightweight, multi-threaded and fast 🚀\n* Minimalistic GUI 🤩\n* Easy to use on mobile 📱\n\nSome features worth mentioning:\n* Endless scrolling 📜\n* Mark favorites ❤️\n* Play slideshows 📽\n* Filter by year, month, favorites, subdirectories 🎚️\n* Run custom scripts from UI 💻\n* Instantly load new files 📨\n* Is a progressive web app 📲\n\nI personally use this project to host around `40.000` images on a [Raspberry Pi CM4](https://www.raspberrypi.com/products/compute-module-4/) 🤯\n\nIf this project is just what you needed and/or has been helpful to you, please consider buying\nme a coffee ☕\n\n[![\"Buy Me A Coffee\"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/gbbirkisson)\n\n\u003ch2\u003eTable of contents\u003c/h2\u003e\n\n\u003c!-- vim-markdown-toc GFM --\u003e\n\n* [Demo](#demo)\n* [Configuration](#configuration)\n    * [Configuration File](#configuration-file)\n    * [Custom Commands](#custom-commands)\n* [Running](#running)\n    * [Docker](#docker)\n    * [Binary](#binary)\n        * [Why do we need a webserver](#why-do-we-need-a-webserver)\n        * [Diagram](#diagram)\n        * [So how do I set this up?](#so-how-do-i-set-this-up)\n        * [Debian + Systemd + Nginx](#debian--systemd--nginx)\n* [Configuration templating](#configuration-templating)\n* [Progressive Web App](#progressive-web-app)\n* [Changelog](#changelog)\n* [Development](#development)\n    * [Setup toolchain](#setup-toolchain)\n    * [Get some test media](#get-some-test-media)\n    * [Running](#running-1)\n\n\u003c!-- vim-markdown-toc --\u003e\n\n## Demo\n\nGo to [spis.fly.dev](https://spis.fly.dev) to see a live demo! Also try opening the demo on your\nmobile.\n\n## Configuration\n\nConfiguration can be managed through CLI flags, environment variables, or a TOML configuration\nfile. The precedence rule is: **Config File \u003e CLI Flags/Env Vars \u003e Defaults**.\n\nYou can always run `spis help` to see how to configure the server:\n\n```console\n$ spis help\nSimple private image server\n\nUsage: spis [OPTIONS] [COMMAND]\n\nCommands:\n  run       Runs the server [default]\n  process   Test process media files\n  template  Render configuration templates\n  help      Print this message or the help of the given subcommand(s)\n\nOptions:\n  -c, --config \u003cCONFIG\u003e\n          Path to configuration file [env: SPIS_CONFIG_FILE=]\n      --media-dir \u003cMEDIA_DIR\u003e\n          Path to search for media files [env: SPIS_MEDIA_DIR=] [default: ]\n      --data-dir \u003cDATA_DIR\u003e\n          Path to store data [env: SPIS_DATA_DIR=] [default: ]\n      --processing-schedule \u003cPROCESSING_SCHEDULE\u003e\n          Schedule to run processing on [env: SPIS_PROCESSING_SCHEDULE=] [default: \"0 0 2 * * *\"]\n      --processing-run-on-start\n          Run processing on start [env: SPIS_PROCESSING_RUN_ON_START=]\n      --api-media-path \u003cAPI_MEDIA_PATH\u003e\n          Path webserver will serve media on [env: SPIS_API_MEDIA_PATH=] [default: /assets/media]\n      --api-thumbnail-path \u003cAPI_THUMBNAIL_PATH\u003e\n          Path webserver will serve thumbnails on [env: SPIS_API_THUMBNAIL_PATH=] [default: /assets/thumbnails]\n      --server-address \u003cSERVER_ADDRESS\u003e\n          Listen to address [env: SPIS_SERVER_ADDRESS=]\n      --server-socket \u003cSERVER_SOCKET\u003e\n          Listen to UNIX socket [env: SPIS_SERVER_SOCKET=]\n      --feature-favorite\n          Disable feature favorite [env: SPIS_FEATURE_FAVORITE=]\n      --feature-archive\n          Disable feature archive [env: SPIS_FEATURE_ARCHIVE=]\n      --feature-delete-on-archive\n          Enable delete on archive [env: SPIS_FEATURE_DELETE_ON_ARCHIVE=]\n      --feature-follow-symlinks\n          Disable feature follow symlinks [env: SPIS_FEATURE_FOLLOW_SYMLINKS=]\n      --feature-allow-no-exif\n          Disable feature no exif [env: SPIS_FEATURE_NO_EXIF=]\n      --slideshow-duration-seconds \u003cSLIDESHOW_DURATION_SECONDS\u003e\n          Slideshow duration in seconds [env: SPIS_SLIDESHOW_DURATION_SECONDS=] [default: 5]\n  -h, --help\n          Print help\n  -V, --version\n          Print version\n```\n\n\u003e [!NOTE]\n\u003e Either `SERVER_ADDRESS` or `SERVER_SOCKET` need to be set, but not both!\n\n\u003e [!TIP]\n\u003e Both `SPIS_API_MEDIA_PATH` and `SPIS_API_THUMBNAIL_PATH` refer to how the webserver (`nginx`)\n\u003e is configured to serve media. For a details on how this works, look at the\n\u003e [diagram](#diagram).\n\n### Configuration File\n\nSee [config.toml](./config.toml) for example. You can also read the\n[config.schema.json](./config.schema.json) file to see the schema.\n\n### Custom Commands\n\nYou can define custom commands in the configuration file to execute scripts or system commands\non the current media file. These commands appear in the UI.\n\n```toml\n## Add custom commands to the UI ##\n[[custom_command]]\nname = \"echo\"\ncmd = [\"echo\", \"{path}\"]\nfa_icon = \"fa-solid fa-bullhorn\"\nhotkey = \"e\"\n```\n\n*   `name`: Unique identifier for the command (must be lowercase, no spaces).\n*   `cmd`: The command to execute. The `{path}` placeholder is replaced by the absolute path of the media file.\n*   `fa_icon`: FontAwesome icon class for the button.\n*   `hotkey`: (Optional) Single lowercase character to trigger the command via keyboard.\n\n## Running\n\n### Docker\n\nEasiest way to run `spis` is with the docker image:\n\n```console\n$ docker run -it \\\n    -p 8080:8080 \\\n    -v /path/to/your/media:/var/lib/spis/media \\\n    -v /path/to/save/data:/var/lib/spis/data \\\n    ghcr.io/gbbirkisson/spis\n```\n\nor using `docker compose`. Try the [docker compose](./examples/docker/docker-compose.yml) example by running...\n\u003e ```console\n\u003e $ cd examples/docker\n\u003e $ docker compose up\n\u003e ```\n\u003e ... and open up http://localhost:8080 in your browser.\n\n### Binary\n\nIf you want to run the binary, you will need to understand that `spis` needs a webserver to serve media.\n\n#### Why do we need a webserver\n\nBecause, serving images and videos is complicated! It involves caching, compressing, streaming and a host of other problems that `spis` does not need to know about. Some people that are way smarter than me have found a solution for all these problems. So instead of implementing a bad solution in `spis`, we stand on the shoulders of others and use a tried and tested webserver to handle this complexity for us.\n\n#### Diagram\n\nSo how do these things tie together. Well here is a simplified diagram of what happens when you open up `spis` in the browser.\n\n\u003e [!NOTE]\n\u003e Never during the interaction does `spis` read images of the file system and serve them.\n\n```mermaid\nsequenceDiagram\n    participant U as Browser\n    participant W as Webserver\n    participant S as SPIS\n    participant F as File System\n    autonumber\n    Note over U: User opens webpage in browser\n    U -\u003e\u003e W: GET /\n    W -\u003e\u003e S: GET /\n    S -\u003e\u003e W: Returns page\n    W -\u003e\u003e U: Returns page\n    Note over U: Browser fetches thumbnails\n    U -\u003e\u003e W: GET \u003cSPIS_API_THUMBNAIL_PATH\u003e/thumb.webp\u003cbr/\u003ei.e.\u003cbr/\u003eGET /assets/thumbnails/thumb.webp\n    W --\u003e\u003e F: Reads \u003cSPIS_DATA_DIR\u003e/thumbnails/thumb.webp\u003cbr/\u003ei.e.\u003cbr/\u003eReads /data/thumbnails/thumb.webp\n    W -\u003e\u003e U: Returns thumb.webp\n    Note over U: Browser fetches video\n    U -\u003e\u003e W: GET \u003cSPIS_API_MEDIA_PATH\u003e/video.mp4\u003cbr/\u003ei.e.\u003cbr/\u003eGET /assets/media/video.mp4\n    W --\u003e\u003e F: Reads \u003cSPIS_MEDIA_DIR\u003e/video.mp4\u003cbr/\u003ei.e.\u003cbr/\u003eReads /media/video.mp4\n    W -\u003e\u003e U: Streams video.mp4\n```\n\n#### So how do I set this up?\n\nWell these are the steps:\n\n1. [Download a binary](https://github.com/gbbirkisson/spis/releases) for your architecture and put in your path\n2. Install a webserver\n3. For video support make sure `ffmpeg` and `ffprobe` are in your path.\n4. Configure `spis` and run....we will get back to this\n5. Configure webserver and run....we will get back to this\n\nNow, steps `4-5` are super unhelpful (a bit like instructions on how to draw an owl). This is because `spis` is flexible, and does not care how you do this. You can use any combination of webserver + supervisor to get this up and running. So covering every single way to set this up is not feasible.\n\nSo I'm just going to describe how to do this with `systemd` and `nginx` on a `debian` system.\n\n#### Debian + Systemd + Nginx\n\n\u003e [!NOTE]\n\u003e We are using [configuration templating](#configuration-templating) in this example!\n```console\n# 1.1 Download spis\n$ sudo curl -L -o /usr/local/bin/spis https://github.com/gbbirkisson/spis/releases/download/latest/spis-x86_64-unknown-linux-gnu\n\n# 1.2 Make executable\n$ sudo chmod +x /usr/local/bin/spis\n\n# 2. Install nginx\n$ sudo apt install nginx\n\n# 3. Add video support\n$ sudo apt install ffmpeg\n\n# 4.1 Set SPIS dirs\n$ export SPIS_MEDIA_DIR=/storage/spis/media\n$ export SPIS_DATA_DIR=/storage/spis/data\n\n# 4.2 Create spis dirs\n$ mkdir -p ${SPIS_MEDIA_DIR} ${SPIS_DATA_DIR}\n\n# 4.3 Make sure user `www-data` owns dirs\n$ chown www-data:www-data ${SPIS_MEDIA_DIR} ${SPIS_DATA_DIR}\n\n# 4.4 Configure systemd to run spis\n$ sudo spis --server-socket ${SPIS_DATA_DIR}/spis.sock \\\n    template systemd --bin $(which spis) --user www-data \u003e /etc/systemd/system/spis.service\n\n# 4.5 Enable and start spis service\n$ systemctl enable --now spis\n\n# 5.1 Configure nginx\n$ spis\n    --server-socket /storage/spis/data/spis.sock \\\n    template nginx --port 8080 \u003e /etc/nginx/sites-available/default\n\n# 4.5 Enable and start nginx service\n$ systemctl enable --now nginx\n```\n\nNow `spis` will process and serve any image/video that you place in `/storage/spis/media`. Just make sure the files are owned by the `www-data` user.\n\nOpen up `spis` on http://yourserver:8080\n\n## Configuration templating\n\nYou can use `spis` to render configuration for various components. In fact, the\n[examples](./examples) in this repository are all created this way.\n```console\n$ spis template --help\nRender configuration templates\n\nUsage: spis template \u003cCOMMAND\u003e\n\nCommands:\n  nginx           Template nginx configuration\n  systemd         Template systemd configuration\n  docker-compose  Template docker compose configuration\n  help            Print this message or the help of the given subcommand(s)\n\nOptions:\n  -h, --help  Print help\n```\n\n## Progressive Web App\n\nYou can add `spis` as a `PWA` to your desktop or mobile. Open up the `spis` home page in a browser on the device, open the top-right menu, and select `Add to Home screen`, `Install` or something to that extent.\n\n## Changelog\n\nYou can take a look at the [CHANGELOG](/CHANGELOG.md) for version information and release notes.\n\n## Development\n\nI use [direnv](https://direnv.net/) to setup the development environment and `make` to run\neverything.\n\n### Setup toolchain\n\n```console\n# Setup rust toolchain\n$ make toolchain\n\n# You need nginx installed on your system\n$ sudo apt install nginx\n```\n\n### Get some test media\n\nI leave it up do you to put some images/videos in the `./data/media` folder.\n\n### Running\n\nRun stack with:\n\n```bash\n# If you don't have direnv installed load the env vars manually!\n. .envrc\nmake dev\n```\n\nAnd then open http://localhost:8080 in your browser\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgbbirkisson%2Fspis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgbbirkisson%2Fspis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgbbirkisson%2Fspis/lists"}