Nextcloud Preview Generation in Environments with Limited Resources
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:
| Format | Requires FFmpeg? | Note |
|---|---|---|
| BMP, GIF, JPEG, PNG, XBitmap | No | Processed using the PHP extension Imagick, which is already included in the Nextcloud Community image |
| HEIC | No | Also via Imagick. Requires libheif support, which is included in the current Community Nextcloud image. For older images, a separate package may be required. |
| Krita | No | Rendering via Imagick |
| MarkDown, TXT, OpenDocument | No | Text-based rendering, no external media libraries |
| MP3 | Yes | FFmpeg extracts any embedded cover art or generates a placeholder image |
| Movie | Yes | FFmpeg 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 pullregistry.example.com/nextcloud:34on 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)