Comics & Manga Server
    Docker Compose

    Deploy Komga on a VPS

    Self-host Komga, a comics, manga, and ebook server with OPDS and Kobo sync, on a RamNode VPS with Docker Compose, a tuned JVM heap, and Nginx TLS.

    Komga is a self-hosted media server for comics, manga, BDs, magazines, and ebooks. It reads CBZ, CBR, PDF, and EPUB, imports metadata embedded in ComicInfo.xml and EPUB OPF files, organizes series into collections and read lists, and serves everything through a responsive web reader, a REST API, OPDS v1 and v2 feeds, and native Kobo sync. It is written in Kotlin on the JVM, which makes it heavier at idle than some alternatives but very stable under large libraries.

    This guide deploys Komga on a single RamNode VPS with Docker Compose, a dedicated service user, an explicit JVM heap limit, Nginx terminating TLS, and the container bound to localhost.

    Architecture Overview

    • Komga container. A Spring Boot application serving the web UI, the REST API, OPDS feeds, Kobo sync endpoints, and a Server-Sent Events stream that pushes scan progress and library changes to open browser tabs.
    • /config. Holds Komga's SQLite databases, the Lucene search index, logs, and the optional application.yml configuration file.
    • /data. The library root inside the container. This guide mounts /srv/media/comics there. You can add more mounts for separate libraries.
    • Nginx. Handles TLS and proxies to 127.0.0.1:25600. It needs buffering disabled for the event stream and larger header buffers if you use Kobo sync.

    Komga uses SSE rather than WebSockets. A proxy that buffers responses holds those events back, so the UI looks frozen during scans even though Komga is working. Step 6 handles this.

    What You Will Need

    • A RamNode VPS running Ubuntu 24.04 LTS with at least 2 GB RAM and 1 vCPU. 4 GB is more comfortable for libraries in the tens of thousands of books, or if Komga shares the box with other services. The JVM will use whatever heap it is allowed, so you set a limit explicitly in Step 4.
    • Disk sized for your library. If your plan supports additional block storage volumes, put media there.
    • Root or sudo access.
    • A domain or subdomain with an A record pointing at the VPS. This guide uses komga.example.com.

    Step 1: Prepare the System

    shell
    apt update && apt upgrade -y
    apt install -y ca-certificates curl gnupg ufw rsync
    timedatectl set-timezone America/New_York

    Add swap. On a 2 GB plan this is not optional. A JVM heap plus page cache plus a large first scan will otherwise trigger the OOM killer.

    shell
    fallocate -l 2G /swapfile
    chmod 600 /swapfile
    mkswap /swapfile
    swapon /swapfile
    echo '/swapfile none swap sw 0 0' >> /etc/fstab

    Optional: Mount a Block Storage Volume for Media

    shell
    lsblk
    mkfs.ext4 /dev/vdb
    mkdir -p /srv/media
    echo "UUID=$(blkid -s UUID -o value /dev/vdb) /srv/media ext4 defaults,nofail 0 2" >> /etc/fstab
    mount -a
    df -h /srv/media

    Keep /opt/komga/config on the root disk. SQLite and the Lucene index both expect local storage.

    Step 2: Install Docker

    shell
    install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
    chmod a+r /etc/apt/keyrings/docker.asc
    
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
    https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
      > /etc/apt/sources.list.d/docker.list
    
    apt update
    apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    systemctl enable --now docker

    Cap container log size.

    shell
    cat > /etc/docker/daemon.json <<'EOF'
    {
      "log-driver": "json-file",
      "log-opts": { "max-size": "10m", "max-file": "3" }
    }
    EOF
    systemctl restart docker

    Step 3: Create the Service User and Directories

    Komga's image supports running as an arbitrary UID through the Compose user: directive. Run it as a dedicated system user rather than root.

    shell
    useradd --system --no-create-home --shell /usr/sbin/nologin komga
    
    mkdir -p /opt/komga/{config,tmp}
    mkdir -p /srv/media/comics
    
    chown -R komga:komga /opt/komga /srv/media/comics

    Write the IDs to an env file for Compose.

    shell
    cat > /opt/komga/.env <<EOF
    KOMGA_UID=$(id -u komga)
    KOMGA_GID=$(id -g komga)
    TZ=$(timedatectl show -p Timezone --value)
    EOF
    cat /opt/komga/.env

    Step 4: Write the Compose File

    shell
    pico /opt/komga/compose.yaml
    shell
    services:
      komga:
        image: gotson/komga:latest
        container_name: komga
        user: "${KOMGA_UID}:${KOMGA_GID}"
        environment:
          - TZ=${TZ}
          - JAVA_TOOL_OPTIONS=-Xmx1g
        ports:
          - "127.0.0.1:25600:25600"
        volumes:
          - /opt/komga/config:/config
          - /opt/komga/tmp:/tmp
          - /srv/media/comics:/data
        restart: unless-stopped

    What each choice does:

    • JAVA_TOOL_OPTIONS=-Xmx1g. Caps the JVM heap. Use 1g on a 2 GB plan, 2g on 4 GB. If you see OutOfMemoryError in the logs during scans, raise it. Leaving it unset lets the JVM size the heap from total RAM, which leaves too little for the OS and page cache on small plans.
    • /opt/komga/tmp:/tmp. Komga extracts archive pages to temp during analysis and thumbnail generation. Mapping it to the host keeps that I/O off the container's overlay layer.
    • 127.0.0.1:25600:25600. Docker's port publishing bypasses UFW. Binding to loopback keeps the port private to Nginx.
    • Library mount is read-write. Komga only writes to library files if you enable features that need it, such as deleting duplicate pages, removing empty directories, or writing metadata back. If you will never use those, add :ro to the /data mount.
    • Pin a version. For production, replace latest with a specific x.y.z tag from Docker Hub or the GitHub releases page.

    Start it.

    shell
    cd /opt/komga
    docker compose up -d
    docker compose logs -f

    The first start takes noticeably longer than later ones while the database initializes. Wait for the Spring Boot "Started" line, exit the log stream, and check it locally.

    shell
    curl -sI http://127.0.0.1:25600 | head -1

    Step 5: Configure the Firewall

    shell
    apt install -y nginx certbot python3-certbot-nginx
    ufw allow OpenSSH
    ufw allow 'Nginx Full'
    ufw enable
    ufw status

    Step 6: Configure the Nginx Reverse Proxy

    shell
    pico /etc/nginx/sites-available/komga
    shell
    server {
        listen 80;
        listen [::]:80;
        server_name komga.example.com;
    
        client_max_body_size 500M;
    
        # Kobo devices send very large request headers during sync.
        # Default buffers reject them with 502 or 400 errors.
        proxy_buffer_size        128k;
        proxy_buffers            4 256k;
        proxy_busy_buffers_size  256k;
        large_client_header_buffers 4 32k;
    
        location / {
            proxy_pass http://127.0.0.1:25600;
            proxy_http_version 1.1;
    
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Host $host;
            proxy_set_header X-Forwarded-Port $server_port;
    
            # Server-Sent Events. Without this, scan progress and
            # library updates arrive in bursts or not at all.
            proxy_buffering off;
            proxy_cache off;
            proxy_set_header Connection '';
            proxy_read_timeout 1h;
        }
    }

    The X-Forwarded-* headers matter more for Komga than most apps. Komga builds absolute URLs in OPDS feeds and Kobo sync responses from them. If they are missing, reader apps receive http://127.0.0.1:25600/... links that do not resolve.

    Enable the site and issue a certificate.

    shell
    ln -s /etc/nginx/sites-available/komga /etc/nginx/sites-enabled/
    rm -f /etc/nginx/sites-enabled/default
    nginx -t && systemctl reload nginx
    
    certbot --nginx -d komga.example.com --redirect -m you@example.com --agree-tos --no-eff-email
    systemctl list-timers | grep certbot

    Step 7: Optional application.yml

    Komga reads /config/application.yml at startup. Most settings live in the web UI, but a few are only configurable here. The useful one on a public server is a fixed remember-me key, so "remember me" logins survive container restarts.

    shell
    pico /opt/komga/config/application.yml
    shell
    komga:
      remember-me:
        key: replace-with-output-of-openssl-rand-hex-32
        validity: 2592000   # 30 days, in seconds
    shell
    openssl rand -hex 32
    chown komga:komga /opt/komga/config/application.yml
    cd /opt/komga && docker compose restart

    Step 8: First-Run Setup

    Browse to https://komga.example.com. The first visit asks you to create the initial account, which becomes the admin. Use a strong password.

    Add a library from the + next to Libraries in the sidebar:

    1. Name it and set the root folder to /data, the container path.
    2. Review the scanner options. Scan on startup and the periodic scan interval control how quickly new files appear. For a library you add to often, a shorter interval works. For a mostly static one, daily is plenty.
    3. Under metadata, enable import of ComicInfo.xml and EPUB metadata if your files carry it.
    4. Save. Komga scans immediately, then analyzes each book to count pages and generate thumbnails. Analysis is the slow part on large libraries.

    Create non-admin users under Server > Users. Each user can be limited to specific libraries, age ratings, and labels.

    Step 9: Load Your Library

    Komga treats every folder that directly contains books as a series. Nest as deep as you like for your own organization.

    shell
    /srv/media/comics/
    ├── Marvel/
    │   └── Saga of the Swamp Thing (1982)/
    │       ├── Saga of the Swamp Thing 001 (1982).cbz
    │       └── Saga of the Swamp Thing 002 (1982).cbz
    └── Manga/
        └── Vinland Saga/
            ├── Vinland Saga v01.cbz
            └── Vinland Saga v02.cbz

    Here Saga of the Swamp Thing (1982) and Vinland Saga are series. Marvel and Manga are just folders. Books sort by filename unless ComicInfo.xml supplies a number.

    Upload with rsync and fix ownership.

    shell
    rsync -avP --partial ~/Comics/ root@your-vps-ip:/srv/media/comics/
    chown -R komga:komga /srv/media/comics

    Trigger a scan from the library menu or wait for the scheduled one.

    Step 10: OPDS, API Keys, and Kobo Sync

    OPDS. Komga serves two feeds:

    shell
    https://komga.example.com/opds/v1.2/catalog
    https://komga.example.com/opds/v2/catalog

    Most reader apps use v1.2. Authenticate with your Komga username and password, or with an API key generated under your account settings, which you can revoke without changing your password.

    Kobo sync. Komga can act as a sync server for Kobo e-readers, pushing EPUBs from read lists to the device and syncing progress back. Enable it per user in the account settings, follow the on-screen instructions to point the device's sync endpoint at your Komga URL, and keep the larger header buffers from Step 6 in place. Kobo sync is the feature most sensitive to proxy misconfiguration, so test it with one book before relying on it.

    Backups

    Stop the container for a consistent copy of the databases and search index.

    shell
    cd /opt/komga
    docker compose stop
    tar czf /root/komga-config-$(date +%F).tar.gz config
    docker compose start

    Automate it with a cron entry and copy the archives off the server.

    shell
    cat > /etc/cron.d/komga-backup <<'EOF'
    30 3 * * * root cd /opt/komga && docker compose stop >/dev/null && tar czf /root/komga-config-$(date +\%F).tar.gz config && docker compose start >/dev/null && find /root -name 'komga-config-*.tar.gz' -mtime +14 -delete
    EOF

    The downtime is a few seconds plus JVM startup. The library is not included. Keep originals elsewhere.

    Upgrading

    shell
    cd /opt/komga
    docker compose pull
    docker compose up -d
    docker image prune -f

    If you pinned a tag, update compose.yaml first. Komga runs database migrations on startup that cannot be reversed. Back up before every upgrade and read the release notes, especially across major versions.

    Troubleshooting

    Container exits or restarts during a large scan. Check docker compose logs --tail=200 for OutOfMemoryError. Raise -Xmx, make sure swap is active with swapon --show, and if needed move to a larger plan. Also check dmesg | grep -i oom for the kernel killing the container from outside the JVM.

    Scan progress never updates in the browser. SSE is being buffered. Confirm proxy_buffering off is present in the server block Certbot edited: nginx -T | grep proxy_buffering.

    OPDS clients get broken links pointing at 127.0.0.1. The X-Forwarded-* headers are missing. Add them, reload Nginx, and re-add the feed in the reader app.

    Kobo sync fails with 502 or "request header too large". Increase the header buffer sizes in Step 6. Watch /var/log/nginx/error.log during a sync attempt to see which limit is being hit.

    Files show as "Unknown" or with zero pages. The book failed analysis. Usually a corrupt archive or an unsupported format inside the archive. Check the book's detail page for the error, and test the file locally.

    Permission errors in the logs. The library or config directory is not owned by the komga UID. Rerun the chown commands from Step 3 and Step 9.

    Hardening Notes

    • Confirm loopback binding with ss -tlnp | grep 25600.
    • Use API keys rather than your password for OPDS clients, and revoke them when a device is retired.
    • Mount the library read-only unless you use Komga's file-modifying features.
    • Use SSH keys and disable password login.
    • For personal use, consider WireGuard or Tailscale in front and no public DNS record.

    Next Steps

    Komga and Kavita overlap heavily. Komga is the stronger choice for Western comics with rich ComicInfo.xml metadata and for Kobo owners. Kavita is lighter on RAM and has a more polished EPUB reader. Both have their own RamNode guides, and you can point both at the same library to compare before committing. Add Audiobookshelf on its own subdomain to cover audiobooks and podcasts from the same VPS.