[Bug]: Unable to start container with non-root user #2880

Open
opened 2026-04-25 00:11:29 +02:00 by adam · 13 comments
Owner

Originally created by @mill1000 on GitHub (Jul 10, 2025).

What happened?

When attempting to run audiobookshelf via Docker as a non-root user the container is unable to start due to EACCES: permission denied errors.

What did you expect to happen?

The container to start normally as a non-root user.

Steps to reproduce the issue

  1. Setup minimal docker compose example.
services:
  audiobookshelf:
    image: ghcr.io/advplyr/audiobookshelf:latest
    container_name: audiobookshelf
    restart: unless-stopped
    user: 1000:1000
    environment:
      - TZ=America/Denver
      - PORT=13378
    ports:
      - 13378:13378
    volumes:
      - ./audiobooks:/audiobooks
      - audiobookshelf-config:/config
      - audiobookshelf-metadata:/metadata

volumes:
  audiobookshelf-config:
  audiobookshelf-metadata:
  1. Bring container up: docker compose up
  2. Watch it fail

Audiobookshelf version

v2.25.1

How are you running audiobookshelf?

Docker

What OS is your Audiobookshelf server hosted from?

Linux

If the issue is being seen in the UI, what browsers are you seeing the problem on?

None

Logs

Attaching to audiobookshelf
audiobookshelf  | Running in production mode.
audiobookshelf  | Options: CONFIG_PATH=/config, METADATA_PATH=/metadata, PORT=13378, HOST=undefined, SOURCE=docker, ROUTER_BASE_PATH=/audiobookshelf
audiobookshelf  | [2025-07-09 21:31:10.777] INFO: === Starting Server ===
audiobookshelf  | [2025-07-09 21:31:10.780] INFO: [Server] Init v2.25.1
audiobookshelf  | [2025-07-09 21:31:10.780] INFO: [Server] Node.js Version: v20.19.2
audiobookshelf  | [2025-07-09 21:31:10.780] INFO: [Server] Platform: linux
audiobookshelf  | [2025-07-09 21:31:10.781] INFO: [Server] Arch: x64
audiobookshelf  | [2025-07-09 21:31:10.793] FATAL: [Server] Unhandled rejection: [Error: EACCES: permission denied, mkdir '/metadata/streams'] {
audiobookshelf  |   errno: -13,
audiobookshelf  |   code: 'EACCES',
audiobookshelf  |   syscall: 'mkdir',
audiobookshelf  |   path: '/metadata/streams'
audiobookshelf  | } 
audiobookshelf  | promise: Promise {
audiobookshelf  |   <rejected> [Error: EACCES: permission denied, mkdir '/metadata/streams'] {
audiobookshelf  |     errno: -13,
audiobookshelf  |     code: 'EACCES',
audiobookshelf  |     syscall: 'mkdir',
audiobookshelf  |     path: '/metadata/streams'
audiobookshelf  |   }
audiobookshelf  | }
audiobookshelf  | [2025-07-09 21:31:10.798] FATAL: [Server] Unhandled rejection: [Error: EACCES: permission denied, mkdir '/metadata/logs'] {
audiobookshelf  |   errno: -13,
audiobookshelf  |   code: 'EACCES',
audiobookshelf  |   syscall: 'mkdir',
audiobookshelf  |   path: '/metadata/logs'
audiobookshelf  | } 
audiobookshelf  | promise: Promise {
audiobookshelf  |   <rejected> [Error: EACCES: permission denied, mkdir '/metadata/logs'] {
audiobookshelf  |     errno: -13,
audiobookshelf  |     code: 'EACCES',
audiobookshelf  |     syscall: 'mkdir',
audiobookshelf  |     path: '/metadata/logs'
audiobookshelf  |   }
audiobookshelf  | }

Additional Notes

No response

Originally created by @mill1000 on GitHub (Jul 10, 2025). ### What happened? When attempting to run audiobookshelf via Docker as a non-root user the container is unable to start due to EACCES: permission denied errors. ### What did you expect to happen? The container to start normally as a non-root user. ### Steps to reproduce the issue 1. Setup minimal docker compose example. ```yaml services: audiobookshelf: image: ghcr.io/advplyr/audiobookshelf:latest container_name: audiobookshelf restart: unless-stopped user: 1000:1000 environment: - TZ=America/Denver - PORT=13378 ports: - 13378:13378 volumes: - ./audiobooks:/audiobooks - audiobookshelf-config:/config - audiobookshelf-metadata:/metadata volumes: audiobookshelf-config: audiobookshelf-metadata: ``` 2. Bring container up: `docker compose up` 3. Watch it fail ### Audiobookshelf version v2.25.1 ### How are you running audiobookshelf? Docker ### What OS is your Audiobookshelf server hosted from? Linux ### If the issue is being seen in the UI, what browsers are you seeing the problem on? None ### Logs ```shell Attaching to audiobookshelf audiobookshelf | Running in production mode. audiobookshelf | Options: CONFIG_PATH=/config, METADATA_PATH=/metadata, PORT=13378, HOST=undefined, SOURCE=docker, ROUTER_BASE_PATH=/audiobookshelf audiobookshelf | [2025-07-09 21:31:10.777] INFO: === Starting Server === audiobookshelf | [2025-07-09 21:31:10.780] INFO: [Server] Init v2.25.1 audiobookshelf | [2025-07-09 21:31:10.780] INFO: [Server] Node.js Version: v20.19.2 audiobookshelf | [2025-07-09 21:31:10.780] INFO: [Server] Platform: linux audiobookshelf | [2025-07-09 21:31:10.781] INFO: [Server] Arch: x64 audiobookshelf | [2025-07-09 21:31:10.793] FATAL: [Server] Unhandled rejection: [Error: EACCES: permission denied, mkdir '/metadata/streams'] { audiobookshelf | errno: -13, audiobookshelf | code: 'EACCES', audiobookshelf | syscall: 'mkdir', audiobookshelf | path: '/metadata/streams' audiobookshelf | } audiobookshelf | promise: Promise { audiobookshelf | <rejected> [Error: EACCES: permission denied, mkdir '/metadata/streams'] { audiobookshelf | errno: -13, audiobookshelf | code: 'EACCES', audiobookshelf | syscall: 'mkdir', audiobookshelf | path: '/metadata/streams' audiobookshelf | } audiobookshelf | } audiobookshelf | [2025-07-09 21:31:10.798] FATAL: [Server] Unhandled rejection: [Error: EACCES: permission denied, mkdir '/metadata/logs'] { audiobookshelf | errno: -13, audiobookshelf | code: 'EACCES', audiobookshelf | syscall: 'mkdir', audiobookshelf | path: '/metadata/logs' audiobookshelf | } audiobookshelf | promise: Promise { audiobookshelf | <rejected> [Error: EACCES: permission denied, mkdir '/metadata/logs'] { audiobookshelf | errno: -13, audiobookshelf | code: 'EACCES', audiobookshelf | syscall: 'mkdir', audiobookshelf | path: '/metadata/logs' audiobookshelf | } audiobookshelf | } ``` ### Additional Notes _No response_
adam added the bug label 2026-04-25 00:11:29 +02:00
Author
Owner

@advplyr commented on GitHub (Jul 10, 2025):

Abs needs write access to the /config and /metadata folder

@advplyr commented on GitHub (Jul 10, 2025): Abs needs write access to the /config and /metadata folder
Author
Owner

@mill1000 commented on GitHub (Jul 10, 2025):

Sure of course, but by setting permissions appropriately we can allow non-root users. I made a small PR #4474 to demonstrate.

@mill1000 commented on GitHub (Jul 10, 2025): Sure of course, but by setting permissions appropriately we can allow non-root users. I made a small PR #4474 to demonstrate.
Author
Owner

@advplyr commented on GitHub (Jul 10, 2025):

Ah I see. The key part that I forgot about is that Docker automatically creates folders for volume mounts if they don't already exist. Do you know if there is a way to have Docker use the user passed into the command to create the folders instead of using root?

@advplyr commented on GitHub (Jul 10, 2025): Ah I see. The key part that I forgot about is that Docker automatically creates folders for volume mounts if they don't already exist. Do you know if there is a way to have Docker use the user passed into the command to create the folders instead of using root?
Author
Owner

@mill1000 commented on GitHub (Jul 15, 2025):

At runtime? I think the container is running as the specific user immediately if using the user option.

I looked at few other implementation. It appears some take UID and GID an environmental vars (instead of user), then tweak permissions & user IDs before dropping permissions when running the final app.

@mill1000 commented on GitHub (Jul 15, 2025): At runtime? I think the container is running as the specific user immediately if using the `user` option. I looked at few other implementation. It appears some take UID and GID an environmental vars (instead of user), then tweak permissions & user IDs before dropping permissions when running the final app.
Author
Owner

@plz12345 commented on GitHub (Aug 3, 2025):

