Penpot на сервере: частые ошибки и решения
Penpot — open-source альтернатива Figma, и на бумаге разворачивается одной командой docker compose up. На практике же после первого запуска почти всегда всплывает что-то из типового набора: белый экран вместо интерфейса, обрыв realtime-соединения при совместном редактировании, письма с подтверждением регистрации не долетают, а после рестарта контейнеров пропадают загруженные картинки. Ниже — разбор самых частых причин и то, как их закрыть без танцев с бубном.
Содержание
- Белый экран или бесконечная загрузка после запуска
- Обрыв realtime и десинхронизация при совместном редактировании
- Ошибки подключения к базе данных при первом старте
- Не приходят письма с подтверждением регистрации и приглашениями
- Пропадают загруженные изображения и ассеты после перезапуска
- Высокая нагрузка на CPU и память под нагрузкой нескольких команд
- Ошибки при обновлении на новую версию
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Белый экран или бесконечная загрузка после запуска
Это самая частая жалоба, и почти всегда причина одна: фронтенд Penpot собран со PENPOT_PUBLIC_URI, зашитым в статику на этапе сборки образа, а обращается он не туда, куда вы на самом деле открываете приложение.
Проверьте, что в .env (или docker-compose.yml) переменная PENPOT_PUBLIC_URI совпадает с реальным адресом, по которому пользователь заходит в браузере — включая схему:
PENPOT_PUBLIC_URI=https://design.example.com
Если у вас http:// за прокси, а снаружи https://, браузер будет пытаться достучаться до бэкенда по неправильному протоколу и молча падать — в консоли разработчика (F12 → Network) вы увидите красные запросы к /api/rpc/... со статусом 0 или mixed content. После правки .env контейнеры нужно не просто перезапустить, а пересоздать:
docker compose down
docker compose up -d
Второй по частоте вариант — не прогрузился JS-бандл фронтенда из-за кэша браузера или CDN. Жёсткая перезагрузка (Ctrl+Shift+R) и проверка вкладки Network на 404 по .js-файлам снимает вопрос за секунду. Если 404 реальный — значит, контейнер penpot-frontend не успел стартовать до того, как прокси уже отдавал старую статику; посмотрите его логи:
docker compose logs -f penpot-frontend
Обрыв realtime и десинхронизация при совместном редактировании
Penpot держит соединение между вкладками и бэкендом через WebSocket (/ws/notifications). Если прокси перед контейнерами не настроен на апгрейд соединения, курсоры коллег перестают двигаться, а изменения одного участника не долетают до другого без ручного обновления страницы.
Для Nginx перед Penpot обязательны заголовки апгрейда протокола именно на этом location:
location /ws/ {
proxy_pass http://127.0.0.1:9001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
Без proxy_read_timeout соединение будет рваться каждую минуту-две по умолчанию — визуально это выглядит как «то работает, то нет» без явной ошибки. Если вы используете Traefik, у него апгрейд WebSocket идёт автоматически, но проверьте, что у роутера не выставлен короткий idleTimeout в конфиге entrypoint — детали настройки обратного прокси для докер-контейнеров разобраны в статье про Traefik как reverse proxy для докера.
Отдельно проверьте, что балансировщик (если он есть перед прокси) настроен на sticky-сессии или хотя бы не разрывает keep-alive соединения — с несколькими репликами фронтенда без sticky WebSocket будет постоянно переустанавливаться.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверОшибки подключения к базе данных при первом старте
Penpot использует PostgreSQL как основное хранилище метаданных, и на старте контейнер penpot-backend должен дождаться, пока база полностью поднимется. Типичная ошибка в логах:
org.postgresql.util.PSQLException: Connection refused
или
FATAL: database "penpot" does not exist
Первое лечится добавлением depends_on с условием service_healthy, а не просто порядком запуска — Docker Compose по умолчанию не ждёт готовности базы, только создания контейнера:
depends_on:
penpot-postgres:
condition: service_healthy
и health-check у самой базы:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U penpot"]
interval: 5s
timeout: 5s
retries: 10
Вторая ошибка — база и пользователь не создались, потому что переменные POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD были заданы уже после первого запуска с пустым volume. PostgreSQL инициализирует базу только при первом старте на чистом каталоге данных — если вы поменяли креды задним числом, нужно либо удалить volume и поднять базу заново (потеряете данные), либо создать базу и пользователя вручную через psql. Общие грабли PostgreSQL на сервере — включая память, настройки подключений и права — подробно разобраны в статье PostgreSQL на сервере: частые ошибки и решения.
Не приходят письма с подтверждением регистрации и приглашениями
По умолчанию Penpot в режиме self-host отправляет письма через SMTP, и без корректно настроенного SMTP_* блока в .env регистрация новых пользователей и приглашения в команду просто не работают — форма показывает успех, а письмо никуда не уходит.
Минимальный рабочий набор переменных:
PENPOT_SMTP_ENABLED=true
PENPOT_SMTP_HOST=smtp.example.com
PENPOT_SMTP_PORT=587
PENPOT_SMTP_USERNAME=noreply@example.com
PENPOT_SMTP_PASSWORD=пароль_приложения
PENPOT_SMTP_TLS=true
PENPOT_SMTP_DEFAULT_FROM=noreply@example.com
PENPOT_SMTP_DEFAULT_REPLY_TO=noreply@example.com
Частая ошибка — использовать порт 25 наружу: многие хостинг-провайдеры (и облачные, и VPS) блокируют исходящий SMTP на 25 порту по умолчанию ради борьбы со спамом, оставляя открытыми только 587 (STARTTLS) или 465 (SSL). Если письма не идут и в логах penpot-backend тишина — проверьте телнетом, открыт ли порт вообще:
telnet smtp.example.com 587
Если провайдер режет исходящий SMTP полностью, единственный практичный выход — использовать транзакционный сервис (SendGrid, Mailgun, Postmark и аналоги) как relay, у них порт 587 открыт всегда и не зависит от репутации вашего IP.
Второй нюанс — некоторые провайдеры (Gmail, Yandex) требуют не обычный пароль аккаунта, а отдельный пароль приложения; обычный пароль SMTP-сервер молча отклонит с ошибкой аутентификации в логах бэкенда.
Пропадают загруженные изображения и ассеты после перезапуска
Если вы не настроили внешнее хранилище, Penpot по умолчанию складывает загруженные файлы (assets, превью, экспортированные изображения) на локальный диск внутри контейнера — в volume assets. Если этот volume не объявлен явно в docker-compose.yml или вы случайно запустили docker compose down -v (флаг -v удаляет именованные volumes), все загруженные картинки исчезают безвозвратно.
Проверьте, что volume для assets примонтирован постоянно:
services:
penpot-backend:
volumes:
- penpot_assets:/opt/data/assets
volumes:
penpot_assets:
Для продакшн-инсталляции надёжнее переключиться на S3-совместимое хранилище — тогда файлы переживут любой пересоздание контейнеров и легко бэкапятся отдельно от базы:
PENPOT_ASSETS_STORAGE_BACKEND=s3
PENPOT_STORAGE_ASSETS_S3_ENDPOINT=https://s3.example.com
PENPOT_STORAGE_ASSETS_S3_BUCKET=penpot-assets
PENPOT_STORAGE_ASSETS_S3_REGION=ru-central1
В качестве S3-совместимого бэкенда на своём сервере часто ставят MinIO — как его развернуть и на что обратить внимание, описано в статье MinIO на сервере: частые ошибки и решения. Регулярные бэкапы базы данных при этом всё равно остаются обязательными — S3 спасает только ассеты, но не метаданные проектов, историю версий и права доступа, которые лежат в PostgreSQL.
Высокая нагрузка на CPU и память под нагрузкой нескольких команд
Penpot тянет за собой стек из нескольких сервисов: backend на Clojure/JVM, frontend на статике, PostgreSQL, Redis для очередей и pub/sub realtime-уведомлений, и опционально exporter на headless-браузере для экспорта в PNG/PDF. Именно exporter (использует Playwright с headless Chromium) — главный потребитель памяти при активном экспорте больших досок: один процесс рендера тяжёлого файла может кратковременно съедать 500 МБ–1 ГБ RAM.
Ориентировочно для команды из 5–15 человек с умеренной активностью хватает:
| Ресурс | Минимум | Комфортно |
|---|---|---|
| CPU | 2 vCPU | 4 vCPU |
| RAM | 4 ГБ | 8 ГБ |
| Диск | 40 ГБ SSD | 80 ГБ SSD (с ростом ассетов) |
Это ориентир, не измеренный бенчмарк — реальное потребление сильно зависит от размера файлов, частоты экспорта и числа одновременных realtime-сессий. Если backend периодически падает по OOM (в dmesg или docker compose logs видно Killed), первым делом ограничьте память exporter-контейнера через mem_limit в compose-файле и проверьте, не залипли ли зависшие процессы Chromium — недостаток описан в апстриме как известная особенность headless-рендера при параллельных запросах экспорта.
Redis в этой связке обычно не требует тонкой настройки, но если вы видите ошибки подключения бэкенда к нему при старте — общие причины и решения собраны в статье Redis на сервере: частые ошибки и решения.
Ошибки при обновлении на новую версию
Penpot развивается быстро, и при обновлении образов через docker compose pull && docker compose up -d иногда всплывают миграции базы данных, которые не завершаются автоматически, если предыдущий запуск был прерван (например, сервер перезагрузился посреди работы). Симптом — backend стартует, но API отвечает 500 на большинство запросов, а в логах видны ошибки вида relation "..." does not exist или незавершённая миграция.
Правильный порядок обновления:
# 1. Бэкап базы перед любым обновлением — обязательно
docker exec penpot-postgres pg_dump -U penpot penpot > penpot_backup_$(date +%F).sql
# 2. Тянем новые образы
docker compose pull
# 3. Останавливаем и поднимаем заново (не restart — именно пересоздание)
docker compose down
docker compose up -d
# 4. Смотрим логи backend, пока миграции не завершатся
docker compose logs -f penpot-backend
Не делайте docker compose restart вместо полного цикла down/up — иногда обновлённый образ не подхватывается, если старый контейнер не был пересоздан, и вы продолжаете работать на старой версии, думая, что обновились. Если после обновления что-то пошло не так, откат — это восстановление бэкапа базы и возврат тега образа на предыдущую версию в docker-compose.yml, а не попытка чинить миграцию руками.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли развернуть Penpot без Docker, напрямую на сервере?
Технически да, но официально поддерживается только контейнерный вариант — вручную собирать Clojure-бэкенд, фронтенд-статику, PostgreSQL и Redis отдельно намного трудозатратнее и лишает вас автоматических миграций при обновлении. Практический смысл в этом есть только для очень специфичных инфраструктурных требований.
Нужен ли отдельный exporter-контейнер, если экспорт в PDF/PNG не используется?
Нет, можно убрать его из docker-compose.yml — это снизит потребление памяти. Учтите, что тогда кнопки экспорта в интерфейсе будут выдавать ошибку, и вернуть функцию можно, просто снова добавив сервис.
Как перенести Penpot на другой сервер без потери данных?
Бэкапьте volume базы PostgreSQL (pg_dump) и volume assets (или бакет S3, если он используется) отдельно, разворачивайте тот же docker-compose.yml на новом сервере, восстанавливайте дамп базы через psql, копируйте содержимое assets-volume, затем поднимайте контейнеры.
Почему после смены домена ломаются старые ссылки на файлы в приглашениях?
Ссылки в письмах и в интерфейсе строятся на основе PENPOT_PUBLIC_URI, зафиксированного на момент отправки. При смене домена старые письма со ссылками на прежний адрес остаются рабочими только если вы настроите редирект со старого домена на новый на уровне прокси.
Сколько одновременных пользователей выдерживает одна инсталляция на скромном сервере?
Жёсткого лимита нет, ограничение упирается в CPU/RAM бэкенда и exporter при пиковой нагрузке. Для небольшой команды 2–4 vCPU и 4–8 ГБ RAM обычно достаточно с запасом; для десятков активных пользователей с частым экспортом стоит закладывать больше ресурсов и мониторить потребление.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →