How to choose self-hosted Penpot: Docker, collaboration, and developer handoff for an open-source design tool

A practical guide to penpot/penpot: what it is, when it fits, where to start self-hosting, design systems, Inspect Mode, and deployment boundaries for team collaboration.

Penpot is an open-source platform designed for product design and code collaboration. Self-hosted is not a single-container drawing tool: the official Compose runs frontend, backend, Exporter, MCP, Postgres, Valkey, and other services simultaneously, and stores the database and uploaded materials in persistent volumes. Therefore, after deployment, container health, HTTP access, registration policies, persistent volumes, and recovery processes must be verified simultaneously.

Project Address:

https://github.com/penpot/penpot

Official website:

https://penpot.app

Quick Conclusion

  • Personal trials can directly access Penpot SaaS; Self-hosting is required for data control, intranet deployment, or compliance boundaries.
  • The official Docker method requires Compose V2 and listens on http://localhost:9001 by default.
  • The downloaded sample Compose is for local trial and must be replaced before public network deploymentPENPOT_SECRET_KEY, Public URI, email, and security cookie settings.
  • Backups cannot be saved alonedocker-compose.yaml, and also cover Postgres withpenpot_assetsEndurance roll.

Download the official Compose and verify it first

1
2
3
4
5
6
7
docker --version
docker compose version
mkdir penpot-selfhost
cd penpot-selfhost
curl -o docker-compose.yaml https://raw.githubusercontent.com/penpot/penpot/main/docker/images/docker-compose.yaml
docker compose -p penpot -f docker-compose.yaml config --services
docker compose -p penpot -f docker-compose.yaml config --volumes

config must exit with code zero. The service list should include the frontend, backend, database, and supporting components; the volume list should include Postgres data and assets. If YAML parsing fails, do not continue to up -d.

Keep the original file before modification to facilitate recovery:

1
cp docker-compose.yaml docker-compose.yaml.original

Modifying Default Security Configuration Before Launch

The official example warns that public deployments must not retain disable-secure-session-cookies or disable-email-verification. Replace these placeholders as well:

  1. PENPOT_PUBLIC_URI: Change to the final HTTPS domain.
  2. PENPOT_SECRET_KEY: do not leave it as change-this-insecure-key.
  3. PENPOT_FLAGS: Decide whether to allow registration, verify emails, and enable MCP.
  4. SMTP: In formal environments, do not treat Mailcatch as a real email service.

Generate random secrets using Python:

1
python3 -c "import secrets; print(secrets.token_urlsafe(64))"

Run againdocker compose ... config, confirm that the variable can still be parsed after replacement. Do not submit Compose files containing Secret to public repositories.

Start and validate each service

1
2
3
4
docker compose -p penpot -f docker-compose.yaml up -d
docker compose -p penpot -f docker-compose.yaml ps
docker compose -p penpot -f docker-compose.yaml logs --tail 100 penpot-backend
curl -I http://localhost:9001

Acceptance cannot be based solely on frontend return 200.docker compose psThere should be no services that continuously restart or exit, and backend logs should not repeatedly show database connection, migration, or secret errors.

If the page cannot be opened, check in order:

1
2
3
4
docker compose -p penpot -f docker-compose.yaml ps -a
docker compose -p penpot -f docker-compose.yaml logs --tail 200 penpot-frontend
docker compose -p penpot -f docker-compose.yaml logs --tail 200 penpot-backend
docker compose -p penpot -f docker-compose.yaml logs --tail 100 penpot-postgres

If the frontend is normal but login or save fails, you should usually continue to check the backend and Postgres, rather than just restarting the browser.

Create the first controlled account

Public network instances are not recommended to keep anonymous registration open for a long time. After closing registration, you can use the backend management command to create an account. First, find the actual container name:

1
2
docker compose -p penpot -f docker-compose.yaml ps
docker exec -ti penpot-penpot-backend-1 python3 manage.py create-profile

Compose versions may use hyphens or underscores in container names, so do not copy the second command blindly. If the container is not found, use the name shown by docker compose ps. The management command also requires prepl-server on the backend.

