All Server Guides Troubleshooting

Running Audiobookshelf as a non-root user in Docker

Fix EACCES permission denied errors when running Audiobookshelf Docker with user: 1000:1000 by pre-creating config and metadata directories

Illustrated cross-section of a Docker whale built from stacked file system layers, with red root-owned layers at the bottom and a small user icon locked outside

The Audiobookshelf Docker image runs as root by default. If you try to run it as a non-root user with the user: directive in Docker Compose, the container crashes immediately with permission errors:

FATAL: [Server] Unhandled rejection: [Error: EACCES: permission denied, mkdir '/metadata/streams'] {
  errno: -13,
  code: 'EACCES',
  syscall: 'mkdir',
  path: '/metadata/streams'
}

The root cause is in the Dockerfile and Server.js. The image never pre-creates /config and /metadata with open permissions. Docker auto-creates volume mount points owned by root, and then the Node app (running as your non-root user) tries to mkdirSync subdirectories like /metadata/streams and /metadata/logs and gets denied. Two community PRs have proposed fixes: #4474 (still open) pre-creates the directories during the Docker build, and #4700 (closed without merge) added PUID/PGID support via an entrypoint script. Neither approach has landed in the official image.

This hits Docker Compose users, Podman/Quadlet users, and anyone on Unraid or Synology where file permissions are locked down.

Pre-create and own the directories

The simplest workaround is to create the config and metadata directories on the host, owned by the user you want the container to run as, before starting it.

# Create the directories with your user
mkdir -p ./config ./metadata

# Make sure they're owned by the UID:GID you'll pass to Docker
# (change 1000:1000 to match your setup)
chown -R 1000:1000 ./config ./metadata

Then use bind mounts in your compose file instead of Docker-managed volumes:

services:
  audiobookshelf:
    image: ghcr.io/advplyr/audiobookshelf:latest
    container_name: audiobookshelf
    restart: unless-stopped
    user: "1000:1000"
    environment:
      - TZ=America/Denver
    ports:
      - 13378:80
    volumes:
      - ./audiobooks:/audiobooks
      - ./config:/config        # bind mount, not a named volume
      - ./metadata:/metadata    # bind mount, not a named volume

The key difference: Docker-managed volumes (the audiobookshelf-config: syntax) get created as root. Bind mounts to pre-existing directories keep whatever ownership you set.

If you’re already running as root and want to switch

If you have an existing ABS install running as root and want to switch to non-root, you need to fix ownership on the existing data first:

# Stop the container
docker compose down

# Change ownership of config and metadata
# (your audiobook files probably already have the right permissions)
chown -R 1000:1000 ./config ./metadata

# Update compose to add user: "1000:1000" and restart
docker compose up -d

Check that ABS starts cleanly. If it doesn’t, look at the logs for which specific path is failing and chown that too.

Podman and rootless containers

If you’re using Podman with quadlet files, ABS is trickier because Podman defaults to rootless operation. The same fix applies: pre-create and own the directories. One user on the issue thread also ran into SELinux issues with volume labels. If you’re on a SELinux-enabled system (Fedora, RHEL), use the lowercase :z option on your volume mounts, not :Z:

- ./config:/config:z
- ./metadata:/metadata:z

The lowercase :z marks the content as shared between containers. The uppercase :Z marks it as private, which can break bind mounts for other services sharing the same paths.

If that didn’t work

If the basic pre-create approach doesn’t cover it (some setups have additional subdirectories that need to exist), you can create all the subdirectories ABS expects:

mkdir -p ./config
mkdir -p ./metadata/streams
mkdir -p ./metadata/logs
mkdir -p ./metadata/cache
mkdir -p ./metadata/items
chown -R 1000:1000 ./config ./metadata

Another option is running with a custom entrypoint that fixes permissions at startup. A community member wrote a PR that adds PUID/PGID environment variable support (similar to how linuxserver.io images work). The PR was closed without merge, but the approach works. If you’re comfortable building the image yourself, their fork has the entrypoint script you’d need.

Affected versions

This affects all current versions of Audiobookshelf’s Docker image. The Dockerfile has never included the VOLUME instruction or pre-created directories with non-root-friendly permissions. If you’re setting up a new Docker install, factor in the directory ownership from the start and you’ll avoid the issue entirely.