Quickstart: image scaling with Thumbor in 5 minutes

What you will build

In this tutorial you will run a Plone 6 site with Thumbor-based image scaling, all backed by a single PostgreSQL database. You will upload an image, see Plone redirect to a signed Thumbor URL, and verify that Thumbor scales the image on the fly.

By the end you will have a four-container stack (PostgreSQL, Plone, Thumbor, nginx) where Plone never touches image bytes – every @@images request becomes a 302 redirect to Thumbor.

Prerequisites

  • Docker and Docker Compose v2+

  • A web browser

  • A terminal

  • About 2 GB free disk space (for Docker images)

Step 1: clone the repository

git clone https://github.com/bluedynamics/plone-pgthumbor.git
cd plone-pgthumbor/tryout

All remaining commands assume you are inside the tryout/ directory. This setup installs all packages from PyPI – no source checkout required.

Note

For development with local source installs, use the development/ directory instead. See Set up a development environment for details.

Step 2: start the stack

docker compose up -d --build

The Thumbor service uses the pre-built image from GHCR (ghcr.io/bluedynamics/zodb-pgjsonb-thumborblobloader), available for linux/amd64 and linux/arm64. Only the Plone service is built locally.

This starts four services:

Service

Port

Description

postgres

5434

PostgreSQL 17 with zodb-pgjsonb storage

plone

8080

Plone 6.2 backend (via nginx)

thumbor

8888

Thumbor 7 image processor (pre-built from GHCR)

nginx

8080

Reverse proxy (public entry point)

Wait until all services are healthy:

docker compose ps

Plone may take 30–60 seconds on first start while it creates the Plone site and installs the plone.pgthumbor:default and plone.pgcatalog:default GenericSetup profiles.

Tip

Watch the Plone logs to see when startup is complete:

docker compose logs -f plone

Look for the line Listening on 0.0.0.0:8080.

Step 3: log in to Plone

Open http://localhost:8080 in your browser.

Log in with the default credentials:

  • Username: admin

  • Password: admin

You should see the Plone site root.

Step 4: upload an image

Click Add new… > Image in the toolbar. 2. Give it a title, for example “Test Image.” 3. Choose any JPEG or PNG file from your computer. 4. Click Save.

Plone displays the image on the content view.

Step 5: inspect the Thumbor URL

Right-click the displayed image and choose Copy Image Address (or Open Image in New Tab).

The URL will look like this:

http://localhost:8080/thumbor/<hmac>/300x200/<zoid_hex>/<tid_hex>

Key observations:

  • The path starts with /thumbor/ – nginx routes this to the Thumbor service.

  • The <hmac> segment is the HMAC-SHA1 signature (generated by Plone using the shared PGTHUMBOR_SECURITY_KEY).

  • 300x200 (or similar) is the target size Plone requested.

  • <zoid_hex>/<tid_hex> identifies the blob in PostgreSQL.

Plone itself returned a 302 redirect to this URL. It never read the blob data – Thumbor fetches it directly from the blob_state table in PostgreSQL.

Step 6: verify scaling works

Open a new browser tab and paste the Thumbor URL. You should see the scaled image.

Try modifying the size in the URL manually. For example, change 300x200 to 100x100 – Thumbor will return a 403 Forbidden because the new URL has an invalid HMAC signature. This proves that the security key prevents arbitrary transformations.

Tip

The example stack sets ALLOW_UNSAFE_URL=True in the Thumbor container environment. In production this must be False – all URLs must be signed. You can test unsigned URLs by prefixing the path with /unsafe/ instead of an HMAC, but only while unsafe mode is enabled.

Step 7: clean up

docker compose down -v

This removes all containers and the PostgreSQL data volume. Omit -v if you want to keep the data for next time.

What you learned

  • plone.pgthumbor replaces Plone’s @@images view with a 302 redirect to Thumbor

  • Image bytes never pass through the Plone process – Thumbor reads them directly from PostgreSQL via zodb-pgjsonb-thumborblobloader

  • nginx serves as a reverse proxy, routing /thumbor/ to the Thumbor service and everything else to Plone (with VirtualHostMonster rewriting)

  • HMAC-signed URLs prevent unauthorized image transformations

Next steps