CHECKING STATUS
I AM LISTENING TO
|

Day 51: Penpot – Self-Hosted Open-Source Design & Prototyping – 7 Days of Docker

LAST MODIFIED: 25. July 2026
23. July 2026
.SHARE

Table of Contents

Design tools have quietly become one of the most locked-in parts of a modern team’s workflow. The files live in someone else’s cloud, access is billed per editor seat, and the moment you stop paying, the door to your own work can close behind you. For a small studio or an internal team, that model gets expensive and uncomfortable fast, especially when the design files are effectively company intellectual property sitting on infrastructure you do not control.

Penpot takes the opposite position. It is an open-source design and prototyping platform that you can run on your own server, built on open web standards so the files are not trapped in a proprietary format. It covers the same core ground as the popular SaaS design tools – vector design, prototyping, real-time collaboration, and a developer-friendly inspect mode – but the whole thing is a set of Docker containers you deploy yourself.

This article walks through a practical Docker Compose deployment of Penpot: a clean self-contained stack you can copy onto a server today, a full breakdown of the environment variables and volumes that actually matter, how to create your first user, and a reverse-proxy troubleshooting section drawn from real-world deployment notes. The reverse-proxy material is included here specifically because Penpot behind a proxy trips people up in very consistent ways.

Also went into a deep DEEP rabbit hole the past few days … Why Your Penpot Animated GIF Freezes in Chrome — and the SVG Trick That Wakes It Up + Video / Lottie support + CLI API connector ;) My vacation timeout …

Quick Win: What This Article Delivers

By the end of this article, you will have a working Penpot deployment pattern you can reuse:

  • A self-contained Docker Compose stack with frontend, backend, exporter, PostgreSQL, and Redis.
  • A clear explanation of PENPOT_SECRET_KEY, PENPOT_FLAGS, PENPOT_PUBLIC_URI, and the database, Redis, storage, and SMTP variables.
  • Two reliable ways to create your first user: temporary open registration or the backend create-profile command.
  • A concrete understanding of why the shared assets volume matters and why the frontend now listens on internal port 8080.
  • A reverse-proxy troubleshooting section covering 502 errors, host not found in upstream, and the buffer tuning that keeps authentication working.

What Does This Thing Actually Do?

Penpot is a full design and prototyping application that runs entirely on your own infrastructure. It is not a lightweight sketch tool bolted onto a wiki. It is a real design environment aimed at product teams, with the distinguishing feature that it is open source and self-hostable.

Key capabilities:

  • Vector design and prototyping – Draw interfaces, build components, and wire up interactive prototypes in one tool.
  • Real-time collaboration – Multiple people can work in the same file at once, with live cursors and shared editing.
  • Open web standards – Penpot is built around SVG, so what you design maps directly to the primitives browsers already understand.
  • Open source – Penpot is released under the Mozilla Public License. The full source is public, which is why self-hosting is a first-class option rather than an afterthought.
  • Teams and projects – Work is organized into teams, projects, and files, with invitations and role-based access.
  • Developer-friendly inspect – Developers can inspect designs and pull code hints, CSS, and measurements without an editor seat.
  • Flexible layout system – Penpot 2.0 introduced CSS Grid and Flex layout directly in the design canvas, so layouts behave the way the front end will.
  • Plugins and automation – A plugin system and an MCP integration allow extending Penpot and connecting it to other tooling.
  • Self-hosted control – Because it runs on your server, your design files stay on infrastructure you own and can wall off from the public internet.

A Little Background

Penpot is developed by Kaleidos, a company with a long history in open-source product development. The project’s core pitch has always been the gap between design and development: designers work in one proprietary silo, developers translate the results by hand, and the handoff loses fidelity. By building on SVG and open web standards, Penpot tries to make the design file something a developer can actually reason about instead of a flattened export.

The Penpot 2.0 release was a significant milestone. It brought a redesigned interface and, more importantly, a native CSS Grid and Flexbox layout system inside the design tool. That matters because it means the layout logic a designer builds lines up with the layout model the browser uses, rather than being an approximation that developers reinterpret later.

Penpot is open-source software released under the Mozilla Public License, which is why a real self-hosted deployment is available and supported rather than a limited community build. For teams with strict data-control requirements, or anyone who simply does not want their design library living in a third-party SaaS account, that licensing and self-hosting story is the whole point.

Docker Compose Setup

Penpot is not a single container. A working instance is a small stack of services:

  • penpot-frontend – Serves the web application and proxies to the backend and exporter. Internally it listens on port 8080.
  • penpot-backend – The main application server that talks to PostgreSQL and Redis.
  • penpot-exporter – A headless service that renders exports (PNG, PDF, SVG) from designs.
  • penpot-postgres – PostgreSQL 15, the primary data store.
  • penpot-redis – Redis, used for coordination and real-time features.

The following is a self-contained stack. It defines its own internal penpot network, maps the frontend to host port 9001, and keeps data in named volumes. It is copy-paste runnable with no pre-setup beyond generating a secret key.

The ${PENPOT_VERSION:-latest} pattern lets you pin a specific release with an environment variable and otherwise falls back to latest. For a real deployment, pinning a version is the more predictable choice.

Installation Steps

1. Confirm Docker and Compose

Penpot needs Docker Engine and the Compose plugin. Confirm both are available:

2. Generate a Secret Key

The PENPOT_SECRET_KEY is the master key Penpot uses to derive session and invitation keys. Never ship the placeholder value. Generate a strong random key:

If Python is not available, OpenSSL works too:

Copy the output into PENPOT_SECRET_KEY in the backend service.

3. Save the Compose File

Create a project directory and save the stack as docker-compose.yaml:

4. Start the Stack

Penpot’s documentation uses a named project so container names are predictable. Start it detached:

To pin a specific version instead of latest:

5. Verify the Containers

The backend log should show it connecting to PostgreSQL and running database migrations on first boot. Give it a moment on the first run while the schema is created.

6. Open the Web Application

With the default mapping, open:

If Penpot runs on a remote server, use the server address and the same port, or place it behind a reverse proxy as described later. Remember that PENPOT_PUBLIC_URI must match the URL users actually use.

7. Create Your First User

By default, open registration is off. You have two options, both covered in the registration section below: temporarily enable registration, or create a user directly with the backend command. For a first login, the fastest path is the create-profile command.

Environment Variables Explained

PENPOT_PUBLIC_URI

Purpose: The public URL where users reach Penpot. It is used to build links, invitations, and asset references, so it must match the address people actually type.

Format: Full URL including scheme.

Example:

Security note: If this does not match the real address, logins and invitation links break in confusing ways. When you move Penpot behind a proxy with a real domain, update this value on both the frontend and backend.

PENPOT_SECRET_KEY

Purpose: The master secret key. Penpot derives HTTP session keys, invitation tokens, and other subsystem secrets from it.

Format: A long, random, high-entropy string.

Example:

Security note: Treat this like a root credential. Do not reuse the placeholder, do not commit it to a repository, and be aware that changing it later invalidates existing sessions and pending invitations.

PENPOT_FLAGS

Purpose: A single space-separated list of feature toggles. Each flag uses the form enable-<feature> or disable-<feature>.

Format: Space-separated flags in one value.

Example:

Flags worth knowing:

  • enable-registration – Allows new users to sign up from the login screen. Off by default.
  • enable-login-with-password – Enables email and password login.
  • disable-email-verification – Skips the email confirmation step, useful before SMTP is configured.
  • enable-smtp – Turns on real email delivery for invitations and notifications.
  • enable-prepl-server – Enables the internal management channel required by the create-profile command.
  • disable-secure-session-cookies – Only needed when running over plain HTTP, since secure cookies require HTTPS.

Note: The same PENPOT_FLAGS value should be set on both the frontend and backend so the two agree on which features are active.

