Media & Archives¶
📚 Things I Want To Keep¶
I grouped these together because they all deal with keeping stuff around: books, comics, downloads, web pages, and other bits I do not want to lose track of.
They are a good reminder that some services are mostly about storage layout, library paths, and persistence rather than raw horsepower.
🗃️ ArchiveBox¶
![]()
Web Archive¶
ArchiveBox is what I use when a bookmark feels too optimistic. I like having a stack that can actually save the thing, not just point at where it used to live.
Config¶
This is a multi-container stack: the main app, Sonic for search, noVNC for browser access, and PyWB for WARC playback. It is a nice example of one product getting real value from helper services instead of pretending everything fits cleanly in one container.
Compose¶
name: archivebox
networks:
archivebox:
name: archivebox
services:
archivebox:
image: archivebox/archivebox:${ARCHIVEBOX_VERSION:-latest}
networks:
- archivebox
ports:
- ${ARCHIVEBOX_PORT:-8000}:8000
volumes:
- ${ARCHIVEBOX_DATA_PATH}:/data
environment:
- LISTEN_HOST=0.0.0.0:8000
- ALLOWED_HOSTS=*
- CSRF_TRUSTED_ORIGINS=https://${ARCHIVEBOX_URL},http://${ARCHIVEBOX_URL}
- PUBLIC_INDEX=False
- PUBLIC_SNAPSHOTS=False
- PUBLIC_ADD_VIEW=False
- SEARCH_BACKEND_ENGINE=sonic
- SEARCH_BACKEND_HOST_NAME=sonic
- SEARCH_BACKEND_PASSWORD=${ARCHIVEBOX_SEARCHBACKEND_PASSWORD}
- YTDLP_MAX_SIZE=1024m
- TIMEOUT=120
- CHECK_SSL_VALIDITY=True
- SAVE_ARCHIVEDOTORG=False
restart: unless-stopped
sonic:
image: archivebox/sonic:${ARCHIVEBOX_SONIC_VERSION:-latest}
networks:
- archivebox
expose:
- 1491
environment:
- SEARCH_BACKEND_PASSWORD=${ARCHIVEBOX_SEARCHBACKEND_PASSWORD}
volumes:
- ${ARCHIVEBOX_DATA_PATH}/sonic:/var/lib/sonic/store
restart: unless-stopped
novnc:
image: theasp/novnc:${ARCHIVEBOX_NOVNC_VERSION:-latest}
networks:
- archivebox
environment:
- DISPLAY_WIDTH=1920
- DISPLAY_HEIGHT=1080
- RUN_XTERM=no
ports:
- ${ARCHIVEBOX_NOVNC_PORT:-8080}:8080
restart: unless-stopped
pywb:
image: webrecorder/pywb:${ARCHIVEBOX_PYWB_VERSION:-latest}
entrypoint: /bin/sh -c '(wb-manager init default || test $$? -eq 2) && wb-manager add default /archivebox/archive/*/warc/*.warc.gz; wayback;'
environment:
- INIT_COLLECTION=archivebox
ports:
- ${ARCHIVEBOX_PYWB_PORT:-8686}:8080
volumes:
- ${ARCHIVEBOX_DATA_PATH}:/archivebox
- ${ARCHIVEBOX_DATA_PATH}/wayback:/webarchive
restart: unless-stopped
Example env¶
ARCHIVEBOX_VERSION=latest
ARCHIVEBOX_SONIC_VERSION=latest
ARCHIVEBOX_NOVNC_VERSION=latest
ARCHIVEBOX_PYWB_VERSION=latest
ARCHIVEBOX_DATA_PATH="/srv/archivebox"
ARCHIVEBOX_PORT=8000
ARCHIVEBOX_URL=archivebox.example.com
ARCHIVEBOX_SEARCHBACKEND_PASSWORD=change-me
ARCHIVEBOX_NOVNC_PORT=8080
ARCHIVEBOX_PYWB_PORT=8686
Vars¶
| Variable | Purpose | Why it matters |
|---|---|---|
ARCHIVEBOX_VERSION |
Main image tag | Pin if you want steady upgrades |
ARCHIVEBOX_DATA_PATH |
Data directory | All archives and helper data live here |
ARCHIVEBOX_PORT |
Main web UI port | App access |
ARCHIVEBOX_URL |
Public hostname | Needed for trusted origins |
ARCHIVEBOX_SEARCHBACKEND_PASSWORD |
Sonic password | Protects the search backend connection |
ARCHIVEBOX_NOVNC_PORT |
noVNC port | Remote browser access |
ARCHIVEBOX_PYWB_PORT |
Playback port | Exposes the replay interface |
📚 Komga¶
Comic Library¶
Komga is the one I actually use now. I like it because it shows what is really in my files instead of depending too much on smart matching, which makes it a lot more reliable for niche manga and odd library entries that metadata services do not recognize properly.
Config¶
The structure is similar to Kavita: one config volume, one library mount, one web port, plus a small app flag to allow OAuth-backed account creation.
Compose¶
name: komga
volumes:
komga:
name: ${KOMGA_VOLUME:-komga}
services:
komga:
image: gotson/komga:${KOMGA_VERSION:-latest}
container_name: komga
volumes:
- komga:/config
- ${LIBRARY_PATH:?LIBRARY_PATH;No library path specified}:/mnt/library:z
environment:
TZ: ${TZ:-Etc/UTC}
KOMGA_OAUTH2_ACCOUNT_CREATION: true
ports:
- ${KOMGA_PORT:-25600}:25600
restart: unless-stopped
Example env¶
LIBRARY_PATH="/srv/library"
TZ="Asia/Tokyo"
KOMGA_VERSION=latest
KOMGA_PORT=25600
KOMGA_VOLUME=komga
Vars¶
| Variable | Purpose | Why it matters |
|---|---|---|
KOMGA_VERSION |
Image tag | App version selection |
KOMGA_PORT |
Host port | Web UI access |
KOMGA_VOLUME |
Config volume | Keeps settings and metadata |
LIBRARY_PATH |
Host library path | Source of the collection |
TZ |
Timezone | Operational consistency |
📖 Kavita¶
Smart Matching¶
Kavita is still the easier one to manage when its metadata matching works well. It can make a library feel cleaner with less manual effort, especially for books and more common manga, but I ran into enough niche titles not being recognized that I ended up sticking with Komga for daily use.
Config¶
This is a clean single-service recipe: one config volume, one bind mount for the real library, one web port, and a timezone setting for cleaner logs.
Compose¶
name: kavita
volumes:
kavita:
name: ${KAVITA_VOLUME:-kavita}
services:
kavita:
image: jvmilazz0/kavita:${KAVITA_VERSION:-latest}
container_name: kavita
volumes:
- kavita:/kavita/config
- ${LIBRARY_PATH:?LIBRARY_PATH;No library path specified}:/mnt/library:z
environment:
TZ: ${TZ:-Etc/UTC}
ports:
- ${KAVITA_PORT:-5000}:5000
restart: unless-stopped
Example env¶
LIBRARY_PATH="/srv/library"
TZ="Asia/Tokyo"
KAVITA_VERSION=latest
KAVITA_PORT=5000
KAVITA_VOLUME=kavita
Vars¶
| Variable | Purpose | Why it matters |
|---|---|---|
KAVITA_VERSION |
Image tag | Version control |
KAVITA_PORT |
Host port | Web UI access |
KAVITA_VOLUME |
Config volume | Keeps app state between restarts |
LIBRARY_PATH |
Host library path | The actual content source |
TZ |
Timezone | Useful for logs and schedules |
⬇️ JDownloader 2 And qBittorrent¶

