Penpot 2.19: What to Check Before You Upgrade

Penpot 2.19: What to Check Before You Upgrade

Penpot 2.19.0 shipped on November 9, 2026 (2.19.1 followed a day later), with configurable keyboard shortcuts, per-side stroke widths and better path editing. For most teams it's a normal update. For two groups it isn't: anyone running a large instance, and anyone whose Docker setup predates the Admin Console.

Both have to act before pulling the new image. Here's how to tell whether that's you, and what to do.

What's New for Designers

The headline features from the 2.19.0 release notes:

  • Configurable keyboard shortcuts, so people coming from Figma can keep their muscle memory.
  • Per-side stroke widths: separate top, right, bottom and left borders on one shape.
  • Better path editing, with improved path operations in the editor.
  • Design tokens across files, with support for internal libraries and file sync for tokens.

The release also fixes a long list of bugs, including a few security-relevant ones: missing permission checks in the plugin API and WebSocket subscriptions, and view-only share links that could reveal unshared library pages.

Change 1: Where Penpot Stores File Data

Until now, Penpot kept file contents in more than one place. 2.19 moves every file state and snapshot into a single file_data table, and deletes old "orphan" revision rows that nothing read and nothing ever cleaned up. There's no change to the interface or API, and you get some disk space back.

The catch is how the move happens. On small and medium instances, it runs automatically when the new backend starts, and you don't have to do anything. On large instances, which the release notes describe as "millions of snapshots, hundreds of GB in TOAST", the work doesn't fit in the startup transaction. Those instances must run the migration by hand before upgrading, using the large-instance runbook.

Do You Need the Runbook?

Ask your database. Replace penpot-postgres with your database service name:

docker compose exec -T penpot-postgres psql -U penpot -d penpot -c "
SELECT (SELECT count(*) FROM file_change WHERE data IS NOT NULL AND label IS NOT NULL) AS snapshots,
       (SELECT count(*) FROM file_change WHERE data IS NOT NULL AND label IS NULL)     AS orphans,
       (SELECT count(*) FROM file WHERE data IS NOT NULL)                              AS files,
       pg_size_pretty(pg_total_relation_size('file_change')) AS file_change_size;"

The first three columns are the same counters the runbook uses to track its progress. The runbook gives no exact cut-off, so read the size against its own description. Thousands of rows and a few gigabytes: let the automatic migration handle it. Millions of rows and hundreds of gigabytes: follow the runbook.

What the Runbook Does

If you're in the second group, set aside some time for this. The runbook copies the data in small batches while Penpot stays online:

  1. Mark the migration as applied, so a backend restart halfway through doesn't start the automatic version.
  2. Create temporary indexes that track only the rows still waiting.
  3. Run three batch loops, deleting orphans, moving snapshots and moving files, each repeated until it returns 0.
  4. Verify all counters read 0, drop the temporary indexes and vacuum the tables.
  5. Upgrade normally. The new version sees the migration as done and skips it.

It's careful work: one batch per transaction, and the runbook tells you to stop and investigate if a batch fails on a duplicate key rather than retry blindly. Take a full database backup before you start.

Change 2: The Admin Console Is Now On by Default

The Admin Console arrived in 2.18 as an option. In 2.19 it's enabled by default, and the enable-admin-console flag is no longer needed. That means your Docker setup now needs:

  • a penpot-admin-console service, and
  • PENPOT_ADMIN_CONSOLE_URI set in both the frontend and backend, pointing at it over the internal network.

The official compose file sets it to this, in both places:

PENPOT_ADMIN_CONSOLE_URI: http://penpot-admin-console:3000

Copy the full service definition from the current official compose file rather than writing it by hand, and match its image tag to your Penpot version.

Don't want the console at all? Add disable-admin-console to PENPOT_FLAGS, remove the penpot-admin-console service and its depends_on entry in penpot-frontend, and leave PENPOT_ADMIN_CONSOLE_URI empty.

The Upgrade Itself

Once both changes are handled:

docker compose -f docker-compose.yaml pull
docker compose -p penpot -f docker-compose.yaml up -d
docker compose -p penpot logs -f penpot-backend

Watch the backend logs until it finishes starting. Penpot's docs also recommend upgrading in small steps, so if you're several versions behind, don't jump straight to 2.19.

Before any of this, back up both volumes from the default setup: the PostgreSQL database and the uploaded assets. Penpot recommends copying volume data through a temporary container, not copying the volume folder directly.

Running It on Elestio

Penpot on Elestio starts at $16/month for the VM, with automated backups included. Before upgrading, open Update config in your dashboard and check whether your docker-compose.yml already has a penpot-admin-console service. If it doesn't, either add one or disable the console as described above.

For a large instance, rehearse on a copy first: use the Clone button, run the database check on the clone, and time the runbook there. Or ask Elestio AI DevOps to handle the upgrade. It takes a backup before touching anything.

Troubleshooting

The backend takes forever to start, or times out, after upgrading. The automatic migration is running on more data than it can comfortably handle. That's the large-instance case. Stop it, check the logs, and if you're unsure what state the database is in, restore your backup. Then follow the runbook before upgrading again.

Compose errors or a broken Admin Console after the upgrade. Check for a missing penpot-admin-console service, a leftover depends_on entry, or an unset PENPOT_ADMIN_CONSOLE_URI. Or disable the console.

A runbook batch fails on a duplicate key. Stop. The runbook has queries to find the overlapping rows. Don't retry blindly.

A plugin misbehaves after the upgrade. 2.19 added permission checks to the plugin API, so a plugin that relied on the old behavior may now be refused. Check for a plugin update.

Thanks for reading ❤️ See you in the next one 👋