Validate the HTTPS reverse proxy

Penpot’s public URI, browser-actually accessed domain name, and reverse proxy TLS domain must be consistent. After deploying the proxy, check from the external network:

1
curl -I https://design.example.com

When a redirect loop occurs, check firstPENPOT_PUBLIC_URIprotocol headers passed with the proxy; If you disconnect immediately after logging in, focus on checking for secure cookies and HTTPS. Do not reopen insecure cookies just for temporary use.

Validate design delivery with a minimal project

After completing the basic deployment, create a new testing team and documentation to at least verify:

  1. Two accounts can join the same team and see changes in real time.
  2. Create a component, a variant, and at least one Design Token.
  3. Development accounts can read SVG, CSS, or layout information in Inspect Mode.
  4. Export one.penpotfile, then imported into the test space.
  5. After uploading an image, refresh the page, but the material remains accessible.

These operations cover collaboration, the database, and the asset volume. Creating an empty file alone does not prove that the self-hosted data path works.

Back up the database and asset volume

The default Compose file uses two key volumes: Postgres data and penpot_assets. First, read their actual names:

1
2
docker compose -p penpot -f docker-compose.yaml config --volumes
docker volume ls --filter label=com.docker.compose.project=penpot

Pausing writing and recording version before backup:

1
2
docker compose -p penpot -f docker-compose.yaml images
docker compose -p penpot -f docker-compose.yaml stop

Then, follow the official Docker volume backup process to archive the database volume and the material volume separately. The backup files must be stored on the host or remote server, not in the container. After completion, restart and recheck:

1
2
3
docker compose -p penpot -f docker-compose.yaml start
docker compose -p penpot -f docker-compose.yaml ps
curl -I http://localhost:9001

Only archives that have actually been restored in the isolated environment are considered usable backups. Restoration acceptance should open the original test file, check the components, and confirm that the uploaded image still exists.

Prepare rollback points before updating

Do not overwrite the Compose file and immediately run pull. Preserve the current configuration, image information, and volume backups first:

1
2
3
4
5
cp docker-compose.yaml docker-compose.yaml.before-upgrade
docker compose -p penpot -f docker-compose.yaml images
docker compose -p penpot -f docker-compose.yaml pull
docker compose -p penpot -f docker-compose.yaml up -d
docker compose -p penpot -f docker-compose.yaml logs --tail 200 penpot-backend

Major version migrations may continue to run after startup. At this point, a page temporarily unavailable does not mean you should restart repeatedly; First, look at the migration logs. If the new version continues to fail, service should be stopped, the old Compose and matching image versions restored, and then the pre-upgrade volume backup should be restored. After the database has been migrated, simply reverting to the old image may not be safe.

Build a long-term design-to-development workflow

Teams can implement the process as follows:

  1. Designers create a Design System, with component names as closely as possible matching the frontend component library.
  2. Use Design Tokens for color, font size, and spacing, rather than scattered hand-filled values.
  3. Develop SVG, CSS, and layout information through Inspect Mode.
  4. Use test files to validate plugins, APIs, or MCPs to avoid directly operating with formal design assets.
  5. Before major version upgrades, export key files and back up server-side persistent volumes.

Fault Diagnosis Table

Phenomenon Key Examination Recovery of Movement
localhost:9001No response Frontend port, container state, host firewall Recreate the container after restoring the original Compose
Page can be opened but cannot be logged in Backend logs, Secret, Cookie, public URI Restoring consistent domain name and security cookie configuration
Invitation emails not received SMTP and email verification flags Fix SMTP, do not disable authentication for long-term bypassing
Files can be opened but images are missing penpot_assetsVolume or object storage Restore a backup of materials at the same time point in time as the database
Backend repeatedly restarts after upgrade Database migration and image version Stop writing and use the complete backup and recovery set before upgrade

Penpot self-hosted completion criteria are: HTTPS domain availability, clear registration boundaries, two test users collaborating, Inspect Mode delivering styles, successful import and export, and both databases and materials having completed recovery drills. If any of these are missing, you should not migrate directly into the team’s only formal design files.