I’ve been running my own Nextcloud instance for several years now, and the performance of my Synology DS1019+ has actually always been sufficient for its operation; it’s just that the preview generation for photos, videos, and other media types has never worked particularly well. The reason for this was simply that the built-in Intel Celeron J3455, with its four cores, couldn’t handle Nextcloud’s regular on-demand preview generation fast enough, and since the Spectre and Meltdown mitigations were enabled, the problem got even worse. Aside from that, I was also bothered by the fact that no previews were generated for videos and audio files.

In this post, I’d like to briefly explain how I solved this problem using my own Docker image, FFmpeg, and the Preview Generator app from the Nextcloud App Store.

The Problem: On-Demand Preview Generation

By default, Nextcloud generates previews on demand the first time a file is accessed. With high-performance hardware, this isn’t a problem. On my Synology, however, this led to CPU load spikes nearing 100% and timeouts for requests as soon as multiple images were requested at the same time. This typically occurred while scrolling in the Photos app.

The solution is to generate previews before they’re actually needed. To do this, I use the Preview Generator app, which is available directly from the Nextcloud App Store. The app monitors file events (new uploads, modifications) and stores them in a queue. A cron job then processes this queue in the background, for example, at times when the system is idle anyway.

Which format requires which dependency?

Not every preview provider requires the same system libraries. Before we get to the Dockerfile, it’s worth taking a look at the configured formats in Nextcloud’s config.php (here’s an example of the ones I’ve enabled in my instance):

'enabledPreviewProviders' =>
  array (
    0 => 'OC\\Preview\\BMP',
    1 => 'OC\\Preview\\GIF',
    2 => 'OC\\Preview\\JPEG',
    3 => 'OC\\Preview\\Krita',
    4 => 'OC\\Preview\\MarkDown',
    5 => 'OC\\Preview\\MP3',
    6 => 'OC\\Preview\\OpenDocument',
    7 => 'OC\\Preview\\PNG',
    8 => 'OC\\Preview\\TXT',
    9 => 'OC\\Preview\\XBitmap',
    10 => 'OC\\Preview\\HEIC',
    11 => 'OC\\Preview\\Movie',
  ),

The following overview shows which dependencies are required in each case:

FormatRequires FFmpeg?Note
BMP, GIF, JPEG, PNG, XBitmapNoProcessed using the PHP extension Imagick, which is already included in the Nextcloud Community image
HEICNoAlso via Imagick. Requires libheif support, which is included in the current Community Nextcloud image. For older images, a separate package may be required.
KritaNoRendering via Imagick
MarkDown, TXT, OpenDocumentNoText-based rendering, no external media libraries
MP3YesFFmpeg extracts any embedded cover art or generates a placeholder image
MovieYesFFmpeg extracts a single frame from the video file as a thumbnail

FFmpeg is used exclusively for the MP3 and Movie formats. All image formats are processed using the Imagick extension, which is already included in the base image. If you only use photos and don’t need MP3 or video previews, you can skip installing FFmpeg entirely.

I’ve been using this setup since Nextcloud 29. In that version and all newer versions, Imagick with libheif support was already included in the Community Image, so HEIC previews worked out of the box.

Build Your Own Docker Image

Since the Nextcloud Community Docker Image does not include FFmpeg, I’m building my own image. The Dockerfile is intentionally minimal:

ARG NEXTCLOUD_VERSION

FROM nextcloud:${NEXTCLOUD_VERSION}-apache AS apache

RUN apt-get update && apt-get upgrade -y && apt-get clean
RUN apt-get install -y --no-install-recommends ffmpeg && apt-get clean

Two comments on this:

Why apt-get upgrade -y? I want to install all available security patches, not just those for Nextcloud itself. This also includes the base system and all transitive dependencies. The trade-off is a slightly longer build time and a larger image, but the security benefit justifies it for me.

