Установка Planka через Docker Compose на свой сервер
Пошаговое руководство по развертыванию Planka с Postgres и Traefik. Узнайте, как настроить переменные окружения и параметр BASE_URL, чтобы избежать ошибок авторизации.
Что дает самостоятельный хостинг Planka
Самостоятельный хостинг Planka предоставляет вашей команде Kanban-доску с моделью карточек, списков и меток, знакомой пользователям Trello, которая работает на подконтрольном вам VPS. Здесь нет ограничений на количество мест и оплаты за каждого пользователя, так как единственные расходы — это сервер. В этом руководстве мы развернем его с помощью Docker Compose за Traefik, используя Postgres для данных и именованный том для всех файлов, которые загружают пользователи.
Это руководство предназначено для команд от двух до пяти человек, которые переходят с бесплатного тарифа Trello. Если вы еще выбираете систему для управления задачами, сначала прочитайте сравнение альтернатив Trello для самостоятельного хостинга. Это руководство предполагает, что выбор уже сделан, и охватывает только процесс развертывания.
Вам потребуется VPS с установленным Docker Engine и плагином Compose, а также DNS A-запись, указывающая на этот сервер. Также необходимо наличие экземпляра Traefik, который уже выполняет TLS termination (transport layer security) на этом узле. Если Traefik еще не настроен, сначала выполните настройку обратного прокси Traefik перед несколькими приложениями Compose, а если приведенный ниже файл кажется незнакомым, изучите основы Docker Compose для VPS.
Сколько ресурсов VPS требуется для Planka?
Проект не публикует минимальные системные требования, поэтому любые цифры следует воспринимать как отправную точку, а не как точное измерение. Значения 2 vCPU и 4 GB RAM, которые часто встречаются на страницах хостинг-провайдеров, являются стандартным комфортным предложением, а не требованием, установленным разработчиками. Для доски, с которой работают пять человек, это избыточные ресурсы.
Фактически запускаются небольшие процессы: один процесс Node.js, обслуживающий API и собранный фронтенд, и один процесс Postgres для хранения данных. Третий небольшой прокси-процесс работает внутри контейнера Planka для фильтрации исходящих запросов. Тарифный план с 1 vCPU и 2 GB RAM справится с нагрузкой доски для команды из двух-пяти человек, при этом большая часть свободной оперативной памяти будет использоваться под кэш Postgres.
Определите размер диска до того, как выбирать объем оперативной памяти, так как объем вложений растет со временем. Измерьте показатели вашего собственного экземпляра, вместо того чтобы полагаться на этот текст:
docker stats --no-stream
docker system df -vПервая команда выводит текущее потребление памяти и CPU для каждого контейнера. Вторая показывает объем данных, занимаемый каждым томом. Снимайте оба показателя после полноценной рабочей недели, а не в день установки, так как простаивающая доска не дает представления о реальной нагрузке вашей команды.
Создание файла Compose
Создайте каталог и назначьте его владельцем нужного пользователя, чтобы вам не приходилось редактировать эти файлы через sudo.
sudo mkdir -p /opt/planka
sudo chown "$USER":"$USER" /opt/planka
cd /opt/plankaСгенерируйте секреты в файл .env рядом с файлом Compose. Compose считывает этот файл автоматически и подставляет значения.
umask 077
{
printf 'SECRET_KEY=%s\n' "$(openssl rand -hex 64)"
printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)"
printf 'ADMIN_PASSWORD=%s\n' "$(openssl rand -hex 12)"
} > .env
chmod 600 .envИспользование openssl rand -hex сделано намеренно. Шестнадцатеричная строка содержит только цифры и буквы от a до f, поэтому она не может нарушить строку подключения DATABASE_URL, в которую вставляется. Пароль в формате base64, содержащий косую черту или символ «собака», вызывает ошибку подключения, которая выглядит как неверное имя хоста, и на её отладку уходит час. Более общие рекомендации описаны в хранении секретов вне файла Compose.
Теперь docker-compose.yml. Замените kanban.example.com на ваше собственное имя хоста в обоих местах, где оно встречается.
services:
planka:
image: ghcr.io/plankanban/planka:2.1.1
restart: unless-stopped
volumes:
- planka-data:/app/data
environment:
- BASE_URL=https://kanban.example.com
- DATABASE_URL=postgresql://planka:${POSTGRES_PASSWORD}@postgres/planka
- SECRET_KEY=${SECRET_KEY}
- TRUST_PROXY=true
- DEFAULT_ADMIN_EMAIL=you@example.com
- DEFAULT_ADMIN_PASSWORD=${ADMIN_PASSWORD}
- DEFAULT_ADMIN_NAME=Your Name
- DEFAULT_ADMIN_USERNAME=admin
networks:
- proxy
- internal
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.planka.rule=Host(`kanban.example.com`)"
- "traefik.http.routers.planka.entrypoints=websecure"
- "traefik.http.routers.planka.tls.certresolver=default"
- "traefik.http.services.planka.loadbalancer.server.port=1337"
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:16-alpine
restart: unless-stopped
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=planka
- POSTGRES_USER=planka
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
networks:
- internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -U planka -d planka"]
interval: 10s
timeout: 5s
retries: 5
volumes:
planka-data:
db-data:
networks:
proxy:
external: true
internal:Четыре решения в этом файле требуют пояснения, так как именно их пользователи часто меняют, а затем жалеют об этом.
- В сервисе Planka отсутствует блок
ports:. Traefik обращается к контейнеру через сетьproxy, поэтому порт 1337 никогда не публикуется на хосте. Публикация порта позволила бы любому обойти ваш прокси и сертификат. loadbalancer.server.port=1337указывает порт внутри контейнера. Planka прослушивает порт 1337, а в примере выше обращение идет через 3000, так как там выполняется маппинг порта на хост. Здесь маппинга нет, поэтому Traefik должен знать порт контейнера.condition: service_healthyработает в связке с проверкой работоспособности (healthcheck) Postgres. Без этого параметра Planka запускается раньше, чем база данных начинает принимать соединения, выполняет первый запрос с ошибкой и завершается, что выглядит как цикл перезагрузок (crash loop). Механика процесса описана в проверках работоспособности и порядке запуска в Compose.- Сервис базы данных намеренно назван
postgres. Planka 2 направляет свои исходящие запросы через внутренний фильтр, чей список блокировки по умолчанию включаетlocalhost,postgres. Переименуйте сервис, и вы незаметно исключите свою базу данных из этого списка.
Перед запуском проверьте, что Compose видит ваши секреты:
docker compose config | grep -E 'image:|BASE_URL|POSTGRES_USER'Эта команда выведет содержимое файла с уже подставленными значениями .env. Пустое значение означает, что Compose не считывает файл .env, обычно это происходит из-за того, что вы запускаете команду из другого каталога.
За что отвечают переменные начальной настройки администратора
Начиная с версии Planka 1.13, администратор не создается автоматически, поэтому в новой базе данных нет пользователей, которые могли бы войти в систему. Группа переменных DEFAULT_ADMIN_* — один из двух способов решить эту проблему.
При запуске Planka ищет пользователя, соответствующего DEFAULT_ADMIN_EMAIL. Если такой пользователь отсутствует, система создает его, используя пароль, отображаемое имя и имя пользователя, указанные в соответствующих переменных. Это происходит только при первом запуске с пустой базой данных, поэтому данные переменные выполняют начальную загрузку учетной записи, а не управляют ею в дальнейшем.
Переменная DEFAULT_ADMIN_EMAIL выполняет вторую функцию, которая часто сбивает пользователей с толку. Пока эта переменная задана, учетную запись с указанным именем нельзя изменить или удалить через интерфейс. Это механизм защиты от блокировки; именно поэтому вы не можете переименовать эту учетную запись или изменить её адрес электронной почты в UI. Удалите переменную и перезапустите контейнер — учетная запись станет обычным администратором, которого можно редактировать как любого другого пользователя.
Будьте внимательны при указании пароля. Любое значение в environment: доступно для чтения любому, кто может выполнить docker inspect внутри контейнера, поэтому DEFAULT_ADMIN_PASSWORD не должна оставаться там постоянно. Войдите в систему, смените пароль через интерфейс, удалите строку с паролем, а затем снова выполните docker compose up -d.
Более чистый способ позволяет полностью обойтись без переменных. Закомментируйте всю группу DEFAULT_ADMIN_*, а затем создайте учетную запись в интерактивном режиме:
docker compose run --rm planka npm run db:create-admin-userСистема запросит адрес электронной почты, пароль, отображаемое имя и (опционально) имя пользователя, после чего запишет данные напрямую в базу данных. Пароль не попадает в файл Compose или переменные окружения контейнера. Используйте этот метод, если доступ к оболочке (shell) на VPS есть у нескольких человек. Команда сначала запускает Postgres из-за depends_on, поэтому она работает даже с тем стеком, который еще ни разу не был запущен.
Любой из этих способов предполагает ручное управление паролями в Planka. Если это уже четвертая учетная запись, которую приходится хранить вашей команде, Planka может делегировать авторизацию OIDC-провайдеру, например, Authentik, работающему как ваш собственный сервер единого входа (SSO). В этом случае администратор, созданный при начальной настройке, останется «аварийной» учетной записью на случай сбоя провайдера.
Почему BASE_URL нарушает работу входа, если он не совпадает с именем хоста
BASE_URL — это точный адрес, который пользователи вводят в браузере, включая схему и без косой черты в конце. Для этого стека это https://kanban.example.com. Planka формирует собственные ссылки и WebSocket-соединение на основе этого значения, поэтому неверный BASE_URL не выдает явную ошибку. Вместо этого страница загружается, но процесс загрузки никогда не завершается.
Распространенный сценарий: вы копируете пример из репозитория, оставляете BASE_URL=http://localhost:3000 как есть и заходите на сайт по HTTPS через свой реальный домен. Форма входа отправляется, учетные данные принимаются, но доска не отображается. Откройте консоль разработчика в браузере, и вы увидите, что запросы к /socket.io/ завершаются ошибкой, так как клиенту было указано открыть соединение с localhost:3000, а на вашем компьютере этот адрес никуда не ведет.
TRUST_PROXY=true — это вторая часть той же проблемы. Planka работает за Traefik, поэтому каждый запрос поступает от прокси по обычному HTTP внутри сети Docker. Без TRUST_PROXY приложение игнорирует заголовки X-Forwarded-Proto и X-Forwarded-For, которые устанавливает Traefik, поэтому оно считает соединение небезопасным и воспринимает всех клиентов как один общий IP-адрес. При установке этого параметра приложение считывает заголовки и корректно определяет схему подключения, как и браузер.
Traefik проксирует WebSockets без дополнительной настройки, что является одной из причин предпочесть его в данном случае. В nginx для socket.io требуется собственный блок location с параметрами proxy_set_header Upgrade $http_upgrade и proxy_set_header Connection "upgrade", иначе вы получите тот же бесконечный индикатор загрузки, но по другой причине.
Перенос доски на новое имя хоста в будущем требует одновременного изменения двух параметров: значения BASE_URL и правила Host() в Traefik. Если изменить один и забыть про другой, вы снова столкнетесь с бесконечной загрузкой. Размещение Planka по подпути, например https://example.com/planka, работает начиная с версии 2.1.0, выпущенной в марте 2026 года. Для более старых версий используйте отдельный поддомен.
Где Planka хранит вложения и аватары
Planka 2 хранит все загружаемые пользователями файлы по одному пути внутри контейнера: /app/data. Вложения, аватары пользователей и фоновые изображения досок находятся в этой директории. В версии 1 использовались три отдельные директории, поэтому файл Compose, скопированный из старого руководства, монтирует пути, которых больше не существует, а реальная директория с данными остается несмонтированной.
Это единственное монтирование определяет разницу между доской, которая сохранится после обновления, и потерей данных. Если /app/data не вынесена в volume, загруженные файлы попадают в записываемый слой контейнера. Этот слой уничтожается при пересоздании контейнера, а контейнер пересоздается каждый раз при смене тега образа. Доска после запуска выглядит нормально, карточки на месте, но все ссылки на вложения не работают, так как записи в базе данных указывают на файлы, которых больше нет.
Именованный том (named volume) в приведенном выше файле Compose предотвращает эту проблему. Bind mount также подходит и упрощает резервное копирование файлов стандартными инструментами, но требует одного дополнительного действия. Процесс Node внутри контейнера выполняется от имени пользователя с UID 1000, поэтому директория на хосте, принадлежащая root, вызовет ошибку прав доступа при первой же загрузке:
sudo chown -R 1000:1000 /opt/planka/dataКомпромисс между этими двумя вариантами разобран в bind mounts против именованных томов.
Если объем вложений превышает доступное место на диске, Planka может записывать их в S3-совместимое хранилище через S3_ENDPOINT, S3_BUCKET и соответствующие переменные ключей. Это может быть облачный бакет или самостоятельно развернутое объектное хранилище MinIO на другом сервере. Определитесь с этим до того, как команда начнет активно наполнять доску, так как настройка применяется только к новым загрузкам.
Запуск стека и проверка работоспособности
docker compose pull
docker compose up -d
docker compose psdocker compose ps должен показывать postgres как healthy, а planka как running. Если Planka перезапускается в цикле, в первую очередь нужно проверять подключение к базе данных, а не само приложение.
docker compose logs -f plankaПри успешном первом запуске выполняются миграции базы данных, после чего сервер начинает прослушивать порт 1337. Убедитесь, что схема данных была создана, обратившись к Postgres напрямую, вместо того чтобы полагаться только на логи:
docker compose exec postgres psql -U planka -d planka -c '\dt'Список таблиц, содержащий board и card, означает, что миграции прошли успешно. Сообщение "Did not find any relations" означает, что Planka не смогла подключиться к базе. В этом случае сравните DATABASE_URL со значениями POSTGRES_USER и POSTGRES_PASSWORD в вашем файле .env.
Затем проверьте маршрут со своего локального компьютера, а не с VPS:
curl -I https://kanban.example.comHTTP/2 200 означает, что Traefik получил сертификат и успешно связывается с контейнером. Ошибка 404, возвращаемая Traefik, означает, что метки (labels) маршрутизатора не совпали; чаще всего это происходит из-за того, что контейнер не подключен к сети proxy. Теперь откройте сайт и войдите в систему под учетной записью администратора.
Выполняйте pg_dump перед каждым обновлением версии
Ваша доска использует два отдельных хранилища, поэтому резервная копия должна охватывать оба: базу данных Postgres и том planka-data. Выполняйте дамп базы данных, пока стек запущен.
docker compose exec -T postgres pg_dump -U planka -d planka > "planka-db-$(date +%F).sql"Флаг -T обязателен. Без него Compose выделяет псевдотерминал, а терминальный уровень перезаписывает символы конца строки в потоке. В результате вы получите файл дампа, который будет поврежден при восстановлении. Ошибка проявится спустя недели, что является худшим сценарием.
Затем сохраните загружаемые файлы. Сначала определите реальное имя тома, так как Compose добавляет к нему префикс в виде имени директории проекта.
docker volume ls | grep planka
docker run --rm -v planka_planka-data:/data -v "$PWD":/backup alpine \
tar czf /backup/planka-files-$(date +%F).tgz -C /data .Проект также содержит docker-backup.sh и docker-restore.sh в своем репозитории, а официальная документация рекомендует запускать их по ночному расписанию cron. Оба подхода приемлемы. Неприемлема ситуация, когда у вас есть резервная копия, которую вы никогда не восстанавливали. Восстановите её на тестовом VPS хотя бы раз, чтобы убедиться, что вы можете войти в систему и открыть вложение.
Выполняйте дамп непосредственно перед каждым изменением версии. Резервная копия, сделанная вчера вечером, не эквивалентна резервной копии, сделанной перед миграцией, которую вы собираетесь запустить.
Закрепление тегов и чтение примечаний к выпуску
Оба тега образов в этом файле закреплены намеренно.
ghcr.io/plankanban/planka:2.1.1 — это конкретный выпуск, актуальный на август 2026 года. latest меняется при каждом обновлении от разработчиков, поэтому обычная команда docker compose pull может привести к миграции схемы базы данных в самый неподходящий момент. Перед изменением этого номера обязательно прочитайте примечания к выпуску, так как именно там описаны критические изменения и исправления безопасности. Версия 2.0.3 была выпущена как обновление безопасности — это именно тот случай, когда лучше ознакомиться с информацией заранее, чем столкнуться с последствиями неожиданно.
postgres:16-alpine закреплен за мажорной версией по более веской причине. Postgres записывает данные в каталог в формате, привязанном к мажорной версии, и сервер отказывается открывать каталог, созданный другой версией. Если вы укажете postgres:latest и позволите тегу обновиться до 17, контейнер не запустится:
FATAL: database files are incompatible with server
DETAIL: The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17.Данные не теряются, и перезапуск проблему не решит. Переход на новую мажорную версию Postgres требует создания дампа из старой версии и восстановления данных в новом пустом каталоге. Это плановая задача, выполняемая при остановленном стеке, а не побочный эффект обновления образа.
Если вы переходите с существующей установки Planka 1.x, а не начинаете с нуля, для этого обновления предусмотрена собственная процедура в документации проекта. Откат к версии 1 невозможен без предварительно созданной резервной копии.
Типичные сбои и сообщения об ошибках
Planka перезапускается в цикле, а в логах упоминается база данных. Учетные данные в DATABASE_URL не совпадают с переменными окружения Postgres. Учтите, что POSTGRES_PASSWORD применяется только при первой инициализации каталога данных, поэтому исправление переменной после неудачного первого запуска ничего не даст. Необходимо удалить том db-data и начать заново.
Вход выполнен, но доска не загружается. Значение BASE_URL не совпадает с адресом в строке браузера, либо отсутствует TRUST_PROXY. В консоли браузера видны неудачные запросы к /socket.io/.
Загрузка файлов не работает, остальное функционирует нормально. Точка монтирования (bind mount) принадлежит пользователю root. Выполните sudo chown -R 1000:1000 для каталога на хосте и перезапустите контейнер.
Вложения исчезли после обновления. Каталог /app/data не был вынесен на том, поэтому файлы находились в слое контейнера, который был заменен при обновлении. Восстановите файлы из резервной копии, затем добавьте том, прежде чем снова менять тег образа.
Traefik возвращает 404. Контейнер не подключен к сети proxy, либо правило Host() не совпадает с вашей DNS-записью. Команда docker compose config показывает метки после подстановки переменных — именно там становятся видны опечатки.
Уведомления или вебхуки не приходят. Planka 2 отправляет исходящие HTTP-запросы через внутренний фильтр, и список блокировки по умолчанию включает localhost и postgres. Вебхук, направленный на другой контейнер на том же хосте, может блокироваться по умолчанию. Отредактируйте OUTGOING_ALLOWED_HOSTS вместо отключения фильтра.
После запуска нагрузка на обслуживание минимальна. Следите за примечаниями к выпускам (release notes) и делайте дамп базы данных перед каждым обновлением. Перезагрузка сервера автоматически поднимает стек благодаря restart: unless-stopped, если служба Docker включена в автозагрузку. В разделе Compose-стеки, которые восстанавливаются после перезагрузки описаны случаи, когда это не происходит.
FAQ
Почему Planka бесконечно загружается после входа в систему?
Учетные данные приняты, но соединение в реальном времени не установлено. Planka формирует URL для WebSocket на основе BASE_URL. Если в этой переменной указано http://localhost:3000, а вы заходите на сайт по адресу https://kanban.example.com, браузер пытается открыть сокет по адресу, которого не существует на вашей машине. В консоли разработчика будут видны неудачные запросы к /socket.io/. Установите BASE_URL в значение точного публичного адреса без слеша в конце, добавьте TRUST_PROXY=true, чтобы приложение учитывало заголовок X-Forwarded-Proto от вашего reverse proxy, затем выполните docker compose up -d.
Как создать первого администратора в Planka?
Начиная с версии 1.13 администратор не создается автоматически. Либо укажите DEFAULT_ADMIN_EMAIL вместе с соответствующими переменными для пароля, имени и имени пользователя и запустите стек, либо выполните docker compose run --rm planka npm run db:create-admin-user и ответьте на вопросы в интерактивном режиме. Интерактивная команда безопаснее на общем сервере, так как пароль не попадает в переменные окружения контейнера, где его может прочитать docker inspect. Если оставить DEFAULT_ADMIN_EMAIL установленной после этого, учетная запись будет заблокирована от редактирования и удаления через интерфейс.
Где Planka хранит вложения и аватары?
В Planka 2 все загруженные файлы находятся по пути /app/data внутри контейнера, включая вложения, аватары пользователей и фоны досок. Примонтируйте этот путь к именованному тому. Если том не примонтирован, файлы сохраняются в записываемом слое контейнера и будут удалены при следующем пересоздании контейнера, что происходит при каждом обновлении образа. Bind mount также подходит, но процесс Node работает от имени UID 1000, поэтому выполните sudo chown -R 1000:1000 для директории на хосте, иначе загрузка файлов завершится ошибкой доступа.
Сколько оперативной памяти нужно для self-hosted Planka?
Проект не устанавливает минимальных системных требований. Цифры 2 vCPU и 4 GB, которые часто встречаются на страницах хостинг-провайдеров, являются стандартными для них, а не результатом измерений, и для небольшой доски это избыточно. Вся нагрузка состоит из одного процесса Node и одного процесса Postgres, поэтому плана с 1 vCPU и 2 GB достаточно для команды из двух-пяти человек. Выполните docker stats --no-stream после обычной рабочей недели и подбирайте ресурсы на основе собственных данных. Следите за диском внимательнее, чем за памятью, так как именно вложения занимают место.
Как обновить Planka без потери данных?
Сделайте дамп базы данных и заархивируйте том с загрузками непосредственно перед обновлением, а не по расписанию за прошлую ночь. Используйте docker compose exec -T postgres pg_dump -U planka -d planka > planka-db.sql, сохраняя -T, чтобы псевдотерминал не повредил перенаправленный вывод. Прочитайте примечания к выпуску для каждой версии, которую вы пропускаете, измените тег образа на конкретный релиз вместо latest, затем выполните docker compose pull и docker compose up -d и следите за логом миграции. Оставьте тег Postgres привязанным к его мажорной версии, так как сервер откажется открывать директорию данных, созданную другой мажорной версией.