Docker Daemon Mode
The podkit daemon runs as a persistent Docker service that automatically detects iPods when they’re plugged in, syncs your music collection, and ejects them when done. Notifications keep you informed at each step.
Daemon mode is opt-in — the default Docker image still runs the CLI as documented in the Docker guide. You switch to daemon mode by setting command: daemon in your Docker Compose file.
Prerequisites
- Linux Docker host — macOS and Windows Docker Desktop cannot pass USB devices to containers. Use the CLI binary directly or manually run the Docker image on those platforms.
- USB device access — the container needs
privileged: trueto detect, mount, and sync iPod block devices. See USB Passthrough for details on why this is required. - Optional: Apprise for notifications (ntfy, Slack, Discord, Telegram, etc.)
Quick Start
Create a docker-compose.yml:
services: podkit: image: ghcr.io/jvgomg/podkit:latest command: daemon restart: unless-stopped environment: - PUID=1000 - PGID=1000 - PODKIT_MUSIC_PATH=/music volumes: - /path/to/music:/music:ro privileged: truedocker compose up -dPlug in an iPod and watch the logs:
docker compose logs -fHow It Works
- The daemon polls for iPod devices every 5 seconds (configurable).
- A new iPod is detected and confirmed after 2 consecutive polls (debounced to avoid false positives from brief USB connections).
- The iPod is mounted at a unique path inside the container (e.g.,
/tmp/podkit-sdb1). - A dry-run sync runs first to preview changes.
- The actual sync executes.
- The iPod is ejected — safe to unplug.
- If configured, notifications are sent at each step via Apprise.
The daemon handles graceful shutdown on SIGTERM — if a sync is in progress when the container stops, it signals the sync to drain and save, then exits cleanly within Docker’s 10-second timeout. Completed tracks are always preserved in the iPod database.
Configuration
Daemon-Specific Variables
| Variable | Default | Description |
|---|---|---|
PODKIT_POLL_INTERVAL | 5 | How often to check for new iPod devices (seconds) |
PODKIT_APPRISE_URL | (unset) | Apprise notification endpoint URL |
Standard podkit Settings
All standard podkit configuration works the same as CLI mode — collections, quality, artwork, clean artists, and everything else. Configure via environment variables or a config file:
services: podkit: image: ghcr.io/jvgomg/podkit:latest command: daemon restart: unless-stopped environment: - PUID=1000 - PGID=1000 - PODKIT_MUSIC_PATH=/music - PODKIT_QUALITY=medium - PODKIT_ARTWORK=true - PODKIT_CLEAN_ARTISTS=true volumes: - /path/to/music:/music:ro privileged: trueOr mount a config file:
environment: - PUID=1000 - PGID=1000 volumes: - ./config:/config - /path/to/music:/music:roSee Environment Variables for the full list.
Finding Your iPod’s UUID
To configure named devices, you need each iPod’s volume UUID. Run device scan to discover it:
docker run --rm --privileged ghcr.io/jvgomg/podkit device scanThis prints each connected iPod’s volume name, UUID, size, and mount status. Copy the UUID into your config file (see below).
For JSON output (useful in scripts):
docker run --rm --privileged ghcr.io/jvgomg/podkit device scan --format jsonMultiple iPods
If you have multiple iPods, configure named devices in your config file with their volume UUIDs. When an iPod is plugged in, podkit automatically matches it by UUID and applies the correct settings.
[music.main]path = "/music"
[devices.nano]volumeUuid = "ABCD-1234"quality = "medium"music = "main"
[devices.classic]volumeUuid = "EFGH-5678"quality = "high"music = "main"artwork = trueEach device can have its own quality preset, collection, artwork setting, and more. See Config File Reference for all device-level options.
Notifications with Apprise
The daemon can send notifications via Apprise, which supports hundreds of notification services. Run Apprise as a sidecar container and point the daemon at it.
Here’s the full setup from packages/podkit-docker/docker-compose.daemon.yml:
services: podkit: image: ghcr.io/jvgomg/podkit:latest command: daemon restart: unless-stopped environment: - PUID=1000 - PGID=1000 - PODKIT_POLL_INTERVAL=5 - PODKIT_APPRISE_URL=http://apprise:8000/notify - PODKIT_MUSIC_PATH=/music volumes: - ./config:/config - /path/to/music:/music:ro privileged: true healthcheck: test: ["CMD-SHELL", "test $$(( $$(date +%s) - $$(stat -c %Y /tmp/podkit-daemon-health 2>/dev/null || echo 0) )) -lt 60 || exit 1"] interval: 30s timeout: 5s start_period: 10s retries: 3
apprise: image: caronc/apprise:latest restart: unless-stopped ports: - "8000:8000" # Optional: expose for testing environment: - APPRISE_STATELESS_URLS=ntfy://your-topicNotification Service Examples
Configure the APPRISE_STATELESS_URLS variable on the Apprise container with your service URL:
| Service | URL Format |
|---|---|
| ntfy | ntfy://your-topic |
| Slack | slack://tokenA/tokenB/channel |
| Discord | discord://webhook_id/webhook_token |
| Telegram | tgram://bot_token/chat_id |
See the Apprise wiki for the full list of supported services.
What Gets Notified
- Pre-sync: Summary of tracks to add, remove, and update
- Post-sync: Sync completed successfully with stats
- Errors: Sync failures with error details
Health Monitoring
The daemon writes a health marker file after each successful poll cycle. The Docker healthcheck verifies this file was updated within the last 60 seconds.
Add the healthcheck to your Compose file:
healthcheck: test: ["CMD-SHELL", "test $$(( $$(date +%s) - $$(stat -c %Y /tmp/podkit-daemon-health 2>/dev/null || echo 0) )) -lt 60 || exit 1"] interval: 30s timeout: 5s start_period: 10s retries: 3Check container health:
docker compose ps # Shows health statusdocker compose logs -f # Watch daemon activityUSB Passthrough
The daemon needs access to USB block devices to detect and mount iPods. The container must be able to see block devices (like /dev/sdb1) when an iPod is plugged in, read USB vendor information from /sys, and mount FAT32 filesystems.
Privileged mode (required):
privileged: truePrivileged mode is required for the daemon to work. We tested several less-privileged configurations and none are sufficient:
| Configuration | Devices visible | Can mount | Result |
|---|---|---|---|
--device /dev/bus/usb + CAP_SYS_ADMIN | lsblk sees iPod, but no /dev nodes | No | Fails |
-v /dev:/dev + CAP_SYS_ADMIN | /dev/sdb* visible | Permission denied | Fails |
Above + device_cgroup_rules: 'a *:* rwm' | /dev/sdb* visible | Permission denied | Fails |
privileged: true | Full access | Yes | Works |
The intermediate tiers fail because Docker’s default security profile (AppArmor/seccomp) blocks the mount syscall even when CAP_SYS_ADMIN is granted. Privileged mode disables these restrictions entirely.
Platform Notes
| Platform | Status | Notes |
|---|---|---|
| Linux (bare metal / VM) | Tested | Full USB passthrough works with privileged: true |
| Synology NAS (DSM 7) | Tested | Works with privileged: true via SSH / Docker Compose. See Synology setup below |
| Proxmox VM (QEMU) | Tested | Use USB passthrough to guest VM, then Docker privileged: true. Set CPU type to host |
| Proxmox LXC | Not supported | Docker-in-LXC cannot access USB block devices — use a VM instead |
| Unraid / TrueNAS | Untested | Community reports welcome |
| macOS / Windows | Not supported | Docker Desktop cannot pass USB devices |
CPU Requirements
The podkit Docker image requires a CPU with SSE4.2 support (Intel Nehalem / AMD Bulldozer or newer, circa 2008+). This is a requirement of the Bun JavaScript runtime used inside the container.
Most physical hardware from the last 15 years meets this requirement. Virtual machines may not — see Troubleshooting if you encounter issues.
Synology NAS
Tested end-to-end on a Synology DS923+ running DSM 7.3.2, including daemon auto-detection, sync, and eject.
Important: Synology’s Container Manager GUI may not support all the Docker options needed for daemon mode (specifically privileged: true). Use Docker Compose via SSH instead.
Setup
-
SSH into your Synology:
Terminal window ssh your-admin-user@your-nas -
Docker is at
/usr/local/bin/dockerbut may not be on your default SSH PATH:Terminal window export PATH=/usr/local/bin:$PATH -
Create a project directory:
Terminal window mkdir -p /volume1/docker/podkit/configcd /volume1/docker/podkit -
Find your user’s UID and GID (for
PUID/PGID):Terminal window id your-username# uid=1026(your-username) gid=100(users) ... -
Create
docker-compose.yml:services:podkit:image: ghcr.io/jvgomg/podkit:latestcommand: daemonrestart: unless-stoppedenvironment:- PUID=1026- PGID=100- PODKIT_MUSIC_PATH=/music- PODKIT_POLL_INTERVAL=5volumes:- ./config:/config- /volume1/music:/music:roprivileged: truehealthcheck:test: ["CMD-SHELL", "test $$(( $$(date +%s) - $$(stat -c %Y /tmp/podkit-daemon-health 2>/dev/null || echo 0) )) -lt 60 || exit 1"]interval: 30stimeout: 5sstart_period: 10sretries: 3 -
Start and verify:
Terminal window docker compose up -ddocker compose logs -f # Watch for iPod detectiondocker compose ps # Check health status
Synology-specific notes
- Synology uses non-standard block device names (
/dev/usb1p2instead of/dev/sdb2). podkit handles this automatically. - Privileged mode is required — Synology’s Docker has additional restrictions that prevent mounting even with
CAP_SYS_ADMIN. - Music on a Synology shared folder should be mounted read-only:
-v /volume1/music:/music:ro. Adjust the volume number to match your storage pool.
Proxmox
For Proxmox users who want to run the daemon:
- Use a VM, not an LXC container. Docker containers inside LXC cannot access USB block devices — the iPod won’t appear in
lsblkinside Docker regardless of privilege settings. - Pass the iPod through to the VM using QEMU USB passthrough (in the Proxmox GUI: VM → Hardware → Add → USB Device).
- Set the VM’s CPU type to
host(not the defaultkvm64). The default virtual CPU lacks instruction set extensions required by the podkit runtime. See Troubleshooting if you see “Illegal instruction” errors.
Troubleshooting
iPod not detected:
- Ensure the container is running with
privileged: true— this is required, not optional - Check that block devices are visible:
docker compose exec podkit lsblk - Check daemon logs:
docker compose logs podkit - If
lsblkshows nothing, the container can’t see USB block devices — verify privileged mode is enabled - On Proxmox LXC: Docker-in-LXC cannot see USB block devices. Use a VM instead
Notifications not arriving:
- Verify the Apprise URL is correct (
http://apprise:8000/notifyif using the sidecar) - Check Apprise container logs:
docker compose logs apprise - Test Apprise directly:
curl -X POST -d '{"title":"test","body":"hello"}' http://localhost:8000/notify - Ensure
APPRISE_STATELESS_URLSis set on the Apprise container with a valid notification URL
Config version error:
If you see Your config file is at version 0, but podkit requires version 1, add version = 1 as the first line of your config.toml, or run:
docker compose run --rm podkit migrateSync fails:
- Verify your music path is mounted correctly
- Check collection configuration (env vars or config file)
- Run a one-off CLI sync to isolate the issue:
docker compose run --rm podkit sync --dry-run
Illegal instruction crash:
If the container crashes immediately with Illegal instruction (core dumped), your CPU lacks the SSE4.2 instruction set required by the Bun runtime. This typically happens in virtual machines with a minimal virtual CPU:
- Proxmox: Change the VM’s CPU type from
kvm64/qemu64tohost(VM → Hardware → Processor → Type) - Other hypervisors: Ensure the guest CPU exposes SSE4.2. Passing through the host CPU features is the simplest fix
- Physical hardware: Any Intel/AMD processor from 2008 or later should work. If you’re on very old hardware, this cannot be worked around
See Also
- Docker — CLI mode with Docker
- Environment Variables — All configuration variables
- Config File Reference — Device-specific settings
- Devices — Device configuration and management