Skip to Content

Production Guide: Deploy Audiobookshelf with Docker Compose + Caddy on Ubuntu

Self-hosted audiobook and podcast streaming with automatic HTTPS and multi-user support

Teams managing large audiobook or podcast libraries find that cloud services like Audible impose DRM restrictions, limit library portability, and charge subscription fees for content you already own. Audiobookshelf eliminates those constraints with a self-hosted media server that streams your files, remembers playback position across all devices, supports multiple users with independent libraries, and exposes a REST API for integration with your infrastructure stack. It handles M4B, MP3, FLAC, and OGG audiobooks alongside podcast RSS subscriptions with automatic episode downloads, all from a single container running comfortably on a 1 vCPU VPS.

This guide deploys a production-ready Audiobookshelf instance using Docker Compose on Ubuntu 22.04 LTS with Caddy handling automatic TLS. By the end you will have a publicly accessible Audiobookshelf dashboard with HTTPS, persistent volumes for your media library and metadata, and a systemd-supervised stack ready for daily use across browsers and native mobile apps.

Architecture and flow overview

Audiobookshelf uses a monolithic single-container architecture. The Node.js application serves both the web interface and the REST/WebSocket API. Metadata, user accounts, progress data, and podcast episode records are stored in a SQLite database inside the container's data directory. Your actual audiobook and podcast files live on host-mounted volumes that the container reads at runtime — the container never copies or re-encodes your media, it streams directly from the mounted paths.

Traffic flow: a browser connects to Caddy on port 443, Caddy terminates TLS using a Let's Encrypt certificate, and reverse-proxies to the Audiobookshelf container on localhost port 13378. WebSocket connections for real-time progress sync use the same proxy with no extra configuration. Named Docker volumes store the SQLite database and metadata across restarts — no external database is needed.

Prerequisites

  • Ubuntu 22.04 LTS server with root or sudo access
  • Minimum 1 GB RAM and 1 vCPU (512 MB works but is tight with large libraries)
  • Docker Engine 24+ and Docker Compose Plugin installed
  • Caddy 2.7+ installed as a systemd service
  • A domain name (e.g., abs.yourdomain.com) with an A record pointing to the server's public IP, fully propagated before starting Caddy
  • UFW or equivalent firewall allowing inbound ports 22 (SSH), 80 (Caddy ACME), and 443 (HTTPS) only
  • Sufficient disk space for your audiobook and podcast library (plan for at least 20 GB; audiobooks average 300–500 MB per title)

Step-by-step deployment

1. Install Docker Engine and Compose Plugin

sudo apt update && sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker

2. Install Caddy from the official repository

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
  | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
  | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy
sudo systemctl enable --now caddy

3. Create the project directory structure

sudo mkdir -p /opt/audiobookshelf
sudo mkdir -p /mnt/audiobooks /mnt/podcasts
cd /opt/audiobookshelf

The /mnt/audiobooks and /mnt/podcasts directories are the host-side mount points for your media. You can point these at any path — an NFS share, a local disk, or any directory your server can reach. Audiobookshelf treats these as read-only sources for audiobooks and read-write for podcast downloads.

4. Write the Docker Compose file

cat > /opt/audiobookshelf/docker-compose.yml << 'EOF'
version: "3.8"

services:
  audiobookshelf:
    image: ghcr.io/advplyr/audiobookshelf:latest
    container_name: audiobookshelf
    restart: unless-stopped
    ports:
      - "127.0.0.1:13378:80"
    volumes:
      - abs_config:/config
      - abs_metadata:/metadata
      - /mnt/audiobooks:/audiobooks
      - /mnt/podcasts:/podcasts
    environment:
      - TZ=UTC
      - AUDIOBOOKSHELF_UID=1000
      - AUDIOBOOKSHELF_GID=1000
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:80/healthcheck"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

volumes:
  abs_config:
    driver: local
  abs_metadata:
    driver: local
EOF

Port 13378 is bound only to localhost so the dashboard is never directly exposed. All traffic goes through Caddy on port 443. The named volumes abs_config and abs_metadata store the SQLite database, cover art, and podcast episode cache. AUDIOBOOKSHELF_UID and AUDIOBOOKSHELF_GID ensure the container writes files as UID 1000, preventing permission conflicts on the media mounts.

