Everything not listed in this document should behave the same as upstream Audiobookshelf. If a feature, setting, or behavior is not mentioned here, the upstream documentation is accurate and fully applicable — see the Documentation section of
instructions.mdfor links.
Audiobookshelf is a self-hosted audiobook and podcast server. This package builds a patched image with the web client's calls to third-party origins removed, splits its storage across four volumes, and can mount another service's files in as a read-only library.
- Upstream repo: https://github.com/advplyr/audiobookshelf
- Wrapper repo: https://github.com/Start9Labs/audiobookshelf-startos
- Image and Container Runtime
- Volume and Data Layout
- File Models
- Dependencies
- Network Access and Interfaces
- Installation and First-Run Flow
- Actions
- Tasks
- Health Checks
- Backups and Restore
- Limitations and Differences
- Quick Reference for AI Consumers
The image is the upstream image with two patches applied on top, because upstream's prebuilt web bundle makes automatic calls from the user's browser to origins outside the server.
| Property | Value |
|---|---|
| Image | Built from Dockerfile, FROM ghcr.io/advplyr/audiobookshelf |
| Architectures | x86_64, aarch64 |
| Entrypoint | Upstream default |
| Subcontainer | audiobookshelf-sub — the primary daemon, and the one to attach to |
The two patches: the PWA service worker's Workbox import is repointed from a public CDN to a copy vendored into the image, and the client's release check against GitHub is repointed at a static same-origin file that always reports the running version as current. Each patch first asserts that the string it is about to rewrite is present, so an upstream bump that reshapes the bundle fails the build rather than silently shipping an unpatched client. Two further outbound calls need no patch: the Cast SDK is gated behind a server setting that defaults off, and upstream's own image already suppresses the server-side binary downloads.
The image upgrades Alpine's ca-certificates and ca-certificates-bundle at build time. NODE_OPTIONS=--use-openssl-ca makes Node use that system trust store instead of its bundled roots.
The Reset Admin Password action runs in a short-lived reset-admin subcontainer from the same image, with only the config volume mounted.
Four volumes, split so that a backup, a media move, or a permissions problem touches one concern at a time.
| Volume | Mount Point | Purpose |
|---|---|---|
config |
/config |
The application database (absdatabase.sqlite), and store.json |
metadata |
/metadata |
Cover art, cached metadata, and generated artifacts |
audiobooks |
/audiobooks |
The writable audiobook library — uploads land here |
podcasts |
/podcasts |
The writable podcast library — subscriptions download here |
When an external library is connected, that service's storage appears as a further mount, read-only: /mnt/nextexplorer, /mnt/filebrowser or /mnt/nextcloud, or only the subfolder set for it, at its own path below that. Which folder inside it becomes a library is chosen in Audiobookshelf's own settings, not here.
One model, holding one setting: which other services are mounted in as read-only libraries, and which folder of each.
| File | Format | Modelled | Written by |
|---|---|---|---|
/config/store.json |
JSON | Yes — FileHelper.json |
Every init, and the External Libraries action |
nextexplorer, filebrowser and nextcloud each hold the form's setting for that service: selection is disabled or enabled, and while it is enabled, value.subfolder is a path relative to the service's volume root, or null to mount the whole volume. Init merges the file without overwriting them, so the settings survive updates and restores, and the action writes all three from whatever the form submitted. An update from a release that kept a single externalLibraries list enables each listed service with no subfolder. A setting that does not parse is reset to disabled rather than honoured.
The setting has reach beyond its own file: main reads it to decide which dependency volumes, or folders of them, to mount, and dependencies.ts reads it to decide which optional dependencies are enabled. Both hold it in a reactive const, so changing the selection or a subfolder restarts the service with the new mounts.
The application's own database is not a file model. absdatabase.sqlite on the config volume holds users, libraries, listening progress, and every server setting; the package neither reads nor writes it in normal operation. The one exception is Reset Admin Password, which edits it directly while the service is stopped.
The only configuration passed to the application is environment — PORT, CONFIG_PATH, METADATA_PATH — and it is the same on every start.
All three are optional and none is required to run: they appear only when enabled in External Libraries.
| Dependency | Id | Version range | Kind | Volume mounted | Mount |
|---|---|---|---|---|---|
| NextExplorer | nextexplorer |
>=2.2.7:0 |
exists |
data |
/mnt/nextexplorer, read-only |
| FileBrowser Quantum | filebrowser |
>=2.63.18:3 |
exists |
data |
/mnt/filebrowser, read-only |
| Nextcloud | nextcloud |
>=33.0.6:1 |
exists |
nextcloud |
/mnt/nextcloud, read-only |
Each is declared only while it is enabled, and none has health checks. Only the volume is needed, so none of them has to be running for Audiobookshelf to start and read it. NextExplorer's volume root holds one directory per location, so its media sits under /mnt/nextexplorer/<location>/. Nextcloud's volume is its whole web root, so each account's files sit under /mnt/nextcloud/data/<username>/files/, beside config/config.php with its database password and instance secret.
A subfolder set for a service in External Libraries is mounted in place of the whole volume, at the path it has in that volume: subfolder data/admin/files/Books appears at /mnt/nextcloud/data/admin/files/Books, and the directories above it are empty. A library folder in Audiobookshelf therefore keeps its path whether or not a subfolder is set. It is given relative to the volume root: <location>/… for NextExplorer, data/<username>/files/… for Nextcloud.
The mounts are readonly: true, so Audiobookshelf cannot write to them even if asked to: uploads and podcast downloads always go to its own volumes.
One interface, serving both the web app and the API the mobile apps use. Nothing is exported for dependent services.
| Interface | Id | Type | Port | Description |
|---|---|---|---|---|
| Web UI | ui |
ui | 80 | The Audiobookshelf web interface and API |
The port is bound on the ui-multi MultiHost and is not masked.
Nothing is generated or bootstrapped at install and no task is raised: the service starts immediately, and account creation happens inside the application on first visit, exactly as upstream.
The one thing worth knowing is that this state is visible from outside the app. A dedicated health check reports loading with "Open the Web UI to create your admin account" until the root account exists, so a freshly installed server that looks unhealthy is usually just waiting for that first visit. See Health Checks.
Connecting an external library, if wanted, comes later and in this order: install the other service, put the media in it, then run the action — the mount only appears on the restart the action triggers.
Two actions, both user-facing.
Mounts NextExplorer's, FileBrowser Quantum's or Nextcloud's storage, or one subfolder of it, into Audiobookshelf, read-only. Run it after installing the other service and moving media into it.
- What it changes: the
nextexplorer,filebrowserandnextcloudsettings instore.json, and through them the package's mount set and dependency set. - Input: one setting per service, Disabled by default; Enabled reveals an optional subfolder. A subfolder must be a relative path with no
..segment; an empty one mounts the whole volume. - Validation: before saving, each enabled service's subfolder is mounted in a throwaway subcontainer. If that fails, because the folder does not exist or the service is not installed, the action fails with a message naming the folder and saves nothing.
- Cost: seconds, then a restart — the mounts can only change when the container is recreated.
- Repeat safety: safe to re-run; the form is pre-filled with the current settings and saved as a whole, so setting a service to Disabled removes its mount and clearing a subfolder mounts the whole volume again.
- What happens next: the storage appears at a fixed path inside the container, and a subfolder at its own path below it. Nothing becomes a library until you point one at a folder under it in Audiobookshelf's own settings — this action connects storage, it does not create libraries.
Generates a new random password for the root admin account. Run it when locked out of the web interface; ordinary password changes belong in the app.
- What it changes: the root account's password hash in
absdatabase.sqlite, and it clears that account's sessions, so existing logins are signed out. It also clears the account's active and locked flags. - Availability: only while the service is stopped, because it writes the database the application would otherwise have open.
- Repeat safety: safe to re-run; each run generates a fresh password and invalidates the previous one.
- Outputs: the username and the new password, the password masked and copyable. It is not recoverable afterwards.
- Guard: it fails with a clear message rather than doing anything if no root account exists yet — that is, if the web UI has never been opened.
None. This package raises no tasks, so the service is never held on a prompt and its ordinary controls are always available.
Two checks, and the second exists to make a normal state legible rather than to detect a fault. A third appears only when an enabled external library cannot be mounted.
| Check | Displayed | Method | Grace Period |
|---|---|---|---|
primary |
"Web Interface" | HTTP GET /healthcheck on the UI port |
SDK default |
initial-setup |
"Initial Setup" | The application's /status, read for isInit |
— |
external-libraries |
"External Libraries" | Each enabled service test-mounted, before the daemon | 0 |
primary failing means the server is not answering — read the service logs for a database or startup error.
initial-setup reports loading, not a failure, until the root account is created, and succeeds permanently once it exists. It reports starting while the server has not answered yet, which is expected during startup. A user asking why a fresh install shows an incomplete check has usually not opened the web UI yet.
external-libraries is present only when it fails. At each start, main test-mounts every enabled service, its subfolder if one is set, in a throwaway subcontainer. One that fails is left out of the mount set and named by the check: as not installed when even its whole volume will not mount, which also covers a volume that is missing, and otherwise as a subfolder not found, typically one renamed or deleted in that service after it was set. Audiobookshelf itself starts normally, without that mount. The log lines before the check carry the mount error. Installing the service or creating the folder and then restarting, or changing its setting in External Libraries, removes the check.
All four volumes are copied wholesale — sdk.Backups.ofVolumes('config', 'metadata', 'audiobooks', 'podcasts'). There is no dump step and nothing is excluded, which means the media is in the backup: an audiobook and podcast collection is usually the largest thing on the server, and the backup is sized accordingly.
- Included: the database with users, libraries and listening progress; cover art and cached metadata; and every file in the two writable libraries.
- Not included: anything in a connected external library. That storage belongs to NextExplorer, FileBrowser Quantum or Nextcloud and is covered by that service's own backup, not this one's.
- Restore: complete, including accounts and progress. If an external library was connected, that dependency must be installed for the service to start with its mount.
- External libraries are read-only, structurally. The mount itself is read-only, so podcast downloads and uploads cannot be directed there; they go to
/audiobooksand/podcasts. With a subfolder set, the directories above it,/mnt/<service>included, are not the mount but empty directories in the container's own filesystem: writable, and discarded at every restart, so a library pointed at one loses what Audiobookshelf saves into it. - Connecting or disconnecting an external library restarts the service, because a container's mounts are fixed for its lifetime. So does changing a subfolder.
- One subfolder per service. With a subfolder set, nothing else of that service's storage is reachable, so media split across folders, such as several Nextcloud accounts, needs their common parent or the whole volume.
- The client's update check always reports "up to date." It is redirected to a static same-origin file, since updates arrive through the StartOS registry rather than from upstream's releases.
- The service worker's Workbox runtime is served from your server, not from a public CDN.
- Media is included in every backup, with no way to exclude it.
- No riscv64 build. x86_64 and aarch64 only.
package_id: audiobookshelf
image: ./Dockerfile # FROM ghcr.io/advplyr/audiobookshelf, patched
architectures:
- x86_64
- aarch64
subcontainers:
- audiobookshelf-sub # the running daemon
- reset-admin # temporary; the Reset Admin Password action
volumes:
config: /config
metadata: /metadata
audiobooks: /audiobooks
podcasts: /podcasts
file_models:
- /config/store.json
startos_managed_env_vars:
- PORT
- CONFIG_PATH
- METADATA_PATH
dependencies: # optional, kind "exists"; whole volume or one subfolder, mounted read-only when enabled
- nextexplorer # >=2.2.7:0; /mnt/nextexplorer
- filebrowser # >=2.63.18:3; /mnt/filebrowser
- nextcloud # >=33.0.6:1; /mnt/nextcloud
interfaces:
ui: { type: ui, port: 80 }
actions:
- external-libraries
- reset-admin-password # only-stopped
tasks: []
health_checks:
- primary # displayed "Web Interface"
- initial-setup # displayed "Initial Setup"; loading until the root account exists
- external-libraries # displayed "External Libraries"; present only when an enabled library could not be mounted