Jump to content

Stash on PMSS

From Pulsed Media Wiki

Stash turns the video and image folders you point it at into a tagged, searchable library in your browser, served at your own Pulsed Media seedbox address behind its own login, in about 60 MiB of memory when idle. It runs in rootless Docker inside your account. Tested on one Pulsed Media seedbox on 24 October 2026 with stashapp/stash v0.31.1.

Privacy

Level: LOW. Stash needs no account outside Pulsed Media. Its database, your tags and the previews it generates stay in the folders under ~/docker/stash in your account. Outside contacts named in Stash's source and manual:

  • At every start Stash asks GitHub's API for its latest release (Stash 0.31.1 source, internal/api/server.go).
  • The lists of available scrapers and plugins in Settings come from the Stash project's pages on GitHub.
  • A metadata source you add, a scraper or a stash-box database, receives what you look up with it. With a stash-box you can also submit file fingerprints (hashes and duration); the Stash manual says those submissions are tied to your account on that stash-box.

Requirements

Need Stash on PMSS
Memory About 60 MiB idle, measured in the test. A scan or preview generation runs FFmpeg, which the image includes, and takes more CPU and memory from your account's limit while it runs; that was not measured.
Disk and file quota Generated previews and sprite images are files under ~/docker/stash/generated and count toward your disk and file quota.
Own login Yes, set in the config before the first start.
Address https://SERVER/user-USERNAME/stash/ through your own web server, with the proxy fragment below. No own hostname is needed.
Other apps None. Rootless Docker is part of every Pulsed Media seedbox plan.

Install

Replace USERNAME with your account name and PORT with a free port between 2000 and 38000 that ss -tln does not list. Other accounts on the same server can connect to 127.0.0.1, so the login is written into Stash's config before the container ever starts.

1. Make the folders and a random password. The password stays in a file only you can read.

mkdir -p ~/docker/stash/config ~/docker/stash/data ~/docker/stash/generated
chmod 700 ~/docker/stash
openssl rand -base64 24 > ~/docker/stash/login.pw
chmod 600 ~/docker/stash/login.pw

2. Write the config with the login in it. Stash takes a bcrypt hash of the password, written with the $2a$ prefix. The generated line is mandatory: without it Stash refuses to start. admin is the login name; any name works.

HASH=$(php -r 'echo str_replace("$2y$", "$2a$", password_hash(trim(file_get_contents($argv[1])), PASSWORD_BCRYPT));' ~/docker/stash/login.pw)
cat > ~/docker/stash/config/config.yml <<EOF
host: 0.0.0.0
port: 9999
username: admin
password: "$HASH"
generated: /generated
database: /root/.stash/stash-go.sqlite
stash: [{path: /data}]
EOF
chmod 600 ~/docker/stash/config/config.yml
printf 'STASH_HOST=0.0.0.0\nSTASH_PORT=9999\n' > ~/docker/stash/stash.env
chmod 600 ~/docker/stash/stash.env

3. Start the container on loopback only. The image is pinned to the digest that was tested.

docker run -d --name stash --env-file ~/docker/stash/stash.env \
  -p 127.0.0.1:PORT:9999 \
  -v ~/docker/stash/config:/root/.stash \
  -v ~/docker/stash/data:/data \
  -v ~/docker/stash/generated:/generated \
  stashapp/stash@sha256:df744af5a0c976e2ec671052ecc1f8a9aa757fa12b8f9930b59910b7295f0da6

4. Add the proxy fragment as ~/.lighttpd/custom.d/stash.conf, with your USERNAME and PORT in all places:

$HTTP["url"] =~ "^/user-USERNAME/stash/" {
  auth.require = ()
  setenv.set-request-header = ( "Authorization" => "" )
  url.redirect = ( "^/user-USERNAME/stash/user-USERNAME/stash/(.*)$" => "/user-USERNAME/stash/$1" )
  setenv.add-request-header = ( "X-Forwarded-Prefix" => "/user-USERNAME/stash" )
  proxy.server = ( "" => ( ( "host" => "127.0.0.1", "port" => PORT ) ) ),
  proxy.header = ( "upgrade" => "enable", "map-urlpath" => ( "/user-USERNAME/stash/" => "/", "/user-USERNAME/stash" => "" ) )
}

What each line does:

  • auth.require = () lets requests past your panel login to Stash's own login. Stash's login was set in step 2, before the port existed.
  • setenv.set-request-header removes your panel login, which a browser logged into your panel sends with every request, before the request reaches Stash. Stash's own login page does not need it.
  • X-Forwarded-Prefix and map-urlpath together serve Stash under a path. Stash has no base-URL setting; it builds its links from this header, and map-urlpath strips the path before the request reaches it.
  • The url.redirect line is needed on PMSS. Stash puts the path on its own redirect to the login page, and map-urlpath then maps that redirect again, so the first visit lands on /user-USERNAME/stash/user-USERNAME/stash/login and loops. The redirect rule sends the doubled path back to the single one.
  • "upgrade" => "enable" lets Stash's WebSocket connection through; scan progress and other live updates use it.

Check the syntax with /usr/sbin/lighttpd -t -f ~/.lighttpd.conf. Your web server picks up the file within about two minutes.

Configure

  • Stash scans the folders listed under stash: in its config. The install maps ~/docker/stash/data to /data, the folder that was tested. To have Stash read files where they already are, change the left side of -v ~/docker/stash/data:/data to that folder, then remove the container with docker rm -f stash and run step 3 again; the config and database stay in ~/docker/stash/config. That variant was not part of the test.
  • Settings → Tasks runs a scan of the library folders and generates previews. Generated files land in ~/docker/stash/generated (see Requirements).
  • Metadata sources are added under Settings → Metadata Providers. Read the Privacy section first.
  • Adding --restart unless-stopped to the docker run line brings Stash back when your Docker daemon restarts, as described on Rootless DOCKER. The test ran without it.
  • Keep the login on. Every account on the server can reach the port on 127.0.0.1.

Integrate

Stash reads files from folders. It does not connect to Sonarr, Radarr, Prowlarr or Jellyfin. To have it list files another application saves, map that application's folder into the container as in Configure; not tested.

Test

  1. docker ps --filter name=stash shows the container as Up.
  2. https://SERVER/user-USERNAME/stash/ shows Stash's login page. Log in with admin and the password from cat ~/docker/stash/login.pw.
  3. Requests without a login straight to the port are refused, which is what another account on the server sees:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:PORT/graphql
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:PORT/

In the test the first returned 401 and the second 302, a redirect to the login page.

Stash's login page at /user-USERNAME/stash/ in the 24 October 2026 test.

Uninstall

docker rm -f stash
rm ~/.lighttpd/custom.d/stash.conf
touch ~/.lighttpd/custom

Deleting a fragment changes no remaining file, so touch ~/.lighttpd/custom makes your web server restart without it within a minute or two (Lighttpd Custom Configuration explains why). docker rmi stashapp/stash@sha256:df744af5a0c976e2ec671052ecc1f8a9aa757fa12b8f9930b59910b7295f0da6 frees the image's disk space.

Deleting the data is your choice. rm -rf ~/docker/stash deletes the login file, the config, Stash's database with all your tags and metadata, the generated previews, and every file kept in ~/docker/stash/data. A folder you mapped from elsewhere is not touched.

Troubleshooting

  • The first visit loops, or the address shows /user-USERNAME/stash/user-USERNAME/stash/login: the url.redirect line is missing from the fragment, or its USERNAME is wrong.
  • The container stops at once and docker logs stash says "missing the following mandatory settings: generated": add generated: /generated to config.yml and start it again.
  • The panel address gives a 500 error and curl http://127.0.0.1:PORT/ returns an empty reply: Stash is listening on 127.0.0.1 inside the container, where the published port does not reach. Set host: 0.0.0.0 in config.yml and STASH_HOST=0.0.0.0 in stash.env, then docker rm -f stash and run step 3 again.
  • The page loads but scan progress never updates: add "upgrade" => "enable" to proxy.header in the fragment.
  • Videos do not play and the browser console shows a blocked http:// address: Stash builds absolute stream addresses and gets http through the proxy. Add external_host: "https://SERVER" to config.yml, scheme and host only with no path (Stash adds the path from X-Forwarded-Prefix), then docker restart stash. This fix comes from a customer case on Stash 0.31.1 and was not part of the 24 October test.
  • docker pull or docker run fails before Stash starts: see the troubleshooting section of Rootless DOCKER.

See also