I'm hitting this too, while trying to migrate an existing working container from Synology to Unraid. Unraid is pretty restrictive on file permissions, so this design is causing issues. The docker file is trying to force the creation of folders that are already there, and I believe it is also ignoring/not leveraging the Docker user directive, as well as UID and GID, all of which are a common standard for running as non-root user.

The expectation is to have these two paths volume-mounted, not created within the container, is it not? The app should only need to write inside them, not create them, and at that, only as the passed-in user, not root.

If so, you should just respect the UID/GID pattern (or Docker's user directive), and handle the volumes like other conatiners doing identical "config" and "metadata" things.

Example, in your dockerfile you would have just the app-internal volume definitions, leaving the user to add their own for /audiobooks, /podcasts, etc. You'd edit the mkdir line to just /config and /metadata for these mount points entirely.

https://docs.docker.com/reference/dockerfile/#volume

VOLUME ["/config", "/metadata"]

and the user would have Volume mount to those that they pass in via CLI or via Docker Compose, like below.

    volumes:
      - /mnt/user/media/audiobooks:/audiobooks
      - /mnt/user/appdata/audiobookshelf/config:/config
      - /mnt/user/appdata/audiobookshelf/metadata:/metadata

From my container console

Running in production mode.
Options: CONFIG_PATH=/mnt/user/appdata/audiobookshelf/config, METADATA_PATH=/mnt/user/appdata/audiobookshelf/metadata, PORT=80, HOST=undefined, SOURCE=docker, ROUTER_BASE_PATH=/audiobookshelf
node:fs:1372
  const result = binding.mkdir(
                         ^
Error: ENOENT: no such file or directory, mkdir '/mnt/user/appdata/audiobookshelf/config'
    at Object.mkdirSync (node:fs:1372:26)
    at new Server (/app/server/Server.js:92:10)
    at Object.<anonymous> (/app/index.js:52:16)
    at Module._compile (node:internal/modules/cjs/loader:1529:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1613:10)
    at Module.load (node:internal/modules/cjs/loader:1275:32)
    at Module._load (node:internal/modules/cjs/loader:1096:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:164:12)
    at node:internal/main/run_main_module:28:49 {

  errno: -2,
  code: 'ENOENT',
  syscall: 'mkdir',
  path: '/mnt/user/appdata/audiobookshelf/config'
}
Node.js v20.19.4

Note: I've never built a container before, so I'm just going off the official docs and other example dockerfiles I've seen.

@plz12345 commented on GitHub (Aug 3, 2025): I'm hitting this too, while trying to migrate an existing working container from Synology to Unraid. Unraid is pretty restrictive on file permissions, so this design is causing issues. The docker file is trying to force the creation of folders that are already there, and I believe it is also ignoring/not leveraging the Docker `user` directive, as well as `UID` and `GID`, all of which are a common standard for running as non-root user. The expectation is to have these two paths volume-mounted, not created within the container, is it not? The app should only need to write inside them, not create them, and at that, only as the passed-in user, not root. If so, you should just respect the UID/GID pattern (or Docker's `user` directive), and handle the volumes like other conatiners doing identical "config" and "metadata" things. Example, in your dockerfile you would have just the app-internal volume definitions, leaving the user to add their own for `/audiobooks`, `/podcasts`, etc. You'd edit the `mkdir` line to just `/config` and `/metadata` for these mount points entirely. https://docs.docker.com/reference/dockerfile/#volume ``` VOLUME ["/config", "/metadata"] ``` and the user would have Volume mount to those that they pass in via CLI or via Docker Compose, like below. ``` volumes: - /mnt/user/media/audiobooks:/audiobooks - /mnt/user/appdata/audiobookshelf/config:/config - /mnt/user/appdata/audiobookshelf/metadata:/metadata ``` From my container console ``` Running in production mode. Options: CONFIG_PATH=/mnt/user/appdata/audiobookshelf/config, METADATA_PATH=/mnt/user/appdata/audiobookshelf/metadata, PORT=80, HOST=undefined, SOURCE=docker, ROUTER_BASE_PATH=/audiobookshelf node:fs:1372 const result = binding.mkdir( ^ Error: ENOENT: no such file or directory, mkdir '/mnt/user/appdata/audiobookshelf/config' at Object.mkdirSync (node:fs:1372:26) at new Server (/app/server/Server.js:92:10) at Object.<anonymous> (/app/index.js:52:16) at Module._compile (node:internal/modules/cjs/loader:1529:14) at Module._extensions..js (node:internal/modules/cjs/loader:1613:10) at Module.load (node:internal/modules/cjs/loader:1275:32) at Module._load (node:internal/modules/cjs/loader:1096:12) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:164:12) at node:internal/main/run_main_module:28:49 { errno: -2, code: 'ENOENT', syscall: 'mkdir', path: '/mnt/user/appdata/audiobookshelf/config' } Node.js v20.19.4 ``` Note: I've never built a container before, so I'm just going off the official docs and other example dockerfiles I've seen.
Author
Owner

@Vylyne commented on GitHub (Sep 25, 2025):

It seems like the standard solution used by groups like the linuxserver.io and others is to use PID and GID Environment variables, instead of the user directive for this, probably because of the issue you're asking about @advplyr as docker is running as root it will create the missing folders as root but if the container is running as something else like 1000:1000 it wont' have write access can't chown or chmod it can't do anything. Until someone intervenes...

With the PUID and PGID environment variables you should be able to chown the files as root to be owned by the PUID:PGID specified int he those variables when the container starts or in the dockerfile then just have the service start as as that user/group Instead of letting it run as root.

@Vylyne commented on GitHub (Sep 25, 2025): It seems like the standard solution used by groups like the linuxserver.io and others is to use PID and GID Environment variables, instead of the user directive for this, probably because of the issue you're asking about @advplyr as docker is running as root it will create the missing folders as root but if the container is running as something else like 1000:1000 it wont' have write access can't chown or chmod it can't do anything. Until someone intervenes... With the PUID and PGID environment variables you should be able to chown the files as root to be owned by the PUID:PGID specified int he those variables when the container starts or in the dockerfile then just have the service start as as that user/group Instead of letting it run as root.
Author
Owner

@Vylyne commented on GitHub (Sep 26, 2025):

Okay took a crack at this. And I've created a draft pull request. Feel free to take a look. #4700

@Vylyne commented on GitHub (Sep 26, 2025): Okay took a crack at this. And I've created a draft pull request. Feel free to take a look. #4700
Author
Owner

@Vylyne commented on GitHub (Sep 30, 2025):

I've actually got another way to think about this user directive now other then the PUID GUID environment variables.

Further investigation into how the USER directive works is that it overrides what user is used to execute the entrypoint. So, basically anything that the needs to read, has to be world readable, and anything it needs to write world writable. So this can actually be fully handled in the dockerfile, I think. I'd need to look at the code again with this in mind rather then my above PR.

What' I'd do is add ARGs for UID and GID, and default them to the IDs currently used. Then make sure to chmod everything that the service touches to make sure "others" would have the permissions to do what ever the service needs. because the UID and GID that is supplied via user is not generally root and won't have privileges to fix permissions and ownership... Hell most likely doesn't even exist in the container. :)

