Kapowarr on PMSS
Kapowarr keeps your comic library at your own Pulsed Media seedbox address, behind its own login, in about 500 MiB of memory. You add a volume, Kapowarr fills in its details from ComicVine, looks for downloads of its issues from the sources you configure, and keeps checking for missing ones. It runs in rootless Docker inside your account. Tested on one Pulsed Media seedbox on 24 October 2026 with mrcas/kapowarr, the image published as latest that day and pinned below by its digest. The parts that were not tested are marked where they appear.
Privacy
Level: MEDIUM. Using Kapowarr reduces your privacy. What leaves your account:
- Kapowarr's web pages link Google Fonts (
fonts.googleapis.com,fonts.gstatic.com) and load a script fromcdn.socket.io. Each time you open Kapowarr, your browser contacts those servers, so they see your IP address and when you use it. In the test, Kapowarr's start page linked all three. - Searches and volume details go to ComicVine with your ComicVine API key, so ComicVine sees what you look up (Kapowarr documentation: "Kapowarr uses ComicVine as its metadata source").
- The download sources and clients you add see what you download, and the sources see the server's address.
Requirements
| Need | Kapowarr on PMSS |
|---|---|
| Memory | 437 to 504 MiB measured in the test. During its start and restarts it briefly used several CPU cores (up to 650% on the busy test server). |
| Disk | The image is 267 MB. Docker keeps it in your home folder, and comics stored under ~/docker/kapowarr/comics are files in your account, so both count toward your disk quota.
|
| Own login | Yes. Set in step 3, while no port is published. |
| Address | https://SERVER/user-USERNAME/kapowarr/ through your own web server. Kapowarr's own URL base setting carries the path, so the proxy fragment needs no map-urlpath.
|
| Other apps | A ComicVine API key for searching, and a download source or torrent client to fetch anything. Connecting them was not part of the test. |
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. Until a password is set, Kapowarr answers a login with an empty password by handing out its API key, and with that key anyone can change its settings, the password included: in the test, a request with no login to /api/auth returned the key. So Kapowarr starts once with no port published, gets its password from inside the container, and only then gets its port. The test itself published the port first and set the password a few seconds later; the no-port order in steps 2 to 4 was not run in the test, and those steps say what was.
1. Make the folders and a random password. The password stays in a file only you can read and never appears on a command line.
mkdir -p ~/docker/kapowarr/db ~/docker/kapowarr/temp_downloads ~/docker/kapowarr/comics chmod 700 ~/docker/kapowarr openssl rand -base64 24 | tr -d '/+=\n' > ~/docker/kapowarr/login.pw chmod 600 ~/docker/kapowarr/login.pw
tr also removes the trailing newline, which would otherwise become part of the password.
2. Start it with no port published. The image is pinned to the digest that was tested. The password file is mounted read-only for step 3 only.
docker run -d --name kapowarr -e PUID=0 -e PGID=0 -e TZ=Etc/UTC \ -v ~/docker/kapowarr/db:/app/db \ -v ~/docker/kapowarr/temp_downloads:/app/temp_downloads \ -v ~/docker/kapowarr/comics:/comics \ -v ~/docker/kapowarr/login.pw:/login.pw:ro \ mrcas/kapowarr@sha256:cd3f004caff59636dde73b06df5b2f8068b485b47ff5fe82f6129562c0f2ac3b docker logs kapowarr 2>&1 | tail -2
Repeat the last command until it shows Kapowarr running on http://0.0.0.0:5656. In the test that line came 4 seconds after Kapowarr logged its start. The test ran the same command with -p 127.0.0.1:PORT:5656 and without the password mount.
3. Set the login from inside the container. The script asks Kapowarr for its API key the way an outsider could, sets your login with it, and then checks that an empty password no longer works.
docker exec -i kapowarr python3 - <<'EOF'
import json, urllib.request, urllib.error
B = "http://127.0.0.1:5656/api"
def call(method, path, body):
req = urllib.request.Request(B + path, data=json.dumps(body).encode(), method=method,
headers={"Content-Type": "application/json"})
try:
with urllib.request.urlopen(req) as r:
return r.status, json.load(r)
except urllib.error.HTTPError as e:
return e.code, json.load(e)
pw = open("/login.pw").read().strip()
code, res = call("POST", "/auth", {"password": ""})
key = res["result"]["api_key"]
code, res = call("PUT", "/settings?api_key=" + key, {"auth_username": "USERNAME", "auth_password": pw})
print("set login:", code, res["error"])
code, res = call("POST", "/auth", {"password": ""})
print("empty password now:", code, res["error"])
EOF
It should print set login: 200 None and empty password now: 401 PasswordInvalid. This script was not run in the test. The requests it makes were: the empty-password login returned the key, the settings change with the key returned 200, and after it the empty password returned 401 PasswordInvalid.
4. Start it again with its port on 127.0.0.1. The login is stored in the database folder, so it survives the new container. Recreating the container was not part of the test.
docker rm -f kapowarr docker run -d --name kapowarr -e PUID=0 -e PGID=0 -e TZ=Etc/UTC \ -p 127.0.0.1:PORT:5656 \ -v ~/docker/kapowarr/db:/app/db \ -v ~/docker/kapowarr/temp_downloads:/app/temp_downloads \ -v ~/docker/kapowarr/comics:/comics \ mrcas/kapowarr@sha256:cd3f004caff59636dde73b06df5b2f8068b485b47ff5fe82f6129562c0f2ac3b
5. Move Kapowarr to its path, and confirm the move within 60 seconds. After a URL base change Kapowarr restarts and starts a 60-second timer. If no request carrying the API key reaches it at the new path in that time, it goes back to the old setting. Run this as one block:
B=http://127.0.0.1:PORT
N=$(docker logs kapowarr 2>&1 | grep -c 'Starting timer for hosting changes')
KEY=$(printf '{"username":"USERNAME","password":"%s"}' "$(cat ~/docker/kapowarr/login.pw)" | curl -s -X POST -H 'Content-Type: application/json' --data-binary @- $B/api/auth | grep -o '[0-9a-f]\{32\}')
printf 'url = "%s/api/settings?api_key=%s"\n' "$B" "$KEY" | curl -s -K - -o /dev/null -w 'url base change: %{http_code}\n' -X PUT -H 'Content-Type: application/json' -d '{"url_base":"/user-USERNAME/kapowarr"}'
for i in $(seq 1 60); do [ "$(docker logs kapowarr 2>&1 | grep -c 'Starting timer for hosting changes')" -gt "$N" ] && break; sleep 2; done
KEY=$(printf '{"username":"USERNAME","password":"%s"}' "$(cat ~/docker/kapowarr/login.pw)" | curl -s -X POST -H 'Content-Type: application/json' --data-binary @- $B/user-USERNAME/kapowarr/api/auth | grep -o '[0-9a-f]\{32\}')
printf 'url = "%s/user-USERNAME/kapowarr/api/volumes?api_key=%s"\n' "$B" "$KEY" | curl -s -K - -o /dev/null -w 'confirm: %{http_code}\n'
sleep 2; docker logs kapowarr 2>&1 | tail -1
unset KEY N
It prints url base change: 200, confirm: 200 and a last log line ending in Timer for hosting changes diffused. In the test the restart took about 25 seconds, and the same requests, run one by one, confirmed the change 13 seconds after the timer started. Two parts were added afterwards and were not run in the test: the for loop that waits for a new timer line, and passing the addresses that carry the API key to curl on its input (-K -) instead of its command line. The password and the API key never reach a command line, where other processes on the server could read them: printf is built into the shell.
6. Add the proxy fragment as ~/.lighttpd/custom.d/kapowarr.conf, with your USERNAME and PORT:
$HTTP["url"] =~ "^/user-USERNAME/kapowarr($|/)" {
auth.require = ()
setenv.set-request-header = ( "Authorization" => "" )
proxy.server = ( "" => ( (
"host" => "127.0.0.1",
"port" => PORT
) ) ),
proxy.header = (
"upgrade" => "enable"
)
}
auth.require = () lets requests past your panel login to Kapowarr's own login, which exists since step 3. The setenv.set-request-header line keeps your panel password from being sent to Kapowarr, which has its own login and does not need it. Without the line, a browser that has logged into your panel also sends that password to Kapowarr. The line was tested on 24 October 2026 with a small test app, not in the Kapowarr test; the rest of the fragment is the one that was tested. Check the syntax with /usr/sbin/lighttpd -t -f ~/.lighttpd.conf. Your web server picks up the file within about a minute; in the test the path answered after about 40 seconds (Lighttpd Custom Configuration).
Configure
- Open
https://SERVER/user-USERNAME/kapowarr/and log in with your account name and the password fromcat ~/docker/kapowarr/login.pw. In the test the login was checked through Kapowarr's API. The login form itself was not seen working in a browser (see Troubleshooting). - Kapowarr needs a ComicVine API key before it can fetch volume details (Kapowarr documentation). Read the Privacy section first.
- When you add a volume, choose
/comicsas its root folder. The install maps it to~/docker/kapowarr/comics. Keep the download folder at its default/app/temp_downloads; Kapowarr's documentation says to leave it there under Docker and that it must be outside your root folders. - Keep the login on. Every account on the server can reach the port on
127.0.0.1. - Adding
--restart unless-stoppedto the step 4 command brings Kapowarr back when your Docker daemon restarts, as described on Rootless DOCKER. The test ran without it.
Integrate
Kapowarr fetches from the indexers and download clients you add in its settings; connecting any of them was not part of the test. Its documentation says that adding at least one torrent client lets it download torrents. It also says that when Kapowarr runs in Docker, localhost in a client's address is the container itself, not the server, so a client running on the server is not reached at localhost. Kapowarr also has a FlareSolverr setting (flaresolverr_base_url in its settings); pointing it at Byparr was not tested.
Test
docker ps --filter name=kapowarrshows the container as Up.https://SERVER/user-USERNAME/kapowarr/leads to Kapowarr's login page.- 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/user-USERNAME/kapowarr/api/volumes
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:PORT/user-USERNAME/kapowarr/api/settings
curl -s -X POST -H 'Content-Type: application/json' -d '{"password":""}' http://127.0.0.1:PORT/user-USERNAME/kapowarr/api/auth; echo
In the test the first two returned 401 and the empty-password login returned {"error":"PasswordInvalid","result":{}}, also with 401. Through the panel address the API paths returned 401 as well, while the login page and its stylesheet returned 200, and /user-USERNAME/ itself still asked for the panel login.
Uninstall
docker rm -f kapowarr rm ~/.lighttpd/custom.d/kapowarr.conf touch ~/.lighttpd/custom
Deleting a fragment changes no remaining file, so touch ~/.lighttpd/custom makes your web server restart without it within about a minute (Lighttpd Custom Configuration explains why). docker rmi mrcas/kapowarr@sha256:cd3f004caff59636dde73b06df5b2f8068b485b47ff5fe82f6129562c0f2ac3b frees the image's 267 MB.
Deleting the data is your choice. rm -rf ~/docker/kapowarr deletes the password file, Kapowarr's database with its settings and library, unfinished downloads in temp_downloads, and every comic stored in ~/docker/kapowarr/comics.
Troubleshooting
- The panel address gives a 500 error and
docker logs kapowarrshows "Timer for hosting changes expired": no request with the API key reached the new path within 60 seconds, so the URL base went back to empty. That happened in the test. Run step 5 again. - The login page shows only "Please log in", with no fields: in the test a headless browser showed exactly that. The form is in the page but hidden until the page's scripts have run; why it stayed hidden there was not found. Logging in through the API worked.
- Step 2's log shows nothing yet, or a
curlto the port prints000: Kapowarr is still starting or restarting. In the test a restart took 15 to 25 seconds. docker pullordocker runfails before Kapowarr 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
- Byparr on PMSS — a FlareSolverr-compatible helper for Prowlarr
- Lighttpd Custom Configuration — proxy fragments and the panel login
- Rootless DOCKER — running containers in your account
- Seedbox — Pulsed Media seedbox plans, each with rootless Docker included