
Introduction
I recently set up my own manga library. I download the manga, put it into Komga running on Kubernetes, and read it on my iPhone with KMReader. The files and reading progress stay under my control, and the whole setup is easier to use than I expected.
How I use it
The workflow is pretty simple:
Download manga from a manga website
↓
Upload it to Komga's /data directory
↓
Let Komga scan and organize it
↓
Read it on an iPhone with KMReader
Komga handles the server side. It scans local directories, sorts manga into series and books, and manages covers, metadata, and reading progress.
KMReader is a native Komga client for iOS. It can stream books or download the whole book for offline reading. Reading progress syncs back to Komga automatically. I use the version from the Chinese App Store:
I will not go into where the manga files come from. Pay attention to copyright and try to organize only content you have the right to access.
Why I use Komga
At first, I just wanted a manga reader for iOS. Then I realized that keeping files only on my phone was awkward. Switching devices, clearing the cache, and syncing reading progress were all annoying.
Komga moves those jobs to the server. It supports cbz, zip, cbr, rar, pdf, and epub. It also has a browser reader, though I normally use KMReader.
It also fits my existing self-hosted setup. The service runs in K8s, Traefik exposes it over HTTPS, and ArgoCD manages the configuration. It works the same way as my other services, so I do not need to maintain a separate setup just for it.
K8s deployment
The official Komga Docker documentation has a few important details:
- The service listens on port
25600 /configstores the database and configuration/datastores the manga files- The container can run as
1000:1000 /configshould use a local filesystem, not CIFS or NFS
I use a StatefulSet with one replica. There is only one Volume, split into config and data directories with subPath.
Here is the main configuration:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: komga
namespace: app
spec:
serviceName: komga
replicas: 1
podManagementPolicy: OrderedReady
updateStrategy:
type: RollingUpdate
selector:
matchLabels:
app: komga
template:
metadata:
labels:
app: komga
spec:
securityContext:
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
initContainers:
- name: init-storage
image: busybox:1.37.0
command:
- sh
- -c
- mkdir -p /storage/config /storage/data && chown -R 1000:1000 /storage/config /storage/data
securityContext:
runAsUser: 0
runAsGroup: 0
allowPrivilegeEscalation: false
volumeMounts:
- name: storage
mountPath: /storage
containers:
- name: komga
image: gotson/komga:1.26.3
imagePullPolicy: IfNotPresent
securityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
allowPrivilegeEscalation: false
ports:
- name: http
containerPort: 25600
env:
- name: TZ
value: Asia/Shanghai
- name: JAVA_TOOL_OPTIONS
value: -Xmx1536m
volumeMounts:
- name: storage
mountPath: /config
subPath: config
- name: storage
mountPath: /data
subPath: data
volumeClaimTemplates:
- metadata:
name: storage
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 100Gi
The StatefulSet creates a PVC named storage-komga-0. /config and /data remain separate directories inside the container, but they share one underlying Volume. Backups are simpler too, since I can back up the whole Volume together.
The initContainer creates the directories and fixes their permissions. Komga itself still runs as 1000:1000, so the main container does not need root access.
The Service is a regular ClusterIP:
apiVersion: v1
kind: Service
metadata:
name: komga
namespace: app
spec:
type: ClusterIP
selector:
app: komga
ports:
- name: http
port: 80
targetPort: http
Traefik handles external access. The address looks like this:
https://komga.example.com
Creating the manga library
Once Komga is running, open the web interface and create the administrator account.
Click the + next to Libraries in the sidebar and create a manga library. Set the Root Folder to:
/data
Komga treats each subdirectory under the library as a series, so it is better to organize the folders first:
/data/
├── One Piece/
│ ├── One Piece Volume 001.epub
│ ├── One Piece Volume 002.epub
│ └── One Piece Volume 003.epub
├── Naruto/
│ ├── Naruto Volume 001.cbz
│ └── Naruto Volume 002.cbz
└── One-Punch Man/
└── One-Punch Man Volume 001.cbz
Do not dump every file directly into the /data root. It may look fine at first, but it gets messy once the library grows.
Uploading manga
Komga’s web interface is not a cloud drive. It does not have a regular button for uploading local files to the server. It scans directories that already exist on the server.
I currently use kubectl cp. First, create a directory for the series:
kubectl exec -n app komga-0 -- mkdir -p "/data/One Piece"
Then copy the local file into it:
kubectl cp "/path/to/One Piece Volume 001.epub" \
app/komga-0:"/data/One Piece/One Piece Volume 001.epub"
After the upload, run Scan Library Files in Komga. It finds the new file, analyzes its format and page count, and generates a cover.
For larger batches, you can keep a separate import directory:
/data/import
/data/library
Set the library root to /data/library and upload files to /data/import first. In Import > Books, scan that directory, choose the destination series, then move, copy, or hard-link the files into place. Both directories are on the same Volume, so hard links can work here.
I do not bother with the extra step for now. I upload each book straight into its series directory under /data, then run a scan.
Connecting KMReader
Open KMReader on the iPhone and tap + to add a server.
Enter the server address:
https://komga.example.com
Use the root address. There is no need to add /opds. You can sign in with a Komga username and password or an API key. Once connected, the libraries, covers, and reading progress sync to the app.
KMReader has three offline policies for each series:
- Manual: download books manually
- Latest: automatically keep the latest books downloaded
- All: download the entire series
I prefer Manual. I cache only the books I plan to read, which saves space on the phone. They still work without a connection, and KMReader syncs the progress back to Komga when the phone is online again.
Problems I ran into
Problem 1. There is no regular upload button in Komga
I spent a while looking for one in the web interface. Komga does not accept browser uploads like a cloud drive. It scans files that are already in a server directory. In K8s, the easiest option is kubectl cp, or you can mount a separate download directory.
Problem 2. Each series needs its own directory
Komga organizes series based on parent directories. If the folders are messy, fixing them later takes more work. I rename the directories and files after downloading them instead of cleaning everything up in the admin interface.
Problem 3. Do not put /config on network storage
The official documentation specifically says that /config must use a local filesystem. It contains Komga’s database and configuration. I use local-path and keep it on the same local Volume as the manga data.
Problem 4. One Volume needs subPath to separate the directories
I did not want to maintain two PVCs, so I created one called storage. It is mounted at /config and /data inside the container. Without subPath, both mount points would show the same root directory and the directory layout would get mixed together.
Problem 5. KMReader needs the server root address
KMReader talks directly to the Komga API. It is not a generic OPDS reader. Use an address such as https://komga.example.com. Adding the OPDS path prevents it from connecting to the regular Komga API.
How it feels to use
Now I download a manga, put it under /data/manga-name/, and run a scan in Komga. It then appears in KMReader on my iPhone. If I want to read offline, I download it first. I do not have to keep track of my reading progress myself either.
There is no complicated architecture here. One StatefulSet, one Volume, and one domain name. Since I already run K8s and ArgoCD, the maintenance cost is basically negligible.
I may automate the download, directory cleanup, and library scan later. For now, manual uploads are good enough. I would rather read the manga first.
Feel free to follow my blog at www.bboy.app
Have Fun
