Vendored from debling/nix-penpot (MIT). Fixes on top: - pnpm fetchers: drop --config.fetch-* flags (UND_ERR_INVALID_ARG on pnpm 11.27) and pin real store hashes (frontend x3, exporter, mcp) - deps-lock.json: add transitive funcool/beicon2 v2.0 so fake-git resolves potok2's pin during the shadow-cljs classpath build - expose frontend pnpm stores via passthru for rehashing - x86_64-linux only; devShell ships afk, jj, git
6.3 KiB
penpot-nix
Nix packages and a NixOS module for self-hosting Penpot (pinned to release 2.18.3). Every component is built from upstream source: the Clojure API backend, the static frontend (served by nginx), the headless-Chromium exporter, the MCP server and the Rust/WASM renderer. One module wires the stack together with PostgreSQL and Valkey, either provisioned locally or pointed at infrastructure you already run.
Derived from debling/nix-penpot
(MIT, see LICENSE). x86_64-linux only.
Packages
| Attribute | Output |
|---|---|
penpot-backend |
bin/penpot-backend (JVM wrapper), bin/penpot-manage |
penpot-frontend |
dist/ static bundle (+ plugins/, plugins/mcp/), config.js |
penpot-exporter |
bin/penpot-exporter (node + nixpkgs chromium) |
penpot-mcp |
bin/penpot-mcp |
penpot-render-wasm |
render-wasm.{js,wasm}, shared.js, worker/render.js (frontend) |
penpot-render-wasm-export |
export flavor of the renderer (unused on 2.18.x) |
The same packages are exposed as an overlay (overlays.default:
pkgs.penpot-backend, pkgs.penpot-frontend, pkgs.penpot-exporter,
pkgs.penpot-mcp). The module's package defaults resolve through the
overlay, so it is required.
Deploying on NixOS
# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
penpot-nix.url = "git+https://<this repo>";
};
outputs = { self, nixpkgs, penpot-nix }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
{ nixpkgs.overlays = [ penpot-nix.overlays.default ]; }
penpot-nix.nixosModules.default
];
};
};
}
# configuration.nix
{
services.penpot = {
enable = true;
publicUri = "https://penpot.example.com";
secretKeyFile = "/run/secrets/penpot-secret-key";
};
networking.firewall.allowedTCPPorts = [ 80 443 ];
}
That is a complete deployment: PostgreSQL and Valkey are provisioned
locally, the nginx vhost is generated, exporter and MCP run behind it.
Secrets are files loaded through systemd credentials; generate the master
key once (openssl rand -base64 64 | tr -d '\n') and manage it with
agenix/sops-nix.
Commonly tuned options (all documented inline in
modules/penpot.nix):
| Option | Default | What it does |
|---|---|---|
publicUri |
– (required) | Public URL of the instance |
secretKeyFile |
– (required) | Master secret key file |
flags |
[ "enable-prepl-server" ] |
Upstream feature flags |
admins |
[ ] |
Emails granted admin on login |
database.createLocally |
true |
Local PostgreSQL, or set database.uri for an external one |
redis.createLocally |
true |
Local Valkey, or set redis.uri |
storage.backend |
"fs" |
"fs" or "s3" (+ endpoint/bucket/keys) |
smtp.* / mailpit.enable |
off | Outgoing email, or a local throwaway SMTP sink |
exporter.enable / mcp.enable |
true |
Toggle the auxiliary services |
nginx.forceHttps |
false |
Force HTTPS (configure TLS yourself) |
Users and admins
No default credentials. Register through the UI, or on the backend host:
penpot-manage create-profile -n "Jane Doe" -e jane@example.org -p 'secret'
Admin rights come from services.penpot.admins = [ "jane@example.org" ];.
Trying it in a VM
nix build .#nixosConfigurations.penpot-vm.config.system.build.vm
./result/bin/run-penpot-vm-vm
Host port 8080 → nginx, 8025 → mailpit UI. curl http://127.0.0.1:8080/readyz
once the backend is up (~1–2 min). Needs ~3 GB RAM.
Development
direnv allow # or: nix develop — devShell ships the afk harness, jj, git
nix flake check --no-build
nix build .#checks.x86_64-linux.penpot-vm-test # end-to-end VM test
nix fmt
Development runs under afk (afk is on
PATH in the devShell).
Updating Penpot
- Bump
penpot.urlandpenpotVersioninflake.nix(upstream'spackage.jsonversion is stale, so it is pinned by hand). - Regenerate
deps-lock.jsonwith clj-nix from a writable copy of the penpot source with theRELEASEpins frompackages/frontend.nix(pinDepsEdn) applied:nix run github:jlesquembre/clj-nix/<pinned-rev>#deps-lock -- \ --deps-include backend/deps.edn --deps-include common/deps.edn \ --deps-include frontend/deps.edn --deps-include exporter/deps.edn \ --alias-exclude outdated --alias-exclude jvm-repl - Rehash the fixed-output derivations (set to
lib.fakeHash, build, copygot:):penpot-frontend.{frontendPnpmDeps,pluginsPnpmDeps,mcpPnpmDeps},penpot-exporter.pnpmDeps,penpot-mcp.pnpmDeps,penpot-backend.builtinTemplates(afternix flake update penpot-files), andcargoDeps/ Rust / Skia hashes inpackages/render-wasm.nixifrender-wasm/Cargo.lock, the Rust version or the Skia URL changed. - Rebase
packages/patches/*iffrontend/src/app/plugins/library.cljsmoved.
License
Nix code: MIT (LICENSE). Penpot: MPL-2.0. The remaining stack (PostgreSQL, Valkey, Chromium, …) ships under its own licenses.