Raspberry HomeGallery for self-hosted family photos

The Raspberry Pi in my local LAN runs a self-hosted, local photo gallery. I do not feel comfortable uploading family photos into the internet cloud since I do not want them to leak and become public.

In this blog post I want to share my experience with the open-source photo gallery HomeGallery suitable to host tens of thousands of photos on a Raspberry Pi for access with different screen-sizes (desktop computers and smartphone). I absolutely love it! The UI concept and the performance are great.

You find more information on the project homepage or you can just try out the demo site. Here I want to focus in the installation and the image/video imports.

Disclaimer: this gallery is a small open source project. Some developers spend their free-time to implement it and share it with the world. As for all software there are bugs or details missing in the documentation. If you find something which can be improved you can open an issue or, even better, fix it and create a pull-request.

Screenshot of the HomeGallery demo

Installation on my Laptop

According to the docs there are different ways to install and run the HomeGallery. I mixed two approaches (because it worked, you know): the tar-balls and a Docker image. The prebuilt binaries work perfectly fine for a quick check of the project but fail in the long run. They basically auto-extract the archive into some temporary directory and my OS removed some files while my image import was running. I avoided the issue by extracting the archive manually.

wget https://dl.home-gallery.org/dist/1.3.0/home-gallery-1.3.0-darwin-x64.tar.gz wget https://dl.home-gallery.org/dist/1.3.0/home-gallery-1.3.0-darwin-x64.tar.gz.sha256sum shasum -a 256 -c home-gallery-1.3.0-darwin-x64.tar.gz.sha256sum tar -xzf home-gallery-1.3.0-darwin-x64.tar.gz cd home-gallery node/bin/node gallery.js --help

Note that there are different archives for different distributions though they only differ in the Node.js-binaries. The gallery itself is written in JavaScript hence platform independent.


You can generate the default configuration and adjust it as needed. I tailored the one below for maximum privacy and easy deployment to the Raspberry Pi including adding new photos. You can generate a default config file with a built-in command.

# the --source option seems to be ignored # but we edit the config anyway node/bin/node gallery.js run init # now there is a gallery.config.yml

The initial configuration file contains a lot of comments, so take a look. My local configuration looks as follows. All non-default values are highlighted.

# # HomeGallery configuration file # # # Default configuration file is gallery.config.yml. JSON format is # also supported (gallery.config.json) # # This is a comment, starting with hash tag and space ('# ') # A default value starting with hash tag and variable ('#baseDir...') # # Directory value examples: # - /absolute/directory # - relative/directory/to/current/working/dir # - ~/directory/in/your/home # - ./relative/directory/to/configuration/file # # Variable replacements # baseDir: '~' # configDir: '{baseDir}/.config/home-gallery' # configDir is replaced to '~/.config/home-gallery' and than to '$HOME/.config/home-gallery' # # Variables are baseDir, configDir, configPrefix, cacheDir and dir in sources # # Variables baseDir, configDir, configPrefix and cacheDir are overwritten by environment # variables GALLERY_BASE_DIR, GALLERY_CONFIG_DIR, GALLERY_CONFIG_PREFIX, GALLERY_CACHE_DIR # # General # baseDir: '.' configDir: '{baseDir}/home-gallery-data/config' # file prefix for index, database and events #configPrefix: '' cacheDir: '{baseDir}/home-gallery-data/cache' # # Sources # # List of media source directories. These can be read only. # # All sources are used to build the gallery database. If you need # different databases or gallery instances use different gallery # configurations sources: - dir: '/Path/To/My/Local/Photos' #index: '{configDir}/{configPrefix}{basename(dir)}.idx' # excludes are using gitignore patterns #excludes: #- .DS_Store #- ._* #- '*.tmp' #- '*cache*' #excludeIfPresent: .galleryignore # excludeFromFile: '{configDir}/excludes' # If source directory/disk is offline/unmounted set it to true. # Offline sources require an index file. Previews and meta data # should be extracted first before marking a source offline #offline: false # Filename matcher for checksum recalculation # size-ctime-inode: this matcher should be used if possible, might # not work on windows # size-ctime: this matcher should be used if stable fs inodes are # not available and might not work for fuse shares # size: this matcher should be used if you know what you are doing #matcher: size-ctime-inode # # Extractor settings # extractor: apiServer: http://localhost:3001 # Preffered address language of geo code reverse lookups # geoAddressLanguage: [de, en-US] # for multiple languages #geoAddressLanguage: en # excludes are using gitignore patterns #excludes: [] # # Storage and files configuration # #storage: #dir: '{cacheDir}/storage' #database: #file: '{configDir}/{configPrefix}database.db' #events: #file: '{configDir}/{configPrefix}events.db' # # Server configuration # #server: #port: 3000 #host: '' # security configuration for https # key: '{configDir}/server.key' # cert: '{configDir}/server.crt' # Open browser when server starts # openBrowser: false logger: # Currently console and file loggers are available - type: console # Log level could be one of trace, debug, info, warn, error, fatal, silent level: info # File logger format is in newline delimited JSON. See http://ndjson.org - type: file # Log level could be one of trace, debug, info, warn, error, fatal, silent level: debug file: '{baseDir}/gallery.log'

