OpenFactory Audiobookshelf lab with nginx reverse proxy and Prometheus observability

Build an Audiobookshelf Library on OpenFactory

A three-VM audiobook + podcast library: Audiobookshelf + nginx + observability, from one prompt

June 2, 2026

← Back to Blog

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.

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.

What you'll build

  • 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.

Why build it on OpenFactory

  • The ISO is the spec. Library paths, env file, and proxy vhost all bake into bootable images.
  • WebSocket-ready proxy out of the box. Audiobookshelf relies on WS for progress sync; the recipe handles the headers upfront.
  • Three VMs, easy to grow. Same prompt scales to add a transcoder or a second proxy without rebuilding.
  • Scenario assertions ride along. The build fails closed if the healthcheck or status endpoints regress, or if the proxy can't reach the library.

Topology

Three Debian Trixie VMs on 10.79.0.0/24. nginx fronts the library; observability scrapes both.

The prompt

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.

Running it

  1. Open the chat builder at console.openfactory.tech and paste the prompt into a new conversation.
  2. Review the streamed build plan. You'll see the topology, per-node recipes, and the scenario assertions that will run after boot. Edit the prompt and re-run if anything is off.
  3. Click Build group. OpenFactory fans the plan out to per-node ISO builds. When every ISO reaches built, boot the group on the runner network from the same UI.
  4. Exercise the stack. The scenario assertions run automatically against the live VMs. From the host you can also hit the service ports directly to confirm end-to-end behavior.

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.

What's still your responsibility

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:

  • Real Audiobookshelf Node binary. Install from the upstream release; the env file already points at the right paths.
  • Library content. The directories are ready — mount your existing audiobook + podcast collection (or a share from your NAS).
  • Real TLS. Layer the nginx + TLS reverse proxy pattern in front for public access.
  • Mobile-app push. Wire the push-notification keys for the iOS / Android client at deploy.
  • Off-host backups. /var/lib/audiobookshelf/config holds users, listening history, and library metadata.

Where to go next

Audiobooks plus the rest of the media stack: see the Jellyfin media stack post for video. For the *arr suite that keeps libraries filled automatically, see the *arr media automation post. And the Enterprise & GxP page covers fleet rollouts.

Ready to ship this in production?

OpenFactory's free flow is for browsing. Persistent VMs, SSH access, snapshots, your own ISO, and fleet deployment live on a paid plan.