Deployment guide

PiGallery2

Overview

PiGallery2 3.5.2 on Ubuntu 24.04 LTS is a ready-to-run image from SecureAnyCloud for Microsoft Azure. PiGallery2 is an open source, self-hosted photo gallery. It reads a folder of photos, keeps your folder structure, indexes the files into a small SQLite database, makes thumbnails and serves a web gallery with search, a map of geotagged photos, faces, albums and password-protected sharing links. Your photos stay on your own VM.

What you get:

  • HTTPS and a unique admin password from the first boot. PiGallery2 listens on 127.0.0.1 only. nginx serves it on port 443 with a certificate generated on your VM, port 80 redirects to HTTPS, and requests for a host name the VM does not serve get no answer. PiGallery2's own login is on. The admin password is generated on first boot and written to /root/pigallery2-credentials.txt (readable by root only), and PiGallery2 stores only a bcrypt hash of it. The upstream fallback login admin / admin is never created, and no secret is baked into the image.
  • The empty-password login fix. In PiGallery2 3.5.2, a login with a known user name and an empty password is accepted. The PiGallery2 developers fixed this in their code in May 2026, but no release contains the fix yet. This image applies that fix (upstream commit bf910bed3) to 3.5.2, plus a SecureAnyCloud guard that stops a malformed sign-in request from restarting PiGallery2. The image's self test checks that empty, blank, wrong-case and malformed passwords are refused.
  • A current runtime and refreshed dependencies. PiGallery2 runs on Node.js 22.23.4 LTS (the October 2026 security release), with image processing by sharp 0.35.5 (libvips 8.18). The libraries pinned by PiGallery2 3.5.2 carried known high and critical vulnerabilities, so they are updated here. The finished tree is scanned against the OSV vulnerability database, with no known advisories at build time. The build record is in /usr/share/doc/pigallery2/BUILD-PROVENANCE.txt.
  • Hardened defaults. PiGallery2 runs as its own unprivileged user under a locked-down systemd unit that never writes a core dump. PiGallery2 and nginx are blocked from the Azure instance metadata service and the Azure wire server. Client addresses come from the connection, not from headers a client can forge. Repeated sign-in attempts are rate limited, and a browser request that changes something and comes from another web site is refused. Extensions (third-party server code) and browser uploads are off until you turn them on. SSH refuses root logins.
  • A dedicated data disk. Your photos, the index, the thumbnails and PiGallery2's configuration live on their own 20 GiB data disk mounted at /var/lib/pigallery2, which you can grow on its own.
  • Photos only. The image ships without FFmpeg, so PiGallery2's video support is switched off. Video files in your photo folder are skipped (no video thumbnails, playback or conversion). See Step 5 if you need videos.
  • No sample content. The gallery starts empty and you add your own photos.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: PiGallery2 3.5.2 (/opt/pigallery2/app), Node.js 22.23.4 (/opt/pigallery2/node), nginx, Ubuntu 24.04 LTS, cloud-init and the Azure Linux Agent.

Recommended size: Standard_B2s or larger. Making thumbnails for a large library is CPU-heavy, and a bigger size indexes faster.

Prerequisites

  • An Azure subscription with permission to create virtual machines, and an SSH key pair (or a password) for the VM's administrator account.
  • A network security group that allows TCP 443 (the gallery) and TCP 80 (redirect to HTTPS) from the people who will use it, and TCP 22 (SSH, also how you copy photos in) from your own networks only.
1

Deploy from the Azure Portal

  • Find PiGallery2 on Ubuntu 24.04 LTS by SecureAnyCloud in the Azure Marketplace and select Create.
  • Choose your subscription, resource group, region and a size (Standard_B2s or larger).
  • Set the administrator user name and your SSH public key (or a password).
  • On the Disks tab, note the 20 GiB data disk that holds your photos. You can grow it after deployment (Step 12).
  • On the Networking tab, allow 443 and 80 from your users and 22 from your own networks only.
  • Select Review + create, then Create.
