When it breaks

The things that go wrong, and what to do about them.

Container running but app wedged, permission errors, port clashes, and the upgrade traps specific to these apps.

Check these first

Four checks, in this order. They resolve most problems before you have to think hard about any of them.

  1. Read the logs. docker compose logs -f servicename. The answer is in there far more often than people expect, and reading it takes less time than guessing.
  2. Check the container is not just pretending. docker compose ps showing Up only means the process started. A container can be running while the application inside it is wedged, waiting on a database that never came up.
  3. Check what it depends on. If an app needs PostgreSQL or Redis, look at those first. An app that cannot reach its database usually reports something vague about its own startup rather than naming the database.
  4. Check the URL variable. Half of all "it loads but nothing works" reports are an app that does not know its own address. APP_URL, PAPERLESS_URL, ROOT_URL, PUBLIC_URL, SERVER_URL. Different name in every app, same problem.
Restarting is not diagnosis. It fixes transient startup ordering and nothing else. If a restart fixes it twice, the real problem is that something starts before its dependency is ready, and the fix is a healthcheck rather than a habit.

Permissions

Permission errors are the most common first-run failure, and the error message rarely says "permissions".

What you usually see instead: the app starts and immediately exits, or it starts and reports that it cannot write to a directory, or it comes up with an empty database every restart.

The cause is a mismatch between the user inside the container and the owner of your bind-mount directory. Two fixes, depending on the image:

  • Images with PUID and PGID, which includes everything from LinuxServer: set them to your own IDs from id -u and id -g.
  • Images with a fixed internal user: change the directory to match. PsiTransfer is the clearest case on this site. Upstream requires its data volume to be owned by UID 1000, so chown -R 1000 ./data before the first start.
Create bind-mount directories yourself. If Docker creates a missing directory, it creates it owned by root, and the app then cannot write to it. Chibisafe documents this explicitly: make the database, uploads and logs directories before the first start.

Port clashes

Two services wanting the same host port will not start, and the error names the port rather than the app, which is why it reads as mysterious.

The clash you will hit is port 3000. On this site alone, Forgejo, Gogs, CryptPad, PairDrop, PsiTransfer, Zipline and OpenSign's client all default to it. Port 8080 is the second most crowded.

Find out what is holding a port with docker compose ps, or on the host with ss -tulpn | grep 3000.

Change the host side, not the container side. In "4000:8080" the left number is yours to pick freely. The right number is what the application listens on inside the container, and changing it usually requires also changing a configuration variable. The Generator assigns unique host ports so this does not come up.

Remember that other containers do not use your host port. They reach the service on its container port by service name. Publishing a port is for you, not for them, and a database needs no published port at all.

Upgrade traps

The generic advice is on the Guide: pin major versions, read release notes, take a backup. These are the traps specific to modules on this site.

ModuleWhat bites
Wiki.jsPulling latest moves you toward version 3, which upstream says is an unstable beta not for production. Pin :2
Dashyv3 moved config to /app/user-data and the port from 80 to 8080. v4 removed the build-and-start entrypoint
Ziplinev3 to v4 is a rewrite with no in-place upgrade. Variables were renamed: CORE_DATABASE_URL to DATABASE_URL, CORE_HOST to CORE_HOSTNAME
Huly0.6 to 0.7 needs upstream's dedicated migration steps, not a pull
HomarrThe v1 rewrite required manual reconfiguration
PdfDingThe GitHub repo is archived. Development moved to Codeberg
Automatic updaters are a poor fit for this stack. Watchtower and similar tools will happily pull a major version overnight and migrate a database you have no backup of. Every entry in the table above is something an unattended update would have done to you without asking.

Getting your data back out

Every module on this site has a way out. Knowing which one, before you need it, is the difference between switching tools and being stuck.

Paperless-ngx
Document export, which writes originals plus metadata to the export volume
Docspell
Documents remain in its storage, with a documented API for bulk retrieval
BookStack and Wiki.js
Content export. Wiki.js can also keep everything mirrored to Git continuously
Forgejo and Gogs
Git repositories are already portable. Clone them. Issues need the API
Wekan
Board export as JSON, attachments included
PdfDing
The PDFs are files on disk in your media directory
Kiwix
The ZIM files are yours already
CryptPad
Export per document. The server cannot do it for you, by design
CryptPad is the one to think about early. Its encryption means the server genuinely cannot recover your documents if you lose access to them. That is the feature. It also means there is no administrator who can help, including you.

Test one export while everything is working. An export path you have never run is a claim, not a plan.