Why --no-install-recommends? This ensures that only the strictly necessary FFmpeg dependencies are installed, not any recommended but non-essential packages. This keeps the image leaner.

I then push the image to a private Docker repository. When pulling it to the NAS, you must first run docker login to the registry:

docker login registry.example.com -u <USER> -p <PASSWORD>
docker pull registry.example.com/nextcloud:34

In my setup, I use Portainer, which allows you to configure private registries directly on the NAS.

CI/CD and Version Control

In my setup, the build is controlled by a CI/CD pipeline that passes the Git tag as a build argument.

Of course, you can also build and push your image locally:

docker build --build-arg NEXTCLOUD_VERSION=34.0.3 \
  -t registry.example.com/nextcloud:34.0.3 \
  -t registry.example.com/nextcloud:34 \
  .

This allows for two strategies:

  • Patch Version Tag (e.g., 34.0.3): A build specifically designed for a particular version.
  • Major Version Tag (e.g., 34): This is overwritten with every rebuild. This way, I can simply pull registry.example.com/nextcloud:34 on the NAS and always use the latest patch version within that major version.

A major update for example from 34 to 35 does not break the image, since Nextcloud automatically performs any necessary database migrations when the container starts. It is sufficient to trigger the build with the new tag and deploy the updated image, as long as the upstream image already provides the version number.

I usually update the image manually once a week. Before doing so, I check the changelogs and release notes to see if they contain any breaking changes or important security fixes. An automated CI/CD pipeline that builds the image regularly would be an option, but I currently prefer to maintain manual control and run the pipeline on demand.

Set Up the Preview Generator App

You can install the Preview Generator app in the container using the following command:

docker exec --user www-data <NEXTCLOUD_CONTAINER> php occ app:install previewgenerator

You can then trigger the initial generation of previews for all existing files:

docker exec --user www-data <NEXTCLOUD_CONTAINER> php occ preview:generate-all

Depending on the number of files for which a preview needs to be generated, this step may take some time.

preview:pre-generate is used for ongoing operations. This command processes only files that have been added or modified.

According to the Preview Generator app documentation, it is recommended to disable the internal background job if you are using your own system cron job:

docker exec --user www-data <NEXTCLOUD_CONTAINER> php occ config:app:set \
  --value=true --type=boolean previewgenerator job_disabled

The System Cron job on the NAS looks like this:

*/5 * * * * docker exec --user www-data <NEXTCLOUD_CONTAINER> php occ preview:pre-generate

Every 5 minutes, the system checks for new or modified files and generates previews for them. This interval was deliberately chosen to ensure that new previews are created quickly. Since new files are usually uploaded via the mobile app or the desktop client, I want the previews to be generated shortly after the upload so that they are available when I open the web interface or use the photo gallery in the mobile app.

You can check the status of the queue at any time:

docker exec --user www-data <NEXTCLOUD_CONTAINER> php occ preview:queue-stats

Since the job runs every 5 minutes, but the generation process can take longer when there are many new files, it makes sense to adjust the generation timeout accordingly so that the job does not run multiple times in parallel when the system cron executes it again.

docker exec --user www-data <NEXTCLOUD_CONTAINER> php occ config:app:set \
  --value=300 --type=integer previewgenerator job_max_execution_time

Depending on how the instance is used and by how many users, it may also make sense to run the cron job overnight; for my instance, where I am the only user, the setup described above has proven effective.

Conclusion

Ever since I’ve been using this setup, previews in my Nextcloud instance have been working much more smoothly. The Photos app loads instantly because the thumbnails are already there. Video previews, which were completely missing before, are also generated thanks to FFmpeg.

The effort required is limited to a minimal Dockerfile, an optional CI/CD pipeline for the build, and a Cron job. The ongoing effort involves regularly updating the image manually, though this can also be automated if desired.

If you’re running Nextcloud on less powerful hardware or have many concurrent users and are struggling with slow or missing previews, this approach is worth trying.

Translated with www.DeepL.com/Translator (free version)