Basically the Issue of the folders being auto created by the docker service is not addressed but you can precreate them and define what user you want them to be owned by then launch the container with the user directive. This also boosts security because nothing is running as root in the container anymore so a break out would still be a less privileged user.

@Vylyne commented on GitHub (Sep 30, 2025): I've actually got another way to think about this user directive now other then the PUID GUID environment variables. Further investigation into how the USER directive works is that it overrides what user is used to execute the entrypoint. So, basically anything that the needs to read, has to be world readable, and anything it needs to write world writable. So this can actually be fully handled in the dockerfile, I think. I'd need to look at the code again with this in mind rather then my above PR. What' I'd do is add ARGs for UID and GID, and default them to the IDs currently used. Then make sure to chmod everything that the service touches to make sure "others" would have the permissions to do what ever the service needs. because the UID and GID that is supplied via user is not generally root and won't have privileges to fix permissions and ownership... Hell most likely doesn't even exist in the container. :) Basically the Issue of the folders being auto created by the docker service is not addressed but you can precreate them and define what user you want them to be owned by then launch the container with the user directive. This also boosts security because nothing is running as root in the container anymore so a break out would still be a less privileged user.
Author
Owner

@mill1000 commented on GitHub (Oct 3, 2025):

What' I'd do is add ARGs for UID and GID, and default them to the IDs currently used. Then make sure to chmod everything that the service touches to make sure "others" would have the permissions

That was basically my quick solution in #4474, pre-create and chmod the directories so they're world accessible.

@mill1000 commented on GitHub (Oct 3, 2025): > What' I'd do is add ARGs for UID and GID, and default them to the IDs currently used. Then make sure to chmod everything that the service touches to make sure "others" would have the permissions That was basically my quick solution in #4474, pre-create and chmod the directories so they're world accessible.
Author
Owner