5. Configure the Caddyfile

sudo tee /etc/caddy/Caddyfile << 'EOF'
abs.yourdomain.com {
    reverse_proxy 127.0.0.1:13378

    encode gzip

    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "SAMEORIGIN"
        Referrer-Policy "strict-origin-when-cross-origin"
    }

    log {
        output file /var/log/caddy/abs_access.log
        format json
    }
}
EOF

sudo mkdir -p /var/log/caddy
sudo systemctl reload caddy

Replace abs.yourdomain.com with your actual domain. Caddy obtains and auto-renews a Let's Encrypt certificate on first startup. WebSocket connections for real-time playback sync are handled transparently by Caddy's reverse proxy with no extra configuration.

6. Harden the firewall with UFW

sudo ufw allow 22/tcp comment "SSH"
sudo ufw allow 80/tcp comment "Caddy ACME"
sudo ufw allow 443/tcp comment "HTTPS"
sudo ufw --force enable
sudo ufw status verbose

7. Start the Audiobookshelf stack

cd /opt/audiobookshelf
docker compose up -d
docker compose ps
docker compose logs -f --tail=30

On first startup, Audiobookshelf initializes its SQLite database. The container is ready once the healthcheck passes. Navigate to https://abs.yourdomain.com and complete the setup wizard to create your admin account and configure your first library.

Configuration and secrets handling

Audiobookshelf requires no environment-variable secrets for basic operation — authentication is handled via the web UI setup wizard. For production deployments enforce these hardening steps.

Library paths: After logging in, navigate to Settings → Libraries and create one library pointing at /audiobooks and another at /podcasts. Audiobookshelf scans these directories and indexes all supported audio files automatically on creation and on demand.

User accounts: Create user accounts under Settings → Users. Assign each user access to specific libraries and disable the guest account if you do not need anonymous access. For organization-wide SSO, place Audiobookshelf behind Authentik ForwardAuth via Caddy middleware.

Backup the config volume: The abs_config volume contains your entire database — all user accounts, progress data, bookmarks, and library metadata. Back it up regularly with:

docker compose -f /opt/audiobookshelf/docker-compose.yml stop
docker run --rm \
  -v abs_config:/data \
  -v /var/backups/audiobookshelf:/backup \
  alpine tar czf /backup/abs_config_$(date +%Y%m%d_%H%M%S).tar.gz /data
docker compose -f /opt/audiobookshelf/docker-compose.yml start

Schedule this with a cron job or integrate with Kopia for automated S3-compatible off-site backups. Your media files on /mnt/audiobooks and /mnt/podcasts should have their own backup strategy independent of the container volumes.

Verification

After the stack is running, confirm every layer is healthy:

# Container health
docker compose -C /opt/audiobookshelf ps
docker inspect audiobookshelf --format '{{.State.Health.Status}}'

# HTTP endpoint via Caddy
curl -s -o /dev/null -w "%{http_code}" https://abs.yourdomain.com/healthcheck
# Expected: 200

# TLS certificate validity
echo | openssl s_client -connect abs.yourdomain.com:443 -servername abs.yourdomain.com 2>/dev/null \
  | openssl x509 -noout -dates

# Caddy logs for any upstream errors
sudo journalctl -u caddy --since "5 minutes ago" --no-pager

A 200 response from the healthcheck confirms that Caddy and the Audiobookshelf container are functional. Log in as admin, navigate to your library, and verify audio files appear. Click a track and confirm playback starts in the browser player.

Common issues and fixes

Library scan finds no files: The most common cause is a permission mismatch — the container process (UID 1000) cannot read files owned by root or another user on the host mount. Fix with: sudo chown -R 1000:1000 /mnt/audiobooks /mnt/podcasts. If files are on an NFS share, ensure the NFS export options include all_squash or a matching UID map. Re-trigger a scan from Settings → Libraries → Scan after fixing permissions.

Caddy returns 502 Bad Gateway: The Audiobookshelf container has not yet started or has crashed. Check container state with docker compose ps and logs with docker compose logs audiobookshelf. A common early failure is a port conflict on 13378 — verify no other process is bound to that address with ss -tlnp | grep 13378.

Playback stalls or buffers on large files: Audiobookshelf streams directly from disk. If your media is on an NFS or CIFS mount with high latency, increase the NFS rsize and wsize mount options to 1048576 and enable asynchronous I/O. For local disk, verify no other process is saturating disk I/O with iostat -x 2 5.

Podcast episodes not downloading: Check that the Audiobookshelf container has outbound internet access. If your server is behind a restrictive egress firewall, whitelist the podcast RSS feed hostnames. Also verify the /mnt/podcasts mount is writable by UID 1000 — podcast downloads require write access whereas audiobook scanning only needs read access.

HTTPS certificate not provisioning: Caddy requires that your domain's DNS A record is fully propagated and that port 80 is reachable from the internet for the ACME HTTP-01 challenge. Run sudo journalctl -u caddy -n 50 --no-pager and look for TLS provisioning errors. If you are behind a NAT, ensure port 80 is forwarded to the server. For servers where port 80 cannot be opened, switch to Caddy's DNS-01 challenge using a supported DNS provider plugin.

FAQ

Does Audiobookshelf support mobile apps for offline listening?

Yes. Official iOS and Android apps are available that connect to your self-hosted server URL. The apps support background audio playback, lock-screen controls, and offline download — you can download individual books or entire series to the device for listening without an internet connection. Progress is synced back to the server when connectivity is restored, so your position is consistent across all devices including the web browser.

Can multiple users share the same library with independent progress tracking?

Yes. Audiobookshelf stores per-user progress, bookmarks, and listening statistics independently. An audiobook that User A has 40% complete shows 0% for User B until they start listening. Admins can see aggregate library statistics and manage all user accounts from the Settings panel. Users only see the libraries they have been granted access to, so you can segment personal and family collections without mixing them.

What audio formats does Audiobookshelf support?

Audiobookshelf supports M4B (the standard audiobook container), MP3, FLAC, OGG, OPUS, M4A, AAC, and WAV. It does not transcode files at rest — it streams the original file to the browser or app. The web player uses the browser's native HTML5 audio capabilities, so format support in the web UI depends on your browser. The mobile apps have broader codec support via their native audio engines. M4B files with embedded chapters are fully supported and chapter navigation appears in both the web player and mobile apps.

How do I add podcast feeds and schedule automatic downloads?

Navigate to Libraries → Podcasts → Find and search by title or paste an RSS feed URL directly. Audiobookshelf fetches the feed, lists available episodes, and allows you to subscribe. Under Settings → Podcast Settings you can configure a global download schedule (daily, weekly, or custom cron expression) that automatically fetches new episodes for all subscribed feeds. Episodes are downloaded to the /podcasts mount path and appear in your library immediately after download.

Is there an API I can use to automate library management?

Yes. Audiobookshelf exposes a full REST API documented at /docs on your server. The API uses JWT bearer tokens for authentication — generate a token from Settings → Users → API Token for your admin account. The API supports library queries, playback session management, user progress updates, podcast feed operations, and metadata editing. This makes it straightforward to integrate with n8n workflows or shell scripts for bulk operations like re-scanning libraries after adding files via rsync or SFTP.

How do I upgrade Audiobookshelf to a newer version?

Since the image is tagged latest, pull the new image and restart the container: cd /opt/audiobookshelf && docker compose pull && docker compose up -d. Audiobookshelf applies any required SQLite schema migrations automatically on startup. Always back up the abs_config volume before a major version upgrade. Check the GitHub releases page for breaking changes — in particular, major versions sometimes change the expected path structure inside the config volume. Downtime during the upgrade is typically under 30 seconds.

Can I run Audiobookshelf behind Authentik for SSO?

Yes. Because Caddy is your reverse proxy, you can add Authentik's ForwardAuth middleware to the Audiobookshelf Caddy vhost block. This routes all browser requests through an Authentik authentication check before they reach the application. Users must log in to Authentik (with MFA if configured) before the Audiobookshelf login page is even served. This is recommended for internet-exposed instances where you want an additional authentication layer enforced at the network edge rather than relying solely on Audiobookshelf's built-in user accounts.

Internal links

Talk to us

If you want this deployed with hardened access controls, monitoring standards, and production runbooks tailored to your environment, our team can help end-to-end.

Contact Us

Production Guide: Deploy Beszel with Docker Compose + Caddy on Ubuntu
Lightweight multi-server infrastructure monitoring with automatic HTTPS and hub-and-agent architecture