Audiobookshelf
Deployment rootless
1. Prepare the host
Prerequisites once as root
Install Podman (needs podman 4.3 or newer; Ubuntu 22.04 ships 3.4 and is too old, use 24.04)
apt update && apt install -y podman
Create user (if not existing)
useradd -m -s /bin/bash audiobookshelf passwd audiobookshelf
Enable linger (services keep running after logout and start after reboot)
loginctl enable-linger audiobookshelf
Switch to deployment user
su - audiobookshelf
Use su - with the dash — this creates a proper login shell as audiobookshelf. Without the dash, root environment variables carry over and rootless Podman will not work correctly.
Fix environment for rootless systemd (new users only — skip if already set)
Required once so systemctl --user works in non-login sessions.
cat << 'EOF' >> ~/.bashrc # Fix for systemd user services (rootless Podman/systemctl) export XDG_RUNTIME_DIR=/run/user/$(id -u) export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus EOF source ~/.bashrc
Create the host directories
The containers write to these host paths as their own user, so they need the right owner, whatever format you use. Quadlet does not even create a missing one.
mkdir -p /audiobooks # podman unshare chown UID:GID /audiobooks # owner unknown: the user the container writes as mkdir -p /podcasts # podman unshare chown UID:GID /podcasts # owner unknown: the user the container writes as
2. Choose a format and deploy
The same pod in four forms. Pick one and follow only its steps.
Each tab says which podman it needs. Check yours with podman --version:
Debian 12 ships 4.3, Ubuntu 24.04 4.9, Debian 13 5.4, Fedora 5.8, Arch 6.x.
Kubernetes YAML for podman kube play, started by systemd through a Quadlet .kube unit (podman 4.4+; older podman like Debian 12 uses podman-kube@, see the version check below).
Kubernetes YAML
apiVersion: v1
kind: Pod
metadata:
name: audiobookshelf
labels:
app: audiobookshelf
spec:
restartPolicy: Always
containers:
- name: audiobookshelf
image: ghcr.io/advplyr/audiobookshelf:latest
imagePullPolicy: Always
ports:
- containerPort: 80
hostPort: 13378
env:
- name: TZ
value: Europe/Vienna
volumeMounts:
- name: vol-0
mountPath: /config
- name: vol-1
mountPath: /metadata
- name: vol-2
mountPath: /audiobooks
readOnly: true
- name: vol-3
mountPath: /podcasts
readOnly: true
volumes:
- name: vol-0
persistentVolumeClaim:
claimName: audiobookshelf-config
- name: vol-1
persistentVolumeClaim:
claimName: audiobookshelf-metadata
- name: vol-2
hostPath:
path: /audiobooks
type: DirectoryOrCreate
- name: vol-3
hostPath:
path: /podcasts
type: DirectoryOrCreate
Save the YAML file
mkdir -p ~/.config/containers/ nano ~/.config/containers/audiobookshelf.yaml
Test the pod (without autostart)
podman kube play ~/.config/containers/audiobookshelf.yaml # Check status: podman pod ps && podman ps # Stop: podman kube down ~/.config/containers/audiobookshelf.yaml
Check your podman version
podman --version
4.4 or newer: continue with step 3 below.
Older, e.g. 4.3 on Debian 12? There is no Quadlet yet: use podman's own podman-kube@ service instead of steps 3–5
systemctl --user enable --now podman-kube@$(systemd-escape ~/.config/containers/audiobookshelf.yaml).service # Status & logs: systemctl --user status podman-kube@$(systemd-escape ~/.config/containers/audiobookshelf.yaml).service journalctl --user -u podman-kube@$(systemd-escape ~/.config/containers/audiobookshelf.yaml).service -f
The day-to-day commands below have a block for this service.
Create Quadlet .kube file
[Unit] Description=audiobookshelf pod After=network-online.target Wants=network-online.target [Kube] Yaml=%h/.config/containers/audiobookshelf.yaml [Service] Restart=always [Install] WantedBy=default.target
%h is a systemd home-directory specifier — do not replace it with ~/.
Save to ~/.config/containers/systemd/:
mkdir -p ~/.config/containers/systemd/ nano ~/.config/containers/systemd/audiobookshelf.kube
Start systemd service
systemctl --user daemon-reload systemctl --user start audiobookshelf.service
Status & Logs
systemctl --user status audiobookshelf.service journalctl --user -u audiobookshelf.service -f podman pod ps podman ps
Enable automatic image updates optional
Requires AutoUpdate=registry in the .kube file. Podman then checks for new images and restarts the pod automatically:
systemctl --user enable --now podman.socket systemctl --user daemon-reload systemctl --user enable --now podman-auto-update.timer # Check status: systemctl --user status podman-auto-update.timer # Trigger manually: podman auto-update
Day-to-day logs, restart, update, remove (click to open)
Logs & shell into a container
podman logs -f audiobookshelf-audiobookshelf podman exec -it audiobookshelf-audiobookshelf /bin/sh
Restart
systemctl --user restart audiobookshelf.service
Update an image by hand
Pull the new version, the restart recreates the containers from it:
podman pull <image>:<tag> systemctl --user restart audiobookshelf.service
Stop & remove
Stop through systemd, otherwise Restart=always brings the pod back. To remove it for good, delete the unit files and reload:
systemctl --user stop audiobookshelf.service rm ~/.config/containers/systemd/audiobookshelf.kube systemctl --user daemon-reload
podman older than 4.4 (podman-kube@ from step 3)
systemctl --user restart podman-kube@$(systemd-escape ~/.config/containers/audiobookshelf.yaml).service # Update an image: pull, then restart as above podman pull <image>:<tag> # Stop and remove from autostart: systemctl --user disable --now podman-kube@$(systemd-escape ~/.config/containers/audiobookshelf.yaml).service
Delete the data — permanent, only after stop & remove
podman volume rm audiobookshelf-config podman volume rm audiobookshelf-metadata podman unshare rm -rf /audiobooks podman unshare rm -rf /podcasts
Files created by containers belong to root in the user namespace — podman unshare is required to delete them.
Native systemd units without the Kubernetes YAML: one .pod plus one .container per container.
Each container becomes its own systemd service (restart, logs and ordering per container).
Needs podman 5.0 or newer — Debian 12 ships 4.3, use the Kube YAML tab there.
Save the unit files
[Unit] Description=audiobookshelf pod [Pod] PodName=audiobookshelf PublishPort=13378:80
[Unit] Description=audiobookshelf-audiobookshelf container [Container] Image=ghcr.io/advplyr/audiobookshelf:latest Pod=audiobookshelf.pod ContainerName=audiobookshelf-audiobookshelf Pull=always Environment=TZ=Europe/Vienna Volume=audiobookshelf-config:/config:Z Volume=audiobookshelf-metadata:/metadata:Z Volume=/audiobooks:/audiobooks:ro Volume=/podcasts:/podcasts:ro [Service] Restart=always [Install] WantedBy=default.target
Save all of them to ~/.config/containers/systemd/:
mkdir -p ~/.config/containers/systemd/ nano ~/.config/containers/systemd/audiobookshelf.pod nano ~/.config/containers/systemd/audiobookshelf-audiobookshelf.container
Start the pod
The pod service pulls in every container service. Autostart at boot comes from the [Install] section of the units, no enable needed.
systemctl --user daemon-reload systemctl --user start audiobookshelf-pod.service
Status & Logs
systemctl --user status audiobookshelf-pod.service journalctl --user -u audiobookshelf-audiobookshelf.service -f podman pod ps && podman ps
Day-to-day logs, restart, update, remove (click to open)
Logs & shell into a container
podman logs -f audiobookshelf-audiobookshelf podman exec -it audiobookshelf-audiobookshelf /bin/sh
Restart
systemctl --user restart audiobookshelf-pod.service
Update an image by hand
Pull the new version, the restart recreates the containers from it:
podman pull <image>:<tag> systemctl --user restart audiobookshelf-pod.service
Stop & remove
Stop through systemd, otherwise Restart=always brings the pod back. To remove it for good, delete the unit files and reload:
systemctl --user stop audiobookshelf-pod.service rm ~/.config/containers/systemd/audiobookshelf.pod rm ~/.config/containers/systemd/audiobookshelf-audiobookshelf.container systemctl --user daemon-reload
Delete the data — permanent, only after stop & remove
podman volume rm audiobookshelf-config podman volume rm audiobookshelf-metadata podman unshare rm -rf /audiobooks podman unshare rm -rf /podcasts
Files created by containers belong to root in the user namespace — podman unshare is required to delete them.
A docker-compose.yml for podman-compose. Handy if you already work with Compose; there are no per-container systemd services.
Install podman-compose as root
Run this as root, e.g. together with the prerequisites before su - audiobookshelf.
apt install -y podman-compose
Save the compose file
services:
audiobookshelf:
image: ghcr.io/advplyr/audiobookshelf:latest
pull_policy: always
restart: always
ports:
- 13378:80
environment:
- TZ=Europe/Vienna
volumes:
- audiobookshelf-config:/config:Z
- audiobookshelf-metadata:/metadata:Z
- /audiobooks:/audiobooks:ro
- /podcasts:/podcasts:ro
volumes:
audiobookshelf-config: null
audiobookshelf-metadata: null
mkdir -p ~/audiobookshelf nano ~/audiobookshelf/docker-compose.yml
Start, status & logs
cd ~/audiobookshelf podman-compose up -d podman-compose ps podman-compose logs -f # Stop: podman-compose down
Start after reboot
podman-restart.service starts every container with restart: always at boot:
systemctl --user enable podman-restart.service
Update images
cd ~/audiobookshelf podman-compose pull podman-compose up -d
Day-to-day logs, restart, update, remove (click to open)
Logs & shell into a container
podman logs -f audiobookshelf_audiobookshelf_1 podman exec -it audiobookshelf_audiobookshelf_1 /bin/sh
Restart
cd ~/audiobookshelf podman-compose restart
Stop & remove
cd ~/audiobookshelf podman-compose down
Delete the data — permanent, only after stop & remove
podman volume rm audiobookshelf_audiobookshelf-config podman volume rm audiobookshelf_audiobookshelf-metadata podman unshare rm -rf /audiobooks podman unshare rm -rf /podcasts
Files created by containers belong to root in the user namespace — podman unshare is required to delete them.
Plain podman pod create / podman create commands. Good for a quick test or to see what happens under the hood.
Save the script
#!/bin/bash set -e # ── Create named volumes ─────────────────────────────────────────── # Named volumes are created automatically — no UID/GID permission adjustment needed. podman volume exists audiobookshelf-config || podman volume create audiobookshelf-config podman volume exists audiobookshelf-metadata || podman volume create audiobookshelf-metadata # ── Create host paths ────────────────────────────────────────────── # The container process needs write access. Existing data may need chown -R. mkdir -p /audiobooks # podman unshare chown UID:GID /audiobooks # owner unknown: the user the container writes as mkdir -p /podcasts # podman unshare chown UID:GID /podcasts # owner unknown: the user the container writes as # ── Create pod ───────────────────────────────────────────────────── podman pod create \ --name audiobookshelf \ -p 13378:80 # ── Container: audiobookshelf-audiobookshelf ───────────────────────────────────────────── podman create \ --pod audiobookshelf \ --name audiobookshelf-audiobookshelf \ --restart always \ --pull always \ -e TZ='Europe/Vienna' \ -v audiobookshelf-config:/config:Z \ -v audiobookshelf-metadata:/metadata:Z \ -v /audiobooks:/audiobooks:ro \ -v /podcasts:/podcasts:ro \ ghcr.io/advplyr/audiobookshelf:latest # ── Start pod ────────────────────────────────────────────────────── podman pod start audiobookshelf # ── Stop & clean up ─────────────────────────────────────────────── # podman pod stop audiobookshelf # podman pod rm audiobookshelf
nano audiobookshelf.sh
Run it, status & logs
bash audiobookshelf.sh podman pod ps && podman ps podman logs -f <container-name>
Start after reboot
podman-restart.service starts every container with --restart always at boot:
systemctl --user enable podman-restart.service
Remove or rebuild
The script creates the pod once. To apply changes or new images, remove the pod and run the script again (named volumes stay):
podman pod rm -f audiobookshelf bash audiobookshelf.sh
Day-to-day logs, restart, update, remove (click to open)
Logs & shell into a container
podman logs -f audiobookshelf-audiobookshelf podman exec -it audiobookshelf-audiobookshelf /bin/sh
Restart
podman pod stop audiobookshelf podman pod start audiobookshelf
Stop & remove
podman pod rm -f audiobookshelf
Delete the data — permanent, only after stop & remove
podman volume rm audiobookshelf-config podman volume rm audiobookshelf-metadata podman unshare rm -rf /audiobooks podman unshare rm -rf /podcasts
Files created by containers belong to root in the user namespace — podman unshare is required to delete them.
Ports < 1024 (e.g. 80, 443)
Rootless cannot open privileged ports. Solution:
sysctl net.ipv4.ip_unprivileged_port_start=80
Make persistent in /etc/sysctl.d/99-podman.conf.
Containers communicate via localhost
All containers in the pod share the same network namespace. Always use localhost, not container names.
# Correct (e.g. app → db): localhost:5432 # Wrong (doesn't work in a pod): db-container:5432
List open ports
Which ports are the running containers publishing? See the PORTS column:
podman ps
Custom DNS for the pod
Set a custom DNS server (e.g. local Pi-hole) in the generator under DNS server. Every format picks it up.
Set volume ownership
Fix permission errors by adjusting UID/GID in the user namespace:
podman unshare chown 1000:1000 /path/to/volume
SELinux volume labels
On SELinux systems (RHEL, Fedora) set the volume suffix:
/host/path:/container/path:Z # private /host/path:/container/path:z # shared
List all volumes
podman volume ls podman volume inspect <volume-name>
Volume backup
Back up data from a named volume:
podman run --rm \ -v <volume-name>:/data:ro \ -v $(pwd):/backup \ busybox tar czf /backup/backup.tar.gz /data
Cleanup
Remove unused images, containers and volumes:
podman system prune -f # containers + images podman image prune -f # untagged images only podman volume prune -f # unused volumes
Check for outdated images
List local images; auto-update only covers units with AutoUpdate= (Kube and Quadlet tabs):
podman images podman auto-update --dry-run
All containers, including stopped ones
podman pod ps podman ps -a
Resource usage
podman stats --no-stream