2

Deploy from the Azure CLI

Replace each value in angle brackets with your own:

az vm image terms accept --urn sac1647283583153:pigallery2-ubuntu-24-04:default:latest
az vm create --resource-group <your-resource-group> --name pigallery2 \
  --image sac1647283583153:pigallery2-ubuntu-24-04:default:latest \
  --size Standard_B2s --admin-username azureuser --ssh-key-values ~/.ssh/id_rsa.pub \
  --public-ip-sku Standard
az vm open-port --resource-group <your-resource-group> --name pigallery2 --port 443 --priority 1001
az vm open-port --resource-group <your-resource-group> --name pigallery2 --port 80 --priority 1002

Then connect over SSH as the administrator account you chose:

ssh azureuser@<public-ip>
3

Check the services

First boot takes a few seconds. After that, PiGallery2, nginx and the first-boot unit are active:

systemctl is-active pigallery2 nginx pigallery2-firstboot
active active active

nginx is the only web listener. PiGallery2 itself (a Node.js process) listens on 127.0.0.1:8080 only:

sudo ss -tlnp | awk 'NR==1 || /nginx|sshd|:8080 /' | awk '{print $1, $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g'
State Local Peer LISTEN 0.0.0.0:22 users:(("sshd"),("systemd")) LISTEN 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx")) LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx")) LISTEN 127.0.0.1:8080 users:(("node")) LISTEN [::]:22 users:(("sshd"),("systemd")) LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx")) LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx"))

Check the runtime, the PiGallery2 release and the data disk:

/opt/pigallery2/node/bin/node --version jq -r .version /opt/pigallery2/app/package.json findmnt -no TARGET,FSTYPE,SIZE /var/lib/pigallery2
v22.23.4 3.5.2 /var/lib/pigallery2 ext4 19.5G
4

Get your URL and password

sudo cat /root/pigallery2-credentials.txt

