
A three-VM audiobook + podcast library: Audiobookshelf + nginx + observability, from one prompt
April 30, 2026
Audiobookshelf is the self-hosted audiobook + podcast server that does what Audible and Spotify Podcasts do, without the algorithm and without the lock-in. It rounds out the top 10 most-deployed apps in the 2026 r/selfhosted survey. You point it at a folder of audiobooks and podcasts, and it gives you a clean library with cover art, chapter navigation, per-user progress that syncs across devices, and native iOS and Android apps with offline downloads — all running on hardware you own.
Two things make it the default pick for spoken-word audio in 2026. First, it understands audiobook formats natively: it plays MP3, M4B, M4A, FLAC, and OGG, and it manages ebooks (EPUB, PDF, MOBI, AZW3, CBR, CBZ) in the same library (Audiobookshelf docs). Second, since the 2.0 release it ships a built-in automated podcast handler that watches RSS feeds and downloads new episodes on a schedule, plus a metadata scraper that pulls covers and details from Audible, Google Books, and Open Library. The project keeps moving — a recent update added a chapter finder that makes jumping around long books far easier (How-To Geek).
This post walks through the Audiobookshelf stack on OpenFactory: three buildable VMs — Audiobookshelf, an nginx reverse proxy with WebSocket-ready upgrade headers for streaming, and a Prometheus observability node — from one prompt, shipped as bootable ISOs.
audiobookshelf (10.79.0.10:13378) — the library server with /var/lib/audiobookshelf/{config,metadata,audiobooks,podcasts} pre-wired.nginx-proxy (10.79.0.20:80) — reverse proxy with Connection: upgrade headers so client streaming and live progress sync work through it.observability (10.79.0.30:9090/3000) — Prometheus + Grafana scrape targets ready for the live stack.config, metadata, audiobooks, and podcasts under /var/lib/audiobookshelf exist at first boot with 0750 ops:opspermissions — mount your real content over them and the env file already points the right way./healthcheck, /status, /ping, and /metrics, so the proxy and the Prometheus node have something real to probe before the Node binary lands.nginx.conf on a fresh box, no guessing which directory the env file expects.Upgrade / Connection headers silently breaks that. The recipe sets them upfront, which is the single most common reverse-proxy mistake people hit with this app.:13378. The wiring is verified at build time, not discovered when a download won't play.Three Debian Trixie VMs on 10.79.0.0/24. The nginx proxy is the front door: clients hit it on :80 and it forwards to the library on :13378, carrying the WebSocket upgrade so live progress sync survives the hop. The observability node scrapes both the library and the proxy, and exposes Prometheus on :9090 and a Grafana endpoint on :3000.
/metrics from both services.Paste this verbatim into the chat builder at console.openfactory.tech. Nothing above or below it — the builder expects the prompt body to start at the “Build a compact multi-node lab…” line.
Build a compact multi-node lab named `audiobookshelf-library`.
Output discipline: keep the plan small. Use one startup script per node, about 25 shell lines or less. Do not install the real Audiobookshelf Node.js binary, FFmpeg, or external `apt` repos at build time. Write deployment-time config examples and tiny Python stdlib or shell compatibility stubs only. The goal is a buildable preparation lab, not a production deployment.
## Topology
Create 3 buildable `debian-trixie` nodes, all `x86_64`, SSH enabled, DHCP/default route intact with lab aliases, firewall disabled, DNS `1.1.1.1` and `8.8.8.8`, user `ops` password `abs-ops` in `sudo`. Every recipe must set top-level `test_config` to `{ "enabled": false, "tests": [] }`.
- `audiobookshelf`: role `audio-library`, 2 GB RAM, 24 GB disk, alias `10.79.0.10/24`, x `230`, y `60`
- `nginx-proxy`: role `reverse-proxy`, 1 GB RAM, 8 GB disk, alias `10.79.0.20/24`, x `110`, y `220`
- `observability`: role `observability`, 2 GB RAM, 16 GB disk, alias `10.79.0.30/24`, x `350`, y `220`
Connections: `nginx-proxy` to `audiobookshelf:13378`; `observability` to all nodes.
## Common Recipe Requirements
All nodes: features `headless`, `ssh`; packages `openssh-server`, `python3`, `curl`, `jq`, `iproute2`, `netcat-openbsd`, `ca-certificates`. Each startup script adds the alias with `IFACE=$(ip route show default | awk '{print $5; exit}')`, `ip link set "$IFACE" up || true`, and `ip addr add <alias> dev "$IFACE" || true`. If `os.startup_scripts[].after` is present, it must be the string `"network-online.target"`, not an array.
## Node Requirements
`audiobookshelf`: features `headless`, `ssh`. Write `/etc/audiobookshelf/abs.env` with `PORT=13378`, `CONFIG_PATH=/var/lib/audiobookshelf/config`, `METADATA_PATH=/var/lib/audiobookshelf/metadata`, `AUDIOBOOKS_PATH=/var/lib/audiobookshelf/audiobooks`, `PODCASTS_PATH=/var/lib/audiobookshelf/podcasts`. Create `/var/lib/audiobookshelf/{config,metadata,audiobooks,podcasts}` mode `0750 ops:ops`. Add a Python stdlib HTTP service on `0.0.0.0:13378` exposing:
- `GET /healthcheck` -> `200 {"status":"ok","node":"audiobookshelf"}`
- `GET /status` -> `200 {"app":"audiobookshelf","serverVersion":"compat-1.0","isInit":true,"language":"en-us"}`
- `GET /ping` -> `200 {"success":true}`
- `GET /metrics` -> `audiobookshelf_compat_up 1`
Register `audiobookshelf-compat.service`.
`nginx-proxy`: features `headless`, `ssh`, `nginx`; packages `nginx`. Write `/etc/nginx/sites-enabled/abs.conf` proxying `:80` to `http://10.79.0.10:13378` with WebSocket upgrade headers (`proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";`) for streaming playback. Restart nginx.
`observability`: features `headless`, `ssh`, `prometheus`, `monitoring`; packages `python3`. Write `/etc/prometheus/prometheus.yml` scraping `10.79.0.10:13378` and `10.79.0.20:80`. Add tiny Python listeners for `:9090/-/healthy` and `:3000/api/health`.
## Scenario
Emit exactly one group scenario named `audiobookshelf-library-validation`. Put `custom_tests[].assertions[]` inside the scenario entry; leave `scenarios[].tests` empty. Every assertion needs `on_vm`. Use only `port_listening`, `command_output`, and `http_responds`; do not emit `vm_boots`, `network_reachable`, or `service_running`.
- `Stack ports listen`: `port_listening` for `audiobookshelf:13378`, `nginx-proxy:80`, `observability:9090`, `observability:3000`.
- `Audiobookshelf healthcheck`: on `audiobookshelf`, `curl -fsS http://localhost:13378/healthcheck | jq -e '.status == "ok"' >/dev/null && echo abs-ok`.
- `Audiobookshelf status`: on `audiobookshelf`, `curl -fsS http://localhost:13378/status | jq -e '.app == "audiobookshelf" and .isInit == true' >/dev/null && echo status-ok`.
- `Proxy reaches abs`: on `nginx-proxy`, `nc -z -w 5 10.79.0.10 13378 && echo upstream-reachable`.
- `Observability`: on `observability`, grep both scrape targets from `/etc/prometheus/prometheus.yml` and `http_responds` for `http://localhost:9090/-/healthy` and `http://localhost:3000/api/health`.
Preserve warnings that real Audiobookshelf Node.js binary install, audio transcoding for streaming, mobile-app push notifications, library scan scheduling, Audible/Libby/Plex integration, off-host backups of `/var/lib/audiobookshelf`, real TLS termination, and `10.79.0.0/24` aliasing are deployment-time concerns.Driving OpenFactory from an AI agent instead of the browser? The same flow is exposed through the OpenFactory MCP server — submit the prompt programmatically, get the build-plan preview back, and call create_build / start_vm on the resulting recipes. Single-image builds go straight through the openfactory CLI.
The prompt produces a buildable preparation lab — the right topology, the right ports listening, deployment-time config templates dropped in the right places, and tiny compatibility services that prove the wiring works. A few things still sit outside the recipe and need operator attention before this carries real load:
audiobooks and podcasts) to your collection, and remember podcast auto-download grows it over time./var/lib/audiobookshelf/config holds users, listening history, bookmarks, and library metadata. That listening-position data is the thing you'd most hate to lose — back it up off the box.If you already run Plex, can't you just use it for audiobooks? You can — with caveats. Plex and Plexamp are music-first: they treat books as albums, which means clumsy chapter handling and, the classic complaint, losing your place in a long title. Getting good audiobook metadata into Plex usually means third-party bundle agents and a fair amount of tag-wrangling. Audiobookshelf was built for spoken-word from the start: resume-anywhere progress that syncs across devices, sleep timers, playback-speed memory, bookmarks, and a podcast subscription engine Plexamp simply doesn't have. Plexamp wins if your real goal is a polished music player; for books and podcasts, the purpose-built tool is the easier life.
Do I need the reverse proxy at all? For LAN-only use, no — you can hit the library on :13378 directly. The proxy earns its keep the moment you want one clean hostname, a single TLS endpoint, or remote access; it's also where the WebSocket upgrade lives, so listening progress keeps syncing through it.
Can my phone download books for offline listening? Yes. The official iOS and Android apps support offline downloads, so a commute or a flight doesn't need a connection back to the server. Progress reconciles the next time you're online.
Will it pull podcasts automatically? That's the headline 2.0 feature: subscribe to an RSS feed and Audiobookshelf checks it on a schedule and downloads new episodes into your podcast library on its own — no separate podcatcher needed.
Audiobooks plus the rest of the media stack: see the Jellyfin media stack post for video, or the Plex media stack post if Plex is already your front-end. For the *arr suite that keeps libraries filled automatically, see the *arr media automation post. The Enterprise & GxP page covers fleet rollouts, and pricing has the plan breakdown.
OpenFactory's free flow is for browsing. Persistent VMs, SSH access, snapshots, your own ISO, and fleet deployment live on a paid plan.