PENPOT_DATABASE_URI, PENPOT_DATABASE_USERNAME, PENPOT_DATABASE_PASSWORD

Purpose: Connection details for PostgreSQL. Penpot stores its core data here.

Format: A PostgreSQL connection URI plus username and password.

Example:

Security note: The example uses penpot/penpot for readability. Change the password to something strong, and make sure it matches POSTGRES_PASSWORD on the database service.

PENPOT_REDIS_URI

Purpose: Connection string for Redis, used for coordination and real-time features. Both the backend and the exporter need it.

Format: Redis URI with a database index.

Example:

PENPOT_ASSETS_STORAGE_BACKEND and PENPOT_STORAGE_ASSETS_FS_DIRECTORY

Purpose: Where uploaded assets are stored. The filesystem backend keeps them on a mounted volume; an S3 backend is also supported.

Format: Backend name plus a container path for the filesystem option.

Example:

Note: With assets-fs, the frontend and backend must share the same assets volume so uploads are visible to both. To use object storage instead, set PENPOT_ASSETS_STORAGE_BACKEND=assets-s3 and provide the matching S3 credentials and bucket variables.

PENPOT_SMTP_* Variables

Purpose: SMTP settings for outgoing email such as invitations and password-related messages.

Format: Host, port, credentials, and TLS/SSL toggles.

Example:

Note: SMTP is only active when enable-smtp is present in PENPOT_FLAGS. Until you configure a mail server, keep disable-email-verification set so account creation does not depend on email delivery.

PENPOT_TELEMETRY_ENABLED

Purpose: Controls whether Penpot sends anonymous usage telemetry.

Format: Boolean.

Example:

Note: Set this to false if you prefer your instance not to report telemetry.

PENPOT_INTERNAL_RESOLVER

Purpose: Points Penpot’s internal upstream lookups at Docker’s embedded DNS server. This makes service name resolution dynamic instead of resolving once at boot and hard-crashing if a neighbor is not ready yet.

Format: An IP address, normally Docker’s internal resolver.

Example:

Note: This is the fix for the host not found in upstream class of errors when the frontend races ahead of the backend or exporter at startup. It is most relevant in the reverse-proxy setup discussed below.

Volume Mounts Explained

penpot_assets:/opt/data/assets

This volume holds uploaded assets when using the filesystem storage backend. The critical detail is that it is mounted into both the frontend and the backend. The backend writes assets there and the frontend serves them, so if only one service had the mount, uploads would appear broken. Keep this volume mapped identically on both services.

penpot_postgres_v15:/var/lib/postgresql/data

This volume is the PostgreSQL data directory. Everything that is not an uploaded asset – teams, projects, files, users, and design data – lives in this database. The volume name is versioned (v15) to match PostgreSQL 15, which is a deliberate signal that the data directory is tied to a specific major PostgreSQL version.

Why the Assets Volume Is Shared

New Penpot users are sometimes surprised that a design tool needs a shared filesystem between two containers. The reason is the split between serving and processing: the backend handles the write path, and the frontend handles the read/serve path. A shared named volume is the simplest way to give both sides the same view of the asset files.

Registration, First User, and Admin Creation

Flag Strategy

Registration is disabled by default, which is a sensible security posture for a self-hosted instance. There are two common approaches to bootstrapping the first account.

Option A – Temporarily open registration. Add enable-registration to PENPOT_FLAGS, restart, sign up through the web interface, then remove the flag and restart again so the instance is closed to new signups.

Scripted Admin Creation

Option B – Create the user directly. If you would rather never open registration, create a profile from the backend. This requires the enable-prepl-server flag, which is why it is present in the compose file above.

The container name follows the <project>-<service>-<index> pattern, so with -p penpot the backend container is penpot-penpot-backend-1. If your project name differs, adjust accordingly. The --skip-tutorial and --skip-walkthrough flags avoid the onboarding steps for an account you are creating administratively.

Reverse Proxy Troubleshooting