In a nutshell this config places all data in the current working directory and does not transmit photos to any remote server which is not the case in the default configuration. Note that GPS data is transmitted to OpenStreetMap to translate them into addresses.

Initial photo import

Now you can add your photos by starting the local API server (first command) and starting the data import (second command). Among other things the import includes object detection, similarity analysis, thumbnail creation and GPS to address translation.

docker run -it --rm -p 3001:3000 xemle/home-gallery-api-server node/bin/node gallery.js run import --initial

The import of roughly 20k photos on my MacBook took less than 24h. The gallery data on disk consumes a bit less then 25% of the disk space consumed by my photos (mostly JPEGs).

Installation on Raspberry Pi

As mentioned in a prior article my local Raspberry Pi already hosts an add-eating DNS. This device comes in handy in several ways for the gallery: there is already a running physical server, I have a local DNS to register LAN-wide domains and it already hosts a web-server.

Now let's co-locate the HomeGallery with the DNS and install it on the Raspberry Pi. I just "recycled" the OS X tar-ball and swapped the Node.js binaries.

sudo su # install OS X version mkdir -p /opt/home-gallery cd /opt/home-gallery tar -xzf /home/pi/home-gallery-1.3.0-darwin-x64.tar.gz # replace NodeJS rm -rf node wget https://nodejs.org/dist/v14.0.0/node-v14.0.0-linux-armv7l.tar.xz tar -xf node-v14.0.0-linux-armv7l.tar.xz mv node-v14.0.0-linux-armv7l node # test if gallery starts node/bin/node gallery.js --help

Since directories are slightly different on the Raspberry we need a slightly different gallery.config.yml. This time I skip the commented lines.

baseDir: '/home/pi/home-gallery' configDir: '{baseDir}/home-gallery-data/config' cacheDir: '{baseDir}/home-gallery-data/cache' sources: - dir: '{baseDir}/Fotos' # this folder must exist ! extractor: apiServer: http://localhost:3001 logger: - type: console level: info - type: file level: info file: '{baseDir}/gallery.log'

Last thing to do is to upload the files and start the gallery. Note that starting a process with nohup has a lot of disadvantages over creating a service/daemon. The most important one: the gallery is not started at reboot which might be troublesome since I activated unattended upgrades. I still use nohup for now just to make it work for the first time. Please do not do this in a production environment!

# upload images # !!! mind the trailing slashes !!! rsync -av home-gallery-data/ pi@ # create empty data folder (required for some reason) mkdir /home/pi/home-gallery/Fotos # test if everything works node/bin/node gallery.js run server # terminate server again # start gallery in backgroud # (you can also create a service) nohup node/bin/node gallery.js run server > /var/log/home-gallery.log & exit

Now you can already access the gallery via IP and port: but let's register a domain at our DNS and use port 80.

Domain and virtual host

Thankfully the Pi-hole comes with a Web-UI hence already includes a web server named lighttpd. The Pi-hole developers made extending the lighttpd config possible such that Pi-hole updates to not revert the additional config (thank you so much for that!).

The web server support reverse-proxies (only for HTTP backends) and I found this snippet to start with. We need to add the following config.

server.modules += ( "mod_proxy" ) $HTTP["host"] =~ ".*fotos\.lan$" { proxy.server = ( "" => (( "host" => "" , "port" => 3000 ))) proxy.header = ( "map-host-request" => ( "-" => "fotos.lan"), "map-host-response" => ("-" => "-")) }

If we make this the external config of the Pi-Hole setup, we are good to go. Just remember to register the domain fotos.lan in the Pi-hole web UI under Local DNS/DNS Records.

# insert config above nano /etc/lighttpd/external.conf # test config lighttpd -t -f /etc/lighttpd/lighttpd.conf # apply config /etc/init.d/lighttpd reload

Enable TLD .lan in Firefox

Too late I realized that Firefox starts a web-search when I enter fotos.lan instead of opening the gallery. You can add .lan to the list of Top Level Domains in Firefox to fix this behavior. Just open about:config and set a new config entry browser.fixup.domainsuffixwhitelist.lan to true.

Adding new photos

As time passes by new photos become available, of course. You can add them to the gallery with the following commands. Only new files are processed.

docker run -it --rm -p 3001:3000 xemle/home-gallery-api-server node/bin/node gallery.js run import --update # !!! mind the trailing slashes !!! rsync -av home-gallery-data/ pi@

Known Issues

As mentioned the gallery is a hobby project. Of course not everything is perfect. I still like it despite its rough edges. Some issues occurred though. If I find a solution I will add it here to this article. If you known a solution, please contact me.

  • Some photos are indexed without object meta-data which excludes them from the similarity search.
  • The executable bundles do not work, since a temporary directory is removed while the gallery is running.

How to Contribute

By using it: you can try it out yourself and tell others about it. This already helps the project. You can also report bugs.

By coding: feel free to fix some issues, update dependencies or implement something. The code is on GitHub and you can see the internals in docs. You may as well extend or fix the documentation.

By donation: This is from the original documentation: Do you like HomeGallery? Does it solve your media problem? Please support this project through any recurring support to my patreon.com/xemle or one time support to my paypal.me/xemle account. Thank you in advance.

Thank's for reading this far. As always feedback is highly welcome. Please feel free to contact me.