Claude_Homelab/114_koillection_deployment.md

12 KiB
Raw Blame History

114 — Koillection Deployment Guide

Status: IN PROGRESS — CT created 2026-08-11, base setup + Docker install done, NAS mount pending (Phase 4+) CT ID: 114 · IP: 192.168.1.114 Domain: collections.spendlik.sk Last updated: 2026-08-11


Overview

Koillection is a self-hosted collection manager for tracking physical collections of any kind. No pre-built metadata scrapers — metadata is added freely per item, with custom fields via templates. MIT licensed.

Collections planned for this instance:

  • 🚗 Hot Wheels (series, year, colour, condition, variants)
  • 🧱 LEGO (set number, theme, piece count, minifigures, completion status)
  • 🦇 Batmobiles (source media, scale, manufacturer, condition)
  • 📚 Comics (title, issue, publisher, language, condition)
  • 📄 Paper Models (designer, scale, subject, format — physical printed copies)

Stack: koillection/koillection (PHP/Symfony + Vue.js) + PostgreSQL 16. Uploads (item photos) bind-mounted to NAS for data safety.


Resource Allocation

Resource Allocation
CT ID 114
IP 192.168.1.114
CPUs 1
RAM 512 MB
Disk 8 GB (app + DB only; photos on NAS)
Template Debian 13 (trixie) — created from 13.1-2 (current template at time of creation; 13.6-1 is now the default for new deployments, but this doesn't matter post-creation — see Proxmox LXC Templates.md)
Privileged Yes (Docker requires it)
Nesting Enabled (features: nesting=1)

NAS Mount Planning

Photos uploaded to Koillection land in /uploads inside the container. This will be bind-mounted from the NAS at:

/volume1/proxmox/data/koillection/uploads

Create this directory on the NAS before deployment:

# On the Synology NAS (SSH or File Station)
mkdir -p /volume1/proxmox/data/koillection/uploads

⚠️ The NAS path follows the same convention as Paperless (/volume1/proxmox/data/<service>). Be consistent.

Host-side mount: verified 2026-08-11 — the Proxmox storage ID is spendlik-nas, mounted at /mnt/pve/spendlik-nas on the host (confirmed via live ls /mnt/pve/). CT 111 (Paperless) uses /mnt/pve/spendlik-nas/data/paperless as its exact host-side bind-mount path — Koillection follows the identical pattern in Phase 4 below.


Phase 1 — Create LXC Container DONE (2026-08-11)

pct create 114 local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst \
  --hostname koillection \
  --cores 1 \
  --memory 512 \
  --swap 512 \
  --rootfs local-lvm:8 \
  --net0 name=eth0,bridge=vmbr0,ip=192.168.1.114/24,gw=192.168.1.1 \
  --unprivileged 0 \
  --features nesting=1 \
  --ostype debian \
  --start 1

Enter the container:

pct enter 114

Phase 2 — Base Setup DONE (2026-08-11)

apt update && apt upgrade -y
apt install -y nano curl ca-certificates gnupg lsb-release

Phase 3 — Install Docker DONE (2026-08-11)

install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/debian \
  $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null

apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

Verify:

docker run --rm hello-world

Phase 4 — NAS Bind Mount

Add the NAS uploads path as a Proxmox bind mount. Exit the container first:

exit

On the Proxmox host:

pct set 114 --mp0 /mnt/pve/spendlik-nas/data/koillection/uploads,mp=/uploads,shared=1

Verified 2026-08-11 against live ls /mnt/pve/ (only spendlik-nas present) and cross-checked against CT 111 (Paperless)'s actual documented host mount (/mnt/pve/spendlik-nas/data/paperless) — this replaces an earlier placeholder path in this guide that would have failed (wrong storage name).

⚠️ Make sure /volume1/proxmox/data/koillection/uploads exists on the NAS first (see "NAS Mount Planning" above) before running this — if it hasn't been created yet, do that via NAS SSH/File Station first.

Re-enter the container and verify the mount is visible:

pct enter 114
ls /uploads

Phase 5 — Deploy Koillection

mkdir -p /opt/koillection
cd /opt/koillection
nano .env

Paste (fill in a strong password for DB_PASSWORD):

DB_DRIVER=pdo_pgsql
DB_NAME=koillection
DB_HOST=db
DB_PORT=5432
DB_USER=koillection
DB_PASSWORD=CHANGE_ME
DB_VERSION=16
APP_ENV=prod
APP_DEBUG=0
APP_SECRET=CHANGE_ME_32CHAR_RANDOM_STRING
PHP_TZ=Europe/Bratislava
HTTPS_ENABLED=0

Generate APP_SECRET with: openssl rand -hex 16

nano docker-compose.yml

Paste:

services:
  koillection:
    image: koillection/koillection:latest
    container_name: koillection
    restart: unless-stopped
    ports:
      - "8080:80"
    env_file:
      - .env
    volumes:
      - /uploads:/uploads
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16
    container_name: koillection-db
    restart: unless-stopped
    env_file:
      - .env
    environment:
      - POSTGRES_DB=${DB_NAME}
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - ./volumes/postgresql:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-q", "-d", "koillection", "-U", "koillection"]
      timeout: 45s
      interval: 10s
      retries: 10

Start:

docker compose up -d
docker compose logs -f

Wait until the koillection container logs settle (Symfony app startup). Then verify locally:

curl -s http://localhost:8080 | grep -i koillection

Phase 6 — nginx Reverse Proxy (CT 101)

Enter CT 101:

pct enter 101
nano /etc/nginx/sites-available/koillection

Paste:

server {
    listen 80;
    server_name collections.spendlik.sk;

    location / {
        proxy_pass http://192.168.1.114:8080;
        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;
        client_max_body_size 20M;
    }
}

client_max_body_size 20M — item photos can be large. Adjust upward if needed.

Enable and reload:

ln -s /etc/nginx/sites-available/koillection /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

Phase 7 — SSL Certificate

Still in CT 101:

certbot --nginx -d collections.spendlik.sk

⚠️ Always inspect the config after certbot:

cat /etc/nginx/sites-available/koillection

Check for duplicate server_name directives and missing closing braces. Fix manually if needed.

Also set HTTPS_ENABLED=1 in /opt/koillection/.env in CT 114, then restart:

# In CT 114
cd /opt/koillection
nano .env   # set HTTPS_ENABLED=1
docker compose restart koillection

Phase 8 — DNS Record

Updated 2026-08-10: All *.spendlik.sk subdomains are now CNAME records pointing at the root spendlik.sk. Only the root spendlik.sk A record holds an IP — WebSupport rejects any duplicate IP value elsewhere in the zone. Do not create an A record for this subdomain.

In WebSupport admin panel:

  1. Add CNAME record: collectionsspendlik.sk
  2. Check both DNS management pages
  3. Note the numeric record ID
  4. Add to 00_index.md DNS table

No DDNS updater step is needed for this subdomain. ddns-update.sh on CT 108 only updates the root A record on IP change; this CNAME resolves through automatically.


Phase 9 — Authelia Protection (CT 102)

Enter CT 102, edit /etc/authelia/configuration.yml. Add to access_control.rules:

- domain: collections.spendlik.sk
  policy: two_factor

Koillection has its own internal login system. Authelia adds a second layer before users even reach the login page. Since this is a personal single-user instance, you may prefer Authelia bypass and rely on Koillection's own login instead — your call.

Restart Authelia after editing:

docker compose restart

Add the Authelia middleware to the nginx vhost in CT 101 (follow the pattern from other protected services).


Phase 10 — First Login & Initial Setup

Open https://collections.spendlik.sk from mobile data (hairpin NAT — never test from LAN).

On first load, Koillection will prompt you to create an admin account. Do so, then:

  1. Set your timezone to Europe/Bratislava in profile settings
  2. Set the display currency if tracking purchase values
  3. Set visibility defaults (private by default is fine for a personal instance)

Phase 11 — Collection Setup

Recommended collection structure. Create each as a top-level Collection:

🚗 Hot Wheels

Suggested item fields (via Template):

  • Series / Line
  • Year of release
  • Colour
  • Casting name
  • Country of manufacture
  • Condition (Mint / Good / Played)
  • Treasure Hunt (yes/no)

🧱 LEGO

Suggested item fields:

  • Set number
  • Theme
  • Sub-theme
  • Piece count
  • Minifigure count
  • Year
  • Completion status (Sealed / Built / Parts only)
  • Instruction booklet present (yes/no)

💡 The set number field makes cross-referencing with kocka-novinky.sk and Brickset API straightforward.

🦇 Batmobiles

Suggested item fields:

  • Source (Film / TV / Comics / Game)
  • Year of appearance
  • Manufacturer (Hot Wheels / Corgi / LEGO / custom)
  • Scale
  • Condition

📚 Comics

Suggested item fields:

  • Title / Series
  • Issue number
  • Publisher
  • Language
  • Year
  • Condition (Mint / Very Good / Good / Fair)
  • Story arc

📄 Paper Models (physical hardcopy)

Suggested item fields:

  • Designer / Publisher
  • Subject (aircraft, ship, building…)
  • Scale
  • Format (magazine supplement / standalone / kit)
  • Build status (Unbuilt / Built / Display)

💡 Tags are cross-collection in Koillection — tag items with #display, #wishlist, #for-sale etc. to group across all five collections at once.


Backup

The only things that need backing up:

  1. PostgreSQL database — contains all collection metadata
  2. NAS uploads directory — contains all item photos (already on NAS, covered by NAS backup)

Add a daily DB dump to cron in CT 114:

crontab -e

Add:

0 3 * * * docker exec koillection-db pg_dump -U koillection koillection > /opt/koillection/backups/koillection-$(date +\%Y\%m\%d).sql 2>/dev/null
mkdir -p /opt/koillection/backups

⚠️ Always back up the database before upgrading Koillection — the developer notes that data migrations can occasionally have edge cases.


Gotchas

Issue Fix
Photos not saving Verify /uploads bind mount is writable inside the container
App not starting Check docker compose logs koillection — usually a DB connection issue on first boot
certbot corrupts nginx config Always inspect after issuance
Large photo uploads rejected Increase client_max_body_size in nginx vhost
HTTPS redirect loop Set HTTPS_ENABLED=1 in .env and restart the koillection container after SSL is in place
DNS record type Use CNAME → spendlik.sk, never a per-subdomain A record (see Phase 8)
Wrong template filename Verify exact template string with pveam list local before pct create — versions bump periodically
Wrong NAS host mount path Storage ID is spendlik-nas, mounted at /mnt/pve/spendlik-nas — verify with ls /mnt/pve/ before trusting any guide's hardcoded path