Repository navigation
Troubleshooting
Start with evidence, protect persistent data, and change one variable at a time. Most Nooklet failures fall into one of five boundaries: container configuration, database/worker health, container-visible storage, outbound service policy, or account access.
Caution
Do not run docker compose down -v, delete the SQLite database, remove WAL/SHM files while Nooklet is running, or reset the Git working tree as a troubleshooting shortcut.
From the deployment directory:
docker compose config --quiet
docker compose ps
docker compose logs --tail=200 app
docker compose logs --tail=200 youtube-pot-providerThe app waits for youtube-pot-provider to become healthy. If the app never starts and its log is empty, inspect the provider log first.
Then query the host-published probe (change 42021 if needed):
curl --fail-with-body http://127.0.0.1:42021/api/healthOn Windows PowerShell:
Invoke-RestMethod http://127.0.0.1:42021/api/health | ConvertTo-Json -Depth 5Use Health and diagnostics to interpret ok, HTTP-200 degraded, and HTTP-503 results.
| Symptom | Most likely boundary | Start here |
|---|---|---|
| Container exits immediately | Invalid/missing environment value or startup error | Validate Compose quietly, then inspect the first startup error in logs. |
Container is unhealthy
|
Database failed or worker stopped/stale | Query /api/health; preserve the volume and inspect health_*, worker_*, and supervisor events. |
Probe is 200 but degraded
|
Recent worker workload failure or degraded built-in engine | Read /health and logs; follow the named integration, storage, or engine recovery detail. |
| Browser cannot connect | Wrong bind address/port, stopped container, or firewall | Compare .env, docker compose ps, and docker compose port app 42021. |
Proxy returns 502
|
Wrong upstream or Nooklet is unavailable | Query the probe directly from the proxy host/network. |
Sign-in redirects to localhost
|
Incorrect APP_URL or stale container environment |
Set the canonical browser origin and force-recreate. |
| “Not enough free disk space” despite a spacious media drive | Engine work or completed-output staging is on a constrained filesystem | Inspect both locations in Settings > Storage and move or expand the constrained one. |
Host path such as F:\Movies is not found |
A Docker install needs container-side paths | Bind the host folder, then configure the right-hand container path such as /media/movies. |
| Library path is outside approved roots | Path and APPROVED_MEDIA_ROOTS disagree |
Approve the container root, recreate, then register a contained directory. |
| Storage is reachable but read-only | Host permissions or mount mode deny the unprivileged container user | Correct host permissions/mount flags; do not make the container privileged. |
| Private service host is blocked | SSRF policy denied a LAN/loopback target | Add the exact hostname/IP to PRIVATE_SERVICE_HOST_ALLOWLIST and recreate. |
| Service test rejects a redirect | Outbound redirects are intentionally refused | Configure the final direct endpoint URL. |
| Built-in download finishes but will not import | Worker, completed-output staging, or destination is unavailable | Inspect Activity and /health; verify DOWNLOAD_ENGINE_DIR, the selected active library path, and both mounts' permissions. |
| YouTube search or initialization fails | yt-dlp/tool readiness, outbound YouTube access, or unsupported/private URL | Paste a recognized public URL, then inspect authenticated health for tool versions and source-sync failure detail. |
| YouTube transfer cannot merge or publish | ffmpeg, YOUTUBE_WORK_DIR, destination mapping, or containment failure |
Verify FFMPEG_PATH, work/destination write access, the active YouTube path, and that no destination component became a symlink. |
| YouTube transfer is active but makes no progress | Extractor/network stall or blocked work filesystem | Inspect Activity and the authenticated runner heartbeat; a genuinely stalled active transfer degrades health and retains durable retry state. |
| Environment/mount edit has no effect | Container was restarted, not recreated | Run docker compose up -d --build --force-recreate. |
| “Container name is already in use” | Another Nooklet project uses the base fixed name | Assign unique project/container/image names; see Multi-instance deployments. |
Docker reports OCI runtime exec failed, setns, or a defunct container |
Stale Docker runtime/container namespace rather than an application error | Preserve the mount inventory, remove only the identified service container without -v, restart the Docker engine if required, and recreate. |
| Sign-in is temporarily rejected after repeated attempts | Five-minute rate-limit window or a recorded temporary lock | Stop retrying and allow the window/lock to expire; inspect logs and account state if it persists. |
| Account is disabled | Administrative access state, not a timeout | A different active administrator must re-enable it. Waiting and password recovery do not re-enable disabled accounts. |
| Administrator forgot the password | Lost credential on an otherwise active account | Use the local recovery script against a verified, backed-up database. |
| Upgrade starts but health fails | Migration/runtime error or mismatched configuration | Preserve logs and the pre-upgrade backup; fix forward or restore code+database as a pair. |
docker compose config --quietAvoid posting the full output of docker compose config; it can contain expanded secrets from .env.
docker compose logs --tail=300 app
docker compose logs --tail=300 youtube-pot-providerCommon environment failures include:
- missing
AUTH_SECRET; - a secret shorter than 32 characters;
- a known placeholder such as
change-me; - malformed
APP_URL; - invalid private-host allowlist entries containing a scheme, port, path, CIDR, or wildcard; and
- a non-positive/non-integer AI timeout.
Correct .env, then recreate:
docker compose up -d --build --force-recreatedocker compose ps
docker ps -a --filter name=nookletIf the host port is allocated, choose another APP_PORT and update APP_URL. If another independent Nooklet uses the container/project name, follow Multi-instance deployments; do not delete an unidentified container or volume.
The response body distinguishes the primary boundary:
-
checks.database: "error"means SQLite initialization/migration/readiness failed. -
checks.database: "ok"withchecks.backgroundWorker: "error"means the worker has not ticked within 60 seconds.
For database failure:
- preserve logs;
- confirm the data volume exists;
- ensure only one app process uses the database;
- avoid manual SQLite changes; and
- create a backup if the database remains readable, or restore the last verified backup.
For a stale worker:
- inspect
worker_*,download_engine_*, and supervisor events; - look for a blocked event loop, repeated database contention, or startup failure;
- confirm the container has adequate CPU/memory; and
- recreate only after correcting configuration or resource pressure.
An HTTP-200 degraded response is different: the worker is responsive and Docker intentionally treats it as healthy. Fix the recorded workload failure instead of forcing restarts.
The built-in downloader checks both the in-flight filesystem containing DOWNLOAD_ENGINE_WORK_DIR and the completed-output filesystem containing DOWNLOAD_ENGINE_DIR, not the final movie/TV library drive. It keeps headroom for active data, assembly/unpacking, and a safety reserve; the more constrained engine filesystem limits admission.
For a new NZB, required free space is:
512 MiB + sum(total bytes + remaining bytes for active built-in downloads) + (2 x the NZB's declared bytes)
That means a 10 GiB release with no active downloads needs about 20.5 GiB free on both engine filesystems even when the final media destination has much more space.
- Open Settings > Storage.
- Read the engine work and completed-output effective paths, free space, active reservation, and maximum new download size.
- Remember that Docker displays paths inside the container.
- Compare both locations with
docker compose.yml,.env, and your override.
In .env:
# Normally keep scratch work on Docker's Linux-native data volume.
DOWNLOAD_ENGINE_WORK_DIR=/app/data/engine-work
DOWNLOAD_ENGINE_DIR=/downloads/nooklet-engineIn the gitignored docker-compose.override.yml on Windows:
services:
app:
volumes:
- "F:/Nooklet/Downloads:/downloads"On macOS/Linux:
services:
app:
volumes:
- /mnt/fast-downloads/nooklet:/downloadsThen recreate and re-check Settings > Storage:
docker compose up -d --build --force-recreateIf DOWNLOAD_ENGINE_WORK_DIR is the constrained location, expand Docker's data storage or move it to a suitable fast filesystem. Avoid a slow Docker Desktop host bind for high-concurrency scratch I/O. Deleting the 512 MiB reserve or pointing either engine location directly at the final library is not the fix.
Docker has two names for every bind mount:
host path container path used by Nooklet
F:/Media/Movies -> /media/movies
/mnt/storage/downloads -> /downloads
Only the right-hand path belongs in Nooklet settings and path-valued environment variables such as DOWNLOAD_ENGINE_DIR or DOWNLOAD_ENGINE_WORK_DIR.
- Bind-mount the host folder into the app service.
- Add a containing container path to
APPROVED_MEDIA_ROOTS, for example/media. - Recreate the container.
- Register
/media/moviesor/media/tvin Settings > Storage. - Confirm the UI reports reachable, readable, and writable.
Inspect what the unprivileged runtime sees:
docker compose exec app sh -lc 'id; ls -ld /app/data /downloads /media /media/movies /media/tv'It is normal for an unused example path to be absent. Correct ownership, ACLs, Docker Desktop file-sharing permissions, or a :ro mount on the host side. Do not switch the container to root as a general permission workaround.
Nooklet rejects filesystem roots, direct Windows network/device path forms, out-of-bound paths, and symlinked files for destructive media operations. Mount a remote share on the Docker host first, then bind only its intended subdirectory into the container.
Nooklet blocks private/loopback outbound destinations by default.
-
Use the final direct HTTP(S) URL; redirects are refused.
-
Put only its exact hostname or IP in
.env:PRIVATE_SERVICE_HOST_ALLOWLIST=plex.local;tautulli.local;192.168.1.25 ALLOW_PRIVATE_SERVICE_HOSTS=false
-
Recreate the container.
-
Use the same host spelling in Settings > Connections.
-
Test the connection in the UI and inspect logs.
If a hostname resolves to both public and disallowed addresses, validation fails. Link-local/metadata-like, CGNAT, multicast, documentation, and other special-purpose ranges remain blocked even when broad private-host access is enabled.
For Docker Desktop services on the host, an exact host.docker.internal allowlist may be appropriate if that is the hostname used in the URL. On Linux, define an explicit Docker network or controlled host-gateway mapping instead of assuming that hostname exists.
The worker imports only finalized output recorded under the engine item's DOWNLOAD_ENGINE_DIR and only into an active library destination inside APPROVED_MEDIA_ROOTS.
- Open Activity and confirm the item reached completed/finalized state rather than failed repair or extraction.
- Open
/healthand inspect the authenticated worker/import error. - In Settings > Storage, confirm the completed-output staging path and selected library destination are fresh, reachable, and writable.
- Verify the host directories are mounted at the exact container paths configured in
DOWNLOAD_ENGINE_DIRand the library path. - Recreate the container after an environment or mount change, then retry the import.
An import-triggered library scan is limited to the affected active destination path. If files were moved outside Nooklet, run a manual library scan after restoring the configured path.
- Confirm the email spelling; Nooklet normalizes account emails to lowercase.
- If attempts were rejected in a burst, stop retrying and wait for the five-minute rate-limit window to clear. A time-bounded login lock/rate limit expires; it does not require an account status change.
- Ask another administrator to inspect
/admin. A disabled account never becomes active by waiting and must be explicitly re-enabled. - If an active administrator forgot the password, create a verified backup and follow Account and user administration.
The local recovery script changes the password, forces a first-sign-in replacement, and clears recorded login failures/locked_until; it does not clear is_disabled. If every administrator is disabled, recovery of a password alone is insufficient. Preserve the database and restore a known-good backup or obtain an explicit, audited database-level recovery procedure.
If sign-in redirects to /settings/account?reason=temporary-password, this is expected: replace the administrator-issued or recovery password before using the rest of the app.
If /bootstrap is unavailable, an administrator may already exist; after successful bootstrap, Nooklet closes the route automatically and refuses later attempts. If the database genuinely has no administrator and the runtime BOOTSTRAP_TOKEN is absent, restore or generate a new independent token in .env, recreate the Docker container or restart the native process, and complete bootstrap. Confirm that /api/health is healthy. Removing the token and recreating/restarting again afterward is optional defense-in-depth, not required for normal operation.
Errors such as OCI runtime exec failed, setns, “container is not running,” or an unresponsive docker exec can come from stale Docker Desktop/containerd namespace state. They do not, by themselves, indicate that the Nooklet database volume is corrupt.
docker compose ps -a
docker compose ps -q app
docker volume ls --filter name=nookletIf docker inspect still works, record the identified app container's state and mounts before removal:
docker inspect <app-container-id-or-name> --format '{{json .State}}'
docker inspect <app-container-id-or-name> --format '{{json .Mounts}}'Confirm that /app/data uses the expected named volume. Do not remove any volume and do not add -v to the commands below.
Try the Compose-scoped removal first:
docker compose rm -s -f appIf Docker cannot stop/remove it because the daemon/runtime namespace itself is stale:
-
Restart Docker Desktop from its UI, or restart the Docker daemon on Linux during an approved maintenance window. This affects other containers on the host.
-
Return to the same deployment directory.
-
Remove only the confirmed Nooklet app container, still without a volume flag:
docker compose rm -f app -
If Compose no longer tracks the defunct container, use the exact ID/name captured above:
docker rm -f <confirmed-app-container-id-or-name>
docker rm -f without -v removes the container object, not the named nooklet-data volume or host bind-mounted media.
docker compose up -d --build --force-recreate
docker compose ps
docker compose logs --tail=200 appThen query /api/health, sign in, and inspect Settings > Storage. If the recreated container still fails, continue from the new startup logs; do not repeatedly remove containers or escalate to volume deletion.
docker compose restart restarts the old container configuration. Apply .env, port, image, and volume changes with:
docker compose up -d --build --force-recreateVerify the actual state with docker compose ps, the storage UI, and the health probe. Avoid sharing full docker compose config output because it can expand secrets.
SQLite is configured for WAL, foreign keys, and a five-second busy timeout. Persistent SQLITE_BUSY usually indicates another process or instance sharing the same database, a stalled filesystem, or unusually long host I/O.
- Run one Nooklet app container/process per database volume.
- Do not place the SQLite database on an unreliable network filesystem.
- Preserve the exact logs and deployed revision.
- Back up before any repair.
- Let Nooklet apply migrations automatically; do not improvise manual schema changes.
- If a verified pre-failure backup exists, restore code and database together using Backup, restore, and upgrades.
If the backup tool reports that source quick_check failed, preserve the database, WAL/SHM sidecars, and logs before doing anything else. Restore a known-good backup for service recovery; perform forensic SQLite salvage only on copies.
- Stop repeated restart/rebuild attempts.
- Record the current revision and logs.
- Confirm whether the database migrated successfully.
- Prefer a corrected forward release when the database is healthy.
- For rollback, restore the pre-upgrade database and its matching code revision.
Checking out older code against a forward-migrated database is not a safe general rollback. See Backup, restore, and upgrades.
Recommendation work runs on the background worker. The default request ceiling is 30 minutes to accommodate local and reasoning models. Configure a positive integer in milliseconds, no greater than 24 hours, only when the provider should fail faster or needs more time:
AI_RECOMMENDATIONS_TIMEOUT_MS=1800000Recreate after editing. A timeout may leave the worker temporarily degraded; inspect the recommendation timeline and logs, then retry after confirming the provider is responsive.
Before opening an issue, collect and sanitize:
- Nooklet revision (
git rev-parse HEAD); - Docker/host-native runtime and operating system;
-
docker compose psoutput; -
/api/healthJSON; - a bounded relevant log excerpt;
- container-side storage paths and their status, without unrelated host paths;
- exact reproduction steps and time; and
- whether the problem began after an upgrade, mount change, key rotation, or service change.
Never attach .env, a database/backup, API keys, webhook URLs, credentials, NZB content, or unredacted full Compose configuration.
Nooklet repository · Features · User guide · Engineering dossier · Issues · Private security advisory
Documentation last reviewed against main: August 19, 2026.
Nooklet
Start here
- Getting started
- Guided Docker setup
- Docker installation
- Native installation (advanced)
- First-time setup
Configure
Use Nooklet
- Discover and recommendations
- Library and requests
- YouTube monitoring
- Downloads and import
- Watch history
- Automation and notifications
Operate
- Health and diagnostics
- Backup, restore, upgrades
- Accounts and users
- Troubleshooting
- Multi-instance boundaries
Engineering