<!-- diataxis: tutorial -->

# 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

```bash
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 {doc}`../how-to/develop` for details.
:::

## Step 2: start the stack

```bash
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:

```bash
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:
```bash
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

1.
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

```bash
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

- {doc}`../how-to/install` to install both packages in your own environment
- {doc}`../how-to/configure-thumbor` for the full list of Thumbor settings
- {doc}`../how-to/configure-plone` for Plone-side environment variables and
  registry settings
- {doc}`../how-to/deploy-production` for a production deployment checklist