Download Pair¶
I keep JDownloader 2 and qBittorrent together because between the two of them, they cover almost all of my personal download needs. They also show a setup pattern I reuse a lot: shared content path, separate app state, and explicit user mapping so permissions stay sane.
For JDownloader, I use the popular Docker image by jlesage because it is well-known, easy to run, and just works without much drama.
Config¶
Both containers use the same downloads directory from the host but keep separate named volumes for their own configuration. That split is exactly what I want: shared content, separate app state.
Compose¶
name: downloader
volumes:
jdownloader:
name: ${JDOWNLOADER_VOLUME:-jdownloader}
qbittorrent:
name: ${QBITTORENT_VOLUME:-qbittorrent}
services:
jdownloader:
container_name: jdownloader
image: jlesage/jdownloader-2:${JDOWNLOADER_VERSION:-latest}
ports:
- ${JDOWNLOADER_PORT:-5800}:5800
volumes:
- jdownloader:/config
- ${DOWNLOADS_PATH:?DOWNLOADS_PATH;No download path specified}:/output:z
environment:
TZ: ${TZ:-Etc/UTC}
UMASK: 0000
PUID: ${UID:-1000}
PGID: ${GID:-1000}
KEEP_APP_RUNNING: 1
DARK_MODE: 1
restart: unless-stopped
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:${QBITTORENT_VERSION:-latest}
container_name: qbittorrent
volumes:
- qbittorrent:/config
- ${DOWNLOADS_PATH:?DOWNLOADS_PATH;No download path specified}:/downloads:z
environment:
TZ: ${TZ:-Etc/UTC}
UMASK: 0000
PUID: ${UID:-1000}
PGID: ${GID:-1000}
ports:
- ${QBITTORENT_PORT:-8080}:8080
- 6881:6881
- 6881:6881/udp
restart: unless-stopped
Example env¶
UID=1000
GID=1000
DOWNLOADS_PATH="/srv/downloads"
TZ="Asia/Tokyo"
JDOWNLOADER_VERSION=latest
JDOWNLOADER_PORT=5800
JDOWNLOADER_VOLUME=jdownloader
QBITTORENT_VERSION=latest
QBITTORENT_PORT=8080
QBITTORENT_VOLUME=qbittorrent
Vars¶
| Variable | Purpose | Why it matters |
|---|---|---|
DOWNLOADS_PATH |
Shared download directory | Main place where files land |
UID / GID |
Host user and group IDs | Prevents permission surprises |
TZ |
Timezone | Keeps logs and schedules sane |
JDOWNLOADER_VERSION / QBITTORENT_VERSION |
Image tags | Version control for each app |
JDOWNLOADER_PORT / QBITTORENT_PORT |
Web ports | Access to both interfaces |
JDOWNLOADER_VOLUME / QBITTORENT_VOLUME |
Config volumes | Preserve app state |