Running Penpot directly on a port is fine for a private LAN, but most real deployments sit behind a reverse proxy for TLS and a clean domain. Penpot behind a proxy fails in a few very consistent ways. This section covers the ones that come up repeatedly, based on real deployment notes using Nginx Proxy Manager, though the underlying causes apply to any Nginx-based proxy.

502 Bad Gateway Because of the Internal Port

In newer releases, Penpot’s frontend moved its internal exposure from port 80 to port 8080. If your proxy or host mapping still targets 80, it points at an unbound channel and every request returns a 502. The fix is to map and forward to 8080:

When forwarding directly to the container, the target is the frontend service on port 8080, not 80.

host not found in upstream

A second failure mode is the proxy or frontend crashing with host not found in upstream. This happens when service discovery resolves once at boot, before neighboring containers are fully up, and then hard-fails. Two changes resolve it. First, use a normal Docker network with proper service DNS rather than isolating containers. Second, inject the internal resolver so lookups stay dynamic:

The following variant of the stack is oriented for a reverse-proxy setup: it attaches to an external penpot network, maps the frontend to a distinct host port, and adds the internal resolver. Because the network is external, you create it first:

Proxy Buffer Overflow

Penpot sends large authentication tokens and multipart frames that can saturate the default Nginx buffer sizes, which surfaces as sudden gateway rejections after login. Increasing the proxy buffers clears the bottleneck. Add this to the custom Nginx configuration for the proxy host:

Connecting the Proxy Directly to Penpot’s Network

If firewall rules block external ports between local container contexts, you can attach the proxy container directly to Penpot’s network instead of routing through a published host port:

Once the proxy shares the network, point its forward target at the container name and internal port:

When you serve Penpot over HTTPS through a proxy, drop disable-secure-session-cookies from the flags. That flag exists for plain-HTTP access; with real TLS in front, secure cookies are what you want.

Common Use Cases

A Team Leaving Per-Seat SaaS Design Tools

The most common motivation is cost and control. A team paying per editor seat for a hosted design tool can move to a single self-hosted Penpot instance where adding a designer does not add a subscription line. The design files stay on company infrastructure, which also simplifies data-handling requirements.

Design-to-Development Handoff

Because Penpot is built on SVG and open web standards, developers can inspect designs and pull layout information without needing an editor seat. On teams that already fight over how many people need paid access just to read a design, the developer inspect workflow is a practical win.

Part of a Self-Hosted Stack

Penpot slots naturally into a broader self-hosted toolchain alongside the other services in this series. Running it on the same server as your other internal tools, behind the same reverse proxy, keeps design work inside the same private perimeter as the rest of your infrastructure.

Invitation-Driven Collaboration With SMTP

Once SMTP is configured and enable-smtp is set, you can invite collaborators by email and let Penpot handle the invitation flow. This is the point where a private instance starts to feel like a shared platform rather than a single-user tool.

Useful Links

Conclusion

Penpot is one of the strongest arguments for self-hosting a category of software that most teams assume has to be SaaS. It gives you a full design and prototyping environment, built on open web standards, that runs entirely on your own server. The Docker stack is a handful of well-defined services, and once you understand the shape of it, the deployment is straightforward.

The details that matter most are consistent across deployments: generate a real PENPOT_SECRET_KEY, share the assets volume between the frontend and backend, remember that the frontend now listens on internal port 8080, and keep registration closed after you have created your users. If you put Penpot behind a proxy, the internal port, the internal resolver, and the Nginx buffer sizes are the three things that turn a frustrating 502 into a working instance.

Set those correctly and you end up with a design platform your team controls end to end, with the files sitting on infrastructure you own rather than an account you rent.

FAQ

What is Penpot?

Penpot is an open-source design and prototyping platform that you can self-host. It covers vector design, prototyping, real-time collaboration, and developer inspect, and is built on open web standards like SVG.

How is Penpot different from Figma?

The biggest difference is that Penpot is open source and can be self-hosted, so your files live on infrastructure you control. It is also built around SVG and open web standards, which is intended to make design-to-development handoff cleaner.

