Ramblings of an aging IT geek
← Ramblings of an aging IT geek
homelab

nextcloud, again, but properly this time

My third attempt at self-hosting Nextcloud, this time with a dedicated database, Redis, and no illusions about the all-in-one image.

Server rack with cabling

This is the third time I've stood up Nextcloud. The first two collapsed under their own weight: slow file scans, mysterious 504s, a database that I'd let limp along on SQLite because the quick-start guide let me. Each time I told myself I'd do it properly later. This is later.

The honest root cause of both previous failures was the same: I treated Nextcloud like a small app when it's actually a small ecosystem. It wants a real database, it wants Redis for locking and caching, and it wants you to take cron seriously. Skip any of those and it works fine for a fortnight, then degrades exactly when you've started trusting it with things you care about.

the actual stack

No all-in-one image this time. I want each piece visible and replaceable, so it's the FPM image behind its own web server, with Postgres and Redis as separate services on the shared Compose network.

services:
  app:
    image: nextcloud:30-fpm
    restart: unless-stopped
    depends_on:
      - db
      - redis
    environment:
      - POSTGRES_HOST=db
      - REDIS_HOST=redis
    volumes:
      - ./data:/var/www/html

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - ./pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    restart: unless-stopped

The FPM split means a separate web container talks to PHP-FPM over the network, which is more moving parts but means the web server is just a web server. When something's slow I can tell whether it's PHP or the proxy, which on the previous attempts I genuinely could not.

Homelab shelf

the bits everyone skips

Two settings fixed most of the historical pain.

First, memory caching. Pointing Nextcloud at Redis for both the local cache and file locking turns the admin overview page from a wall of warnings into a single reassuring tick. APCu for the local cache, Redis for distributed locking.

Second, background jobs via system cron rather than the AJAX default. The AJAX mode only runs jobs when someone loads a page, which on a single-user instance means basically never, which means previews never generate and cleanup never happens. A proper cron container hitting cron.php every five minutes makes the whole thing feel alive.

*/5 * * * * php -f /var/www/html/cron.php

It's running now, the overview is all green ticks, and the file scan on my photo library finished in minutes rather than the hours it used to take. Whether this one survives is a question for future me, but for the first time I've built it like I expect it to last, which is probably the difference. The previous two were always temporary in my head, and software has a way of living down to your expectations.