@mill1000 commented on GitHub (Jan 2, 2026):

I took another look at this and updated my PR to use the VOLUME instruction that @plz12345 highlighted as well.

Adding VOLUME helps tell the user "hey you should be mounting something here", and causes docker to create anonymous volumes if no mount point is setup.

I also tested @Vylyne PR and it worked too. It has the advantage of also defaulting to non-root. It could probably benefit from adding the VOLUME instruction too.

@advplyr Anything that's preventing either of these solutions from moving forward?

@mill1000 commented on GitHub (Jan 2, 2026): I took another look at this and updated my PR to use the VOLUME instruction that @plz12345 highlighted as well. Adding VOLUME helps tell the user "hey you should be mounting something here", and causes docker to create anonymous volumes if no mount point is setup. I also tested @Vylyne PR and it worked too. It has the advantage of also defaulting to non-root. It could probably benefit from adding the VOLUME instruction too. @advplyr Anything that's preventing either of these solutions from moving forward?
Author
Owner

@Zie0 commented on GitHub (Feb 19, 2026):

Any chance this will be addressed soon?

I run all my self-hosted services with podman quadlet and ABS is the only of eleven that requires that it's run as root. This messes up bind mounts for other services to the point that ABS is requiring that I run my other services as root too. I like ABS, but this has been frustrating the way I've had to adjust the deployment of other services and my file system permissions to accommodate it. I saw this was opened when I ran into this months ago and kept using ABS expecting after seeing the MR that appears to resolve it. I figured this wouldn't be a problem for long.

@Zie0 commented on GitHub (Feb 19, 2026): Any chance this will be addressed soon? I run all my self-hosted services with podman quadlet and ABS is the only of eleven that _requires_ that it's run as root. This messes up bind mounts for other services to the point that ABS is requiring that I run my _other services_ as root too. I like ABS, but this has been frustrating the way I've had to adjust the deployment of other services and my file system permissions to accommodate it. I saw this was opened when I ran into this months ago and kept using ABS expecting after seeing the MR that appears to resolve it. I figured this wouldn't be a problem for long.
Author
Owner

@Vylyne commented on GitHub (Feb 20, 2026):

It could probably benefit from adding the VOLUME instruction too.

I'd suggest we open the VOLUME instruction request as a Feature Request so it can have it's own discussion/change tracked appropriately. It probably makes sense for the mandatory volumes (/config /metadata) As stated above.

I've added the VOLUME instruction to my dev and main(ghcr.io/vylyne/audiobookshelf:latest) branches, not my PR for now. If we get any feedback from the people who maintain this project we can see where we go from there but I'll probably incorporate it into my main after a while not sure I'll add it to my non-root PR, at last not without feedback from the maintainers of this project.

@Vylyne commented on GitHub (Feb 20, 2026): > It could probably benefit from adding the VOLUME instruction too. I'd suggest we open the VOLUME instruction request as a Feature Request so it can have it's own discussion/change tracked appropriately. It probably makes sense for the mandatory volumes (/config /metadata) As stated above. I've added the VOLUME instruction to my dev and main(ghcr.io/vylyne/audiobookshelf:latest) branches, not my PR for now. If we get any feedback from the people who maintain this project we can see where we go from there but I'll probably incorporate it into my main after a while not sure I'll add it to my non-root PR, at last not without feedback from the maintainers of this project.
Author
Owner

@Zie0 commented on GitHub (Feb 24, 2026):

I run all my self-hosted services with podman quadlet and ABS is the only of eleven that requires that it's run as root. This messes up bind mounts for other services to the point that ABS is requiring that I run my other services as root too.

Turns out it was user error on my part that made me need to run my other services as root too. I was using the SELinux :Z option for the volumes mounted by audiobookshelf (while it was run as root) instead of the :z option. I can run my other podman quadlet services rootless now too after updating that option for all shared mounted volumes.

@Zie0 commented on GitHub (Feb 24, 2026): > I run all my self-hosted services with podman quadlet and ABS is the only of eleven that requires that it's run as root. ~~This messes up bind mounts for other services to the point that ABS is requiring that I run my other services as root too.~~ Turns out it was user error on my part that made me need to run my other services as root too. I was using the SELinux `:Z` option for the volumes mounted by audiobookshelf (while it was run as root) instead of the `:z` option. I can run my other podman quadlet services rootless now too after updating that option for all shared mounted volumes.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/audiobookshelf#2880