The file holds the address PiGallery2 is served on (your VM's public IP address by default) and the admin password generated on this VM:

# PiGallery2 3.5.2 - generated on first boot by pigallery2-firstboot.service. Unique to this VM; keep it safe. PIGALLERY2_URL=https://<public-ip>/ PIGALLERY2_USER=admin PIGALLERY2_PASS=<24 letters and digits>
# Sign in to the gallery with this user name and password. Only a bcrypt hash of the password is stored by PiGallery2.
# Add photos: copy them into /var/lib/pigallery2/images (azureuser can write there), then open the
# gallery (folders are indexed when you browse them; Settings -> Indexing rescans everything).
# Change the host name:
sudo pigallery2-set-host <dns-name>
# Change the password:
sudo pigallery2-set-password (prompts)
5

Add your photos

PiGallery2 reads photos from /var/lib/pigallery2/images, and its folders become albums in the gallery. On first boot, the VM's administrator account (the user name you chose at deployment) is added to the pigallery2 group, which can write to that folder. You can therefore copy photos straight in over SSH. From your own computer, for example:

rsync -av --progress ~/Pictures/Your-Photos/ azureuser@<public-ip>:/var/lib/pigallery2/images/

(scp -r and SFTP clients work too.) Keep the hidden file .sac-gallery-root in the folder. PiGallery2 will not index an empty photo folder, which protects its index if the storage is missing, and this file keeps a new gallery working. The file is never shown in the gallery.

PiGallery2 indexes a folder the first time someone opens it, and picks up new and removed files when the folder is opened again. To index the whole library at once (recommended after a large copy), sign in and open Settings from the menu at the top right. Switch the mode (top right) to Advanced, then choose Run now: Indexing in the Indexing section. Thumbnails are made as you browse, and the Photo section has a job that makes them ahead of time.

Check the photo folder and the members of the pigallery2 group (your administrator account):

ls -ld /var/lib/pigallery2/images getent group pigallery2
drwxrwsr-x 2 pigallery2 pigallery2 4096 Oct 23 09:23 /var/lib/pigallery2/images pigallery2:x:988:azureuser

Videos are not included. This image is for photos. It does not ship FFmpeg, the tool PiGallery2 needs to read video files. On Ubuntu 24.04, FFmpeg and the libraries it brings in receive security updates only through Ubuntu Pro (ESM Apps), which a Marketplace VM is not attached to. Video support is therefore off in PiGallery2's configuration, and video files you copy into the photo folder are not shown. If you need videos, you can add FFmpeg yourself and attach your own Ubuntu Pro subscription with sudo pro attach to receive its security updates. Then turn video support on and restart PiGallery2:

sudo apt-get install ffmpeg
sudo sh -c 'umask 077 && jq ".Media.Video.enabled = true" /var/lib/pigallery2/config/config.json > /root/pg2-config.json && install -o pigallery2 -g pigallery2 -m 0600 /root/pg2-config.json /var/lib/pigallery2/config/config.json && rm /root/pg2-config.json'
sudo systemctl restart pigallery2
6

Sign in

Open PIGALLERY2_URL in your browser. The certificate is self-signed (generated on your VM), so accept the browser warning once, or use your own name and certificate (Step 10). Sign in as admin with PIGALLERY2_PASS:

The gallery opens on your photo folder, with subfolders first and then the photos in it, grouped by date:

The images in these screenshots are generated patterns and fractals. The image itself ships with an empty gallery.

7

Browse, search and share

Open a folder to see its photos as a grid. The bar at the top shows where you are and how many items there are, and lets you download, filter, sort and group them:

Click a photo to open it full screen. The arrows move between photos, the play button starts a slideshow and the i button shows its details (file, size, date and, when the photo has them, camera, lens and location):

The search box finds photos by file name, folder, date, place, camera and keywords. Share creates a link to the current folder for people without a login, optionally with a password and an expiry. A share link uses your configured address (Step 10). Albums saves searches, Faces lists the people tagged in your photos' metadata, and the map shows geotagged photos. Your browser loads the map tiles from OpenStreetMap.

8

Settings

Signed in as admin, open Settings from the menu at the top right. Basic mode shows the main options. Advanced shows everything else, including the indexing and thumbnail jobs and the user list (Add user), where you can give other people their own logins and roles:

PiGallery2 keeps its settings in /var/lib/pigallery2/config/config.json. Only the pigallery2 user can read it, because it holds the session signing keys and the admin password hash. These are the settings that make up this image's security model:

sudo python3 -c 'import json; c=json.load(open("/var/lib/pigallery2/config/config.json")); print(json.dumps({"Server":{k:c["Server"][k] for k in ("host","port","publicUrl","trustProxy")},"Users":{"authenticationRequired":c["Users"]["authenticationRequired"]},"Media":{"Video":{"enabled":c["Media"]["Video"]["enabled"]}},"Extensions":{"enabled":c["Extensions"]["enabled"]},"Upload":{"enabled":c["Upload"]["enabled"]}},indent=2))'
{ "Server": { "host": "127.0.0.1", "port": 8080, "publicUrl": "https://<public-ip>", "trustProxy": "loopback" }, "Users": { "authenticationRequired": true }, "Media": { "Video": { "enabled": false } }, "Extensions": { "enabled": false }, "Upload": { "enabled": false } }

Keep Host 127.0.0.1, Port 8080, Trust Proxy loopback and Password protection on. PiGallery2 must be reachable only through nginx, and with password protection off, anyone who can reach the VM gets full admin rights. Three features are off on this image:

  • Upload lets signed-in users upload photos through the browser. nginx accepts uploads of up to 1 GiB per request.
  • Extensions download and run third-party server code from a repository on the internet. Extensions with dependencies need npm, which this image does not ship. Turn extensions on only for code you trust.
  • Video needs FFmpeg, which this image does not ship (see Step 5).
9

Check the security model

The image ships a self test that checks the login over HTTPS. It confirms that the gallery refuses you without signing in. It confirms that a wrong password, the right password in the wrong case, a blank password, an empty, null or missing password, and admin / admin are all refused and leave no session behind. It also confirms that the password from your credentials file is accepted and that PiGallery2 listens on 127.0.0.1 only:

sudo /usr/local/sbin/pigallery2-selftest
OK: https://<public-ip>/ refuses no session / wrong / wrong case / whitespace / admin:admin / empty / null / missing password, logs in with the per-VM password, app on 127.0.0.1:8080 only

These checks confirm that plain HTTP redirects to HTTPS and that the gallery's API refuses you until you sign in. They also confirm that a sign-in with an empty password is rejected, a sign-in posted from another web site is refused, and a request for a host name the VM does not serve gets no answer:

H=$(sudo sed -nE 's#^PIGALLERY2_URL=https://([^/]+)/.*#\1#p' /root/pigallery2-credentials.txt)
curl -s -o /dev/null -w 'plain HTTP: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'gallery API without signing in: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/pgapi/gallery/content/"
curl -sk -w '\n' --resolve "$H:443:127.0.0.1" -H 'Content-Type: application/json' -H "Origin: https://$H" --data '{"loginCredential":{"username":"admin","password":""}}' "https://$H/pgapi/user/login" | cut -c1-80
curl -sk -o /dev/null -w 'sign in posted from another web site: %{http_code}\n' --resolve "$H:443:127.0.0.1" -H 'Origin: https://evil.example' -H 'Content-Type: application/json' --data '{}' "https://$H/pgapi/user/login"
curl -s -o /dev/null -w 'unknown host name: %{http_code}\n' -H 'Host: evil.example' http://127.0.0.1/ || true
plain HTTP: 301 -> https://<public-ip>/ gallery API without signing in: 401 {"error":{"code":50,"message":"not all parameters are included for loginCredenti sign in posted from another web site: 403 unknown host name: 000

PiGallery2 and nginx cannot reach the Azure instance metadata service or the wire server, and PiGallery2 never writes a core dump. nginx passes PiGallery2 the configured host and overwrites the client address with the address of the connection. Secrets are readable by root only (or by PiGallery2, for its own configuration):

systemctl show pigallery2 nginx -p Id -p IPAddressDeny
systemctl show pigallery2 -p LimitCORE grep -E 'proxy_set_header (Host|X-Real-IP|X-Forwarded-For) ' /etc/nginx/pigallery2-proxy.conf
sudo stat -c '%a %U:%G %n' /root/pigallery2-credentials.txt /var/lib/pigallery2/config/config.json /etc/nginx/ssl/pigallery2.key
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 Id=pigallery2.service IPAddressDeny=168.63.129.16/32 169.254.169.254/32 Id=nginx.service LimitCORE=0 proxy_set_header Host $pg_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; 600 root:root /root/pigallery2-credentials.txt 600 pigallery2:pigallery2 /var/lib/pigallery2/config/config.json 600 root:root /etc/nginx/ssl/pigallery2.key

SSH refuses root logins, empty passwords and X11 forwarding. Whether SSH accepts passwords at all follows the choice you made at deployment:

sudo sshd -T | grep -E '^(permitrootlogin|x11forwarding|permitemptypasswords) '
permitrootlogin no x11forwarding no permitemptypasswords no

The certificate was generated on this VM and lists the VM's own addresses:

H=$(sudo sed -nE 's#^PIGALLERY2_URL=https://([^/]+)/.*#\1#p' /root/pigallery2-credentials.txt)
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = <public-ip>, O = sac X509v3 Subject Alternative Name: IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost
10

Your own DNS name and certificate

Point a DNS record at the VM, then set the name PiGallery2 is served on. The helper creates a new self-signed certificate for the name, updates nginx, PiGallery2's public URL (used in share links) and the URL in /root/pigallery2-credentials.txt, restarts PiGallery2 and keeps the name across reboots:

sudo pigallery2-set-host <your-domain>

To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx. Running pigallery2-set-host again replaces it with a self-signed certificate:

sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/pigallery2.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/pigallery2.key
sudo nginx -t && sudo systemctl reload nginx
11

Change the password

Run the helper on the VM. It prompts for the new password twice, so the password never appears on a command line. It stores the password as a bcrypt hash in PiGallery2's database and configuration, updates PIGALLERY2_PASS in /root/pigallery2-credentials.txt and restarts PiGallery2:

sudo pigallery2-set-password

The helper also resets a forgotten password. To give other people their own logins, use Add user under Settings (Advanced mode, Users). Each user signs in with their own password and role.

12

Storage, backups and server components

To grow the data disk, stop (deallocate) the VM and increase the disk's size in the portal or with az disk update --size-gb. Start the VM, then grow the file system (it is mounted by UUID with nofail):

sudo resize2fs "$(findmnt -no SOURCE /var/lib/pigallery2)"

Your originals are the photo folder. PiGallery2 rebuilds the index and thumbnails when they are missing, and your albums, shares and users live in the database. Back up /var/lib/pigallery2/images, /var/lib/pigallery2/db and /var/lib/pigallery2/config, or snapshot the data disk with Azure Backup or a disk snapshot.

Server components and where they live:

  • PiGallery2 3.5.2: /opt/pigallery2/app
  • Node.js 22.23.4 runtime: /opt/pigallery2/node/bin/node
  • Data disk: /var/lib/pigallery2 (20 GiB, ext4)
  • Your photos: /var/lib/pigallery2/images
  • Configuration: /var/lib/pigallery2/config/config.json (pigallery2, 0600)
  • Index database (SQLite): /var/lib/pigallery2/db
  • Thumbnails: /var/lib/pigallery2/tmp
  • systemd unit: pigallery2.service (user pigallery2, 127.0.0.1:8080)
  • nginx site: /etc/nginx/sites-available/sac-pigallery2
  • Certificate and key: /etc/nginx/ssl/
  • First-boot script: /usr/local/sbin/pigallery2-firstboot.sh
  • Host name helper: /usr/local/sbin/pigallery2-set-host
  • Password helper: /usr/local/sbin/pigallery2-set-password
  • Self test: /usr/local/sbin/pigallery2-selftest
  • Credentials: /root/pigallery2-credentials.txt (mode 0600)
  • Licences, build record and dependency scan: /usr/share/doc/pigallery2/
13

Security recommendations

  • Allow port 22 in the network security group from your own networks only, and open 80 and 443 only to the people who should see the gallery.
  • Keep the admin password private, and change it if it may have leaked (Step 11). Give other people their own users rather than the admin login.
  • Use your own DNS name and a certificate your users trust (Step 10), so nobody learns to click through certificate warnings.
  • Give share links a password and an expiry when they leave your organisation.
  • Keep the operating system patched. Unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else.

Support and licensing

PiGallery2 is free software under the MIT licence (Copyright bpatrik). A copy is at /usr/share/doc/pigallery2/LICENSE, and the Node.js licence is at /usr/share/doc/pigallery2/NODE-LICENSE. This image runs the PiGallery2 3.5.2 release from https://github.com/bpatrik/pigallery2 with the runtime, dependency and security changes listed in /usr/share/doc/pigallery2/BUILD-PROVENANCE.txt. There is no per-user or per-CPU licence fee. The SecureAnyCloud charge covers hardening, first-boot setup, image maintenance and 24/7 support.

  • Email: sales@secureanycloud.com
  • Website: https://secureanycloud.com
  • Support: 24/7, with a 24 hour response SLA

Deploy on Azure

Launch PiGallery2 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure