Stash on PMSS
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-headerremoves 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-Prefixandmap-urlpathtogether serve Stash under a path. Stash has no base-URL setting; it builds its links from this header, andmap-urlpathstrips the path before the request reaches it.- The
url.redirectline is needed on PMSS. Stash puts the path on its own redirect to the login page, andmap-urlpaththen maps that redirect again, so the first visit lands on/user-USERNAME/stash/user-USERNAME/stash/loginand 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/datato/data, the folder that was tested. To have Stash read files where they already are, change the left side of-v ~/docker/stash/data:/datato that folder, then remove the container withdocker rm -f stashand 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-stoppedto thedocker runline 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
docker ps --filter name=stashshows the container as Up.https://SERVER/user-USERNAME/stash/shows Stash's login page. Log in withadminand the password fromcat ~/docker/stash/login.pw.- 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.

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: theurl.redirectline is missing from the fragment, or itsUSERNAMEis wrong. - The container stops at once and
docker logs stashsays "missing the following mandatory settings: generated": addgenerated: /generatedtoconfig.ymland 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 on127.0.0.1inside the container, where the published port does not reach. Sethost: 0.0.0.0inconfig.ymlandSTASH_HOST=0.0.0.0instash.env, thendocker rm -f stashand run step 3 again. - The page loads but scan progress never updates: add
"upgrade" => "enable"toproxy.headerin the fragment. - Videos do not play and the browser console shows a blocked
http://address: Stash builds absolute stream addresses and getshttpthrough the proxy. Addexternal_host: "https://SERVER"toconfig.yml, scheme and host only with no path (Stash adds the path fromX-Forwarded-Prefix), thendocker restart stash. This fix comes from a customer case on Stash 0.31.1 and was not part of the 24 October test. docker pullordocker runfails before Stash starts: see the troubleshooting section of Rootless DOCKER.
See also
- Self-Hosted Apps on PMSS — other applications for rootless Docker, with their path settings and login defaults
- Lighttpd Custom Configuration — proxy fragments, path stripping and the panel login
- Rootless DOCKER — running containers in your account
- Seedbox — Pulsed Media seedbox plans, each with rootless Docker included