Is Penpot free?

Penpot is open-source software released under the Mozilla Public License. You can self-host it at no license cost. You are responsible for the server it runs on.

Does Penpot need a database?

Yes. Penpot uses PostgreSQL as its primary data store and Redis for coordination and real-time features. Both are part of the standard Docker Compose stack.

What ports does Penpot use?

The frontend listens on internal port 8080 and is commonly published to host port 9001. The backend, exporter, PostgreSQL, and Redis communicate over the internal Docker network and are not exposed directly.

Why does the article use port 9001?

9001 is the host port the official Docker documentation maps the frontend to. It is just the external mapping; internally the frontend still serves on 8080. You can change the host side to any free port you prefer.

Why is the internal port 8080 and not 80?

Newer Penpot releases moved the frontend’s internal exposure from port 80 to port 8080. If a proxy or mapping still targets 80, it hits an unbound port and returns a 502. Always forward to 8080.

What is PENPOT_SECRET_KEY?

It is the master secret from which Penpot derives session keys, invitation tokens, and other subsystem secrets. It must be a long, random value and kept private, like a root credential.

How do I generate PENPOT_SECRET_KEY?

Run python3 -c "import secrets; print(secrets.token_urlsafe(64))" or openssl rand -base64 48, then paste the output into the backend service’s environment.

How do I create the first user?

Either temporarily add enable-registration to PENPOT_FLAGS and sign up through the web interface, or run the backend command manage.py create-profile to create a profile directly.

How do I enable or disable registration?

Registration is off by default. Add enable-registration to PENPOT_FLAGS to open it and restart. Remove the flag and restart again to close signups once your users exist.

Why does create-profile need enable-prepl-server?

The create-profile command talks to the backend over an internal management channel that only exists when the enable-prepl-server flag is set. Without that flag, the command cannot connect.

What is the create-profile container name?

With the project started as -p penpot, the backend container is named penpot-penpot-backend-1, following the project-service-index pattern. Adjust the name if you used a different project name.

Do I need email verification?

Not necessarily. Setting disable-email-verification skips the confirmation step, which is convenient before you have configured SMTP. Once a mail server is in place, you can require verification instead.

Does Penpot require SMTP?

No. Penpot runs without SMTP if you disable email verification. SMTP becomes important when you want email invitations and notifications; enable it with the enable-smtp flag plus the PENPOT_SMTP_* variables.

Why must the assets volume be shared?

With the filesystem storage backend, the backend writes uploaded assets and the frontend serves them. Mounting the same penpot_assets volume into both services gives them a shared view of those files. If only one had it, uploads would appear broken.

Which PostgreSQL version does Penpot use?

The standard stack uses PostgreSQL 15. The data volume is named penpot_postgres_v15 to make the version binding explicit, since the data directory is tied to a specific major version.

What is Redis used for in Penpot?

Redis coordinates internal messaging and supports real-time features. Both the backend and the exporter connect to it through PENPOT_REDIS_URI.

What does the exporter service do?

The exporter renders designs into output formats such as PNG, PDF, and SVG. It is a separate headless service so export rendering does not block the main application.

Can I run Penpot without the exporter?

You can technically start the rest of the stack, but export functionality will not work. The exporter is a core part of a complete deployment, so keep it in the stack.

How do I turn off telemetry?

Set PENPOT_TELEMETRY_ENABLED=false on the backend service. This stops the instance from reporting anonymous usage telemetry.

What causes a 502 Bad Gateway with Penpot?

The most common cause is targeting the wrong internal port. The frontend listens on 8080, so a proxy or mapping pointing at 80 returns a 502. Buffer overflow on large auth tokens is a second cause.

How do I fix host not found in upstream?

Use a normal Docker network with service DNS instead of isolated network modes, and set PENPOT_INTERNAL_RESOLVER=127.0.0.11 so upstream lookups stay dynamic instead of hard-failing when a container is not ready yet at boot.

