Node Docker containers don’t have permission to access their mounted volumes?

So I’m doing my absolute best to run a dockerized Node server app on OpenSUSE Leap 16.0, but I’m having mighty mysterious problems with permissions to access mounted volumes…

How to reproduce

Using the official instructions, install Docker:

sudo zypper install docker docker-compose
sudo systemctl enable docker
sudo usermod -G docker -a $USER
newgrp docker
sudo systemctl restart docker

Let’s make a test directory owned by the docker group while we’re at it:

mkdir /home/$USER/test

Then run any Node app with a mounted volume – here, we mount the user’s home directory to /test and run the Node REPL for testing:

docker run -it --name node -v /home/$USER/test:/test bitnami/node

In the Node REPL, execute the following:

const fs = require('fs');
const os = require('os');
os.userInfo();
fs.readdirSync('/test');

The output I get is as following:

> os.userInfo()
[Object: null prototype] {
  uid: 0,
  gid: 0,
  username: 'root',
  homedir: '/root',
  shell: '/bin/bash'
}
> fs.readdirSync('/test');
Uncaught Error: EACCES: permission denied, scandir '/test'
    at Object.readdirSync (node:fs:1569:26) {
  errno: -13,
  code: 'EACCES',
  syscall: 'scandir',
  path: '/test'
}

What this tells me is that despite Docker running as root (UID 0; GID 0), and despite the user’s home directory being successfully mounted to /test in the container, the Docker container for some reason does not have permission to access (even read!) the directory.

What’s going on here…? :face_with_spiral_eyes:


I’ve confirmed that the directory has the correct permissions set…
molly@localhost:~> ls -l
[…]
drwxr-xr-x. 1 molly docker    0 Dec 17 17:06 test

Output of sudo systemctl status docker, which may be relevant… There are some errors in there!
● docker.service - Docker Application Container Engine
     Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; preset: disabled)
     Active: active (running) since Wed 2025-12-17 16:59:12 CET; 1min 21s ago
 Invocation: d5140dc502cc4e429c683ec722e7d9b4
TriggeredBy: ● docker.socket
       Docs: http://docs.docker.com
   Main PID: 1134 (dockerd)
      Tasks: 23
        CPU: 783ms
     CGroup: /system.slice/docker.service
             ├─1134 /usr/bin/dockerd --add-runtime oci=/usr/sbin/runc
             └─1206 containerd --config /var/run/docker/containerd/containerd.toml

Dec 17 16:59:10 localhost.localdomain systemd[1]: Starting Docker Application Container Engine...
Dec 17 16:59:12 localhost.localdomain dockerd[1134]: time="2025-12-17T16:59:12.565114345+01:00" level=warning msg="Error (Unable to complete atomic operation, key modified) deleting object [endpoint_count 402d1a6df161950b04de2c13910196723084b16bcca3b6a4446472926a32f6f1], retrying...."
Dec 17 16:59:12 localhost.localdomain systemd[1]: Started Docker Application Container Engine.
Dec 17 16:59:55 localhost.localdomain dockerd[1206]: time="2025-12-17T16:59:55.765036230+01:00" level=warning msg="cleaning up after shim disconnected" id=9e7f6a62c936ef01b6d7b9b18f66dd0903136545cf18b4e1985e1b23b845ceb9 namespace=moby

My actual use case, in case someone has the same issue down the line and needs to google it…

I’m trying to run Audiobookshelf on an OpenSUSE Leap 16.0 server. My compose.yaml looks like this:

services:
  audiobookshelf:
    image: ghcr.io/advplyr/audiobookshelf:latest
    ports:
      - 13378:80
    volumes:
      - /run/media/red/Audio:/audio:ro
      - /run/media/red/.config/audiobookshelf/config:/config
      - /run/media/red/.config/audiobookshelf/metadata:/metadata
    environment:
      - TZ=Europe/Stockholm

And docker compose up gives me the following error output:

Attaching to audiobookshelf-1
audiobookshelf-1  | Running in production mode.
audiobookshelf-1  | Options: CONFIG_PATH=/config, METADATA_PATH=/metadata, PORT=80, HOST=undefined, SOURCE=docker, ROUTER_BASE_PATH=/audiobookshelf
audiobookshelf-1  | [2025-12-17 16:53:22.337] INFO: === Starting Server ===
audiobookshelf-1  | [2025-12-17 16:53:22.340] INFO: [Server] Init v2.31.0
audiobookshelf-1  | [2025-12-17 16:53:22.340] INFO: [Server] Node.js Version: v20.19.6
audiobookshelf-1  | [2025-12-17 16:53:22.341] INFO: [Server] Platform: linux
audiobookshelf-1  | [2025-12-17 16:53:22.341] INFO: [Server] Arch: x64
audiobookshelf-1  | [2025-12-17 16:53:22.345] ERROR: [PlaybackSessionManager] cleanOrphanStreams failed [Error: EACCES: permission denied, scandir '/metadata/streams'] {
audiobookshelf-1  |   errno: -13,
audiobookshelf-1  |   code: 'EACCES',
audiobookshelf-1  |   syscall: 'scandir',
audiobookshelf-1  |   path: '/metadata/streams'
audiobookshelf-1  | }
audiobookshelf-1  | [2025-12-17 16:53:22.351] INFO: [Database] Initializing db at "/config/absdatabase.sqlite"
audiobookshelf-1  | [2025-12-17 16:53:22.379] ERROR: [Database] Failed to connect to db ConnectionError [SequelizeConnectionError]: SQLITE_CANTOPEN: unable to open database file
audiobookshelf-1  |     at Database.<anonymous> (/app/node_modules/sequelize/lib/dialects/sqlite/connection-manager.js:52:25) {
audiobookshelf-1  |   parent: [Error: SQLITE_CANTOPEN: unable to open database file] {
audiobookshelf-1  |     errno: 14,
audiobookshelf-1  |     code: 'SQLITE_CANTOPEN'
audiobookshelf-1  |   },
audiobookshelf-1  |   original: [Error: SQLITE_CANTOPEN: unable to open database file] {
audiobookshelf-1  |     errno: 14,
audiobookshelf-1  |     code: 'SQLITE_CANTOPEN'
audiobookshelf-1  |   }
audiobookshelf-1  | }
audiobookshelf-1  | [2025-12-17 16:53:22.382] FATAL: [Server] Unhandled rejection: Error: Database connection failed
audiobookshelf-1  |     at Database.init (/app/server/Database.js:188:13)
audiobookshelf-1  |     at async Server.init (/app/server/Server.js:159:5)
audiobookshelf-1  |     at async Server.start (/app/server/Server.js:223:5)
audiobookshelf-1  | promise: Promise {
audiobookshelf-1  |   <rejected> Error: Database connection failed
audiobookshelf-1  |       at Database.init (/app/server/Database.js:188:13)
audiobookshelf-1  |       at async Server.init (/app/server/Server.js:159:5)
audiobookshelf-1  |       at async Server.start (/app/server/Server.js:223:5)
audiobookshelf-1  | }
audiobookshelf-1  | [LogManager] Appended crash log [Error: EACCES: permission denied, open '/metadata/logs/crash_logs.txt'] {
audiobookshelf-1  |   errno: -13,
audiobookshelf-1  |   code: 'EACCES',
audiobookshelf-1  |   syscall: 'open',
audiobookshelf-1  |   path: '/metadata/logs/crash_logs.txt'
audiobookshelf-1  | }
audiobookshelf-1 exited with code 1

No idea how you got your yml. Mine looks different.

Have you tried the following instead:

volumes:
- /run/media/red/Audio:/audio:ro
- /run/media/red/.config/audiobookshelf/config:/config
- /run/media/red/.config/audiobookshelf/metadata:/metadata

the following:

volumes:
-./audiobooks:/audiobooks
-./ebooks:/ebooks
-./podcasts:/podcasts
- ./config:/config
- ./metadata:/metadata

Likely SELinux.

You think so…? systemctl status docker shows this as the process:

             ├─1134 /usr/bin/dockerd --add-runtime oci=/usr/sbin/runc

So it’s not using the --selinux-enabled flag… Might still be, somehow, I suppose.

Looks like it is indeed SELinux getting in the way… I don’t know what the proper solution is, but this person on Reddit came up with a temporary fix. I’ve added this to my compose.yaml:

      security_opt:
        - label=disable

Then it works. Does this carry any notable security risks with it? Does anyone know what the “proper” solution would be?

Hello, can you try with:

security_opt:
      - label:type:container_runtime_t

Since they want to access a directory in $HOME that’s probably not going to work. Those would all have the user_home_t context.

Of course, if this is anything other than a test demo, docker volumes in $HOME isn’t the right way to do it.

In that case idk.

I always save my compose files and container data in /opt/apps/<container-name>

for the container data directory I put :Z at the end of the volume mountpoint.

Maybe listing the current labels give him an idea or the Selinux AVC’s

This is what you can do when it’s outside of $HOME. You can than have /opt/apps/* have the container_file_t context and the :Z lets docker relabel files and folders in that location to allow the needed context switches.

Relabelling $HOME is just going to break things, now or later.