What is PENPOT_INTERNAL_RESOLVER?

It tells Penpot which DNS resolver to use for internal upstream lookups. Pointing it at Docker’s embedded resolver, 127.0.0.11, makes name resolution dynamic and avoids boot-time crashes when neighbor services start in a different order.

Why do I need to increase Nginx proxy buffers?

Penpot sends large authentication tokens and multipart frames that can exceed default Nginx buffer sizes, causing gateway errors after login. Raising proxy_buffer_size, proxy_buffers, and proxy_busy_buffers_size resolves it.

Can I connect my proxy directly to Penpot’s network?

Yes. Run docker network connect penpot <proxy-container>, then forward to penpot-frontend on port 8080. This avoids relying on a published host port when firewall rules get in the way.

Should I keep disable-secure-session-cookies?

Only for plain-HTTP access. When you serve Penpot over HTTPS through a proxy, remove that flag so secure session cookies are used, which is the safer configuration.

Can Penpot use S3 for asset storage?

Yes. Set PENPOT_ASSETS_STORAGE_BACKEND=assets-s3 and provide the matching S3 credentials, region, bucket, and optional endpoint variables. The default filesystem backend keeps assets on a mounted volume instead.

Does Penpot support real-time collaboration?

Yes. Multiple people can work in the same file simultaneously with live editing. This is part of why the stack includes Redis for coordination.

Can I run Penpot on an air-gapped or private network?

Yes. Because Penpot is self-hosted, it can run entirely inside a private network. Set PENPOT_PUBLIC_URI to the internal address, disable telemetry, and keep the instance behind your own access controls.

Does Penpot support plugins?

Yes. Penpot has a plugin system for extending the editor, along with an MCP integration for connecting it to external tooling and automation.

Why does PENPOT_PUBLIC_URI matter?

Penpot builds links and invitations from it, so it must match the exact URL users access. A mismatch causes broken login redirects and invitation links. Update it on both frontend and backend when you move to a real domain.

How do I pin a specific Penpot version?

Set the PENPOT_VERSION environment variable when starting the stack, for example PENPOT_VERSION=2.4.3 docker compose -p penpot up -d. Without it, the images fall back to latest.

Let’s Talk!

Looking for a reliable partner to bring your project to the next level? Whether it’s development, design, security, or ongoing support—I’d love to chat and see how I can help.

Get in touch,
and let’s create something amazing together!

RELATED POSTS

WHAT? cubicFUSION’s Elementor Menu turns WordPress nav menus into design surfaces. Six widgets cover the range — a mega menu that drops full Elementor templates into any item, a hamburger menu with five panel layouts from a simple slide-out to a modal-style fullscreen overlay and an off-canvas mode that scales the page back into a […]

Last update: 25.07. I know a lot of text, but that is how I process ideas and iterate fast! You add an animated GIF to a Penpot prototype, open it in Google Chrome, and… nothing. The GIF sits there like it’s waiting for written permission to move. Then you wiggle the cursor or interact with […]

Spend a lot of time with Vikunja (cubicFUSION Vikunja Importer) lately, one thing that it is currently missing is an easy way to style the interface. Especially the task view is just one long page without any clear section separation. LET’S TWEAK IT Here a quick starting point to tweak the style quickly. You can […]

Alexander

I am a full-stack developer. My expertise include:

  • Server, Network and Hosting Environments
  • Data Modeling / Import / Export
  • Business Logic
  • API Layer / Action layer / MVC
  • User Interfaces
  • User Experience
  • Understand what the customer and the business needs


I have a deep passion for programming, design, and server architecture—each of these fuels my creativity, and I wouldn’t feel complete without them.

With a broad range of interests, I’m always exploring new technologies and expanding my knowledge wherever needed. The tech world evolves rapidly, and I love staying ahead by embracing the latest innovations.

Beyond technology, I value peace and surround myself with like-minded individuals.

I firmly believe in the principle: Help others, and help will find its way back to you when you need it.