MAATRIX / Блог / Plane в Docker Compose: готовый файл

Plane в Docker Compose: готовый файл

MAATRIX

Команда доросла до момента, когда общий гугл-док и доска в Trello уже не тянут: нужны эпики, циклы, роадмап и трекинг времени — по сути, Jira или Linear. У обоих есть цена за пользователя, которая на команде из 15-20 человек превращается в заметную статью бюджета, а переписка по задачам лежит на чужих серверах. Plane — open-source трекер с похожим набором функций, который можно поднять на своём VPS и платить только за сервер. Ниже — рабочий docker-compose.yml для полного стека Plane и разбор мест, где self-host отличается от облачной версии.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Что такое Plane и когда он оправдан вместо Jira или Linear

Plane — не один контейнер, а набор сервисов, потому что под капотом Django-бэкенд с Celery-воркерами и три отдельных Next.js-приложения:

  • web — рабочее пространство: проекты, задачи, циклы, доски;
  • space — публичные страницы без логина (например, дорожная карта);
  • admin — панель инстанса («god-mode»): почта, регистрация, интеграции;
  • live — реальное время для совместного редактирования описаний задач;
  • api / worker / beat-worker — REST API и фоновые задачи на Celery.

Хранилище — PostgreSQL для данных, Redis для очередей и кеша, S3-совместимое хранилище (в self-host обычно MinIO) для вложений.

Сравнение с привычными вариантами:

Jira CloudLinearPlane self-hosted
Модель оплатыза пользователя/месза пользователя/месцена VPS, без лимита пользователей
Где данныесерверы Atlassianсерверы Linearваш сервер
Циклы/спринтыесть (доп. модуль)естьесть
Публичный роадмапограниченноестьесть (сервис space)
APIREST, с лимитами на планеGraphQLREST API

Честно о нюансах: self-host Plane — это 10+ контейнеров вместо одного, и между мажорными версиями иногда меняются переменные окружения и имена entrypoint-скриптов в образах. Перед обновлением стоит закладывать время на чтение changelog, а не жать pull вслепую.

Готовый docker-compose.yml

Структура каталогов на сервере:

/opt/plane/
├── pgdata/       # данные PostgreSQL
├── redisdata/    # данные Redis
├── uploads/      # файлы MinIO (вложения, аватары)
├── .env
└── docker-compose.yml
mkdir -p /opt/plane/{pgdata,redisdata,uploads}
cd /opt/plane
nano .env

Файл .env — здесь задаём пароли и адрес, на котором будет жить инстанс:

# PostgreSQL
POSTGRES_USER=plane
POSTGRES_PASSWORD=замените_на_случайный_пароль
POSTGRES_DB=plane
PGHOST=plane-db
DATABASE_URL=postgresql://plane:замените_на_случайный_пароль@plane-db:5432/plane

# Redis
REDIS_HOST=plane-redis
REDIS_URL=redis://plane-redis:6379/

# Django
SECRET_KEY=замените_на_строку_из_openssl_rand_-hex_32
WEB_URL=https://plane.your-domain.example
CORS_ALLOWED_ORIGINS=https://plane.your-domain.example
DOCKERIZED=1

# MinIO / S3
USE_MINIO=1
MINIO_ROOT_USER=plane
MINIO_ROOT_PASSWORD=замените_на_случайный_пароль
AWS_ACCESS_KEY_ID=plane
AWS_SECRET_ACCESS_KEY=замените_на_случайный_пароль
AWS_S3_ENDPOINT_URL=http://plane-minio:9000
AWS_S3_BUCKET_NAME=uploads
AWS_REGION=us-east-1

Пароли POSTGRES_PASSWORD и AWS_SECRET_ACCESS_KEY/MINIO_ROOT_PASSWORD должны совпадать с реальными паролями в соответствующих переменных — в примере выше это одно и то же значение, подставьте своё в оба места. SECRET_KEY сгенерируйте отдельной командой: openssl rand -hex 32.

Сам docker-compose.yml:

services:
  plane-db:
    image: postgres:15.7-alpine
    container_name: plane-db
    restart: unless-stopped
    env_file: .env
    volumes:
      - ./pgdata:/var/lib/postgresql/data

  plane-redis:
    image: redis:6.2.7-alpine
    container_name: plane-redis
    restart: unless-stopped
    volumes:
      - ./redisdata:/data

  plane-minio:
    image: minio/minio:latest
    container_name: plane-minio
    restart: unless-stopped
    command: server /export --console-address ":9090"
    env_file: .env
    volumes:
      - ./uploads:/export

  migrator:
    image: makeplane/plane-backend:latest
    container_name: plane-migrator
    restart: "no"
    command: ./bin/docker-entrypoint-migrator.sh
    env_file: .env
    depends_on:
      - plane-db
      - plane-redis

  api:
    image: makeplane/plane-backend:latest
    container_name: plane-api
    restart: unless-stopped
    command: ./bin/docker-entrypoint-api.sh
    env_file: .env
    depends_on:
      - migrator

  worker:
    image: makeplane/plane-backend:latest
    container_name: plane-worker
    restart: unless-stopped
    command: ./bin/docker-entrypoint-worker.sh
    env_file: .env
    depends_on:
      - api

  beat-worker:
    image: makeplane/plane-backend:latest
    container_name: plane-beat-worker
    restart: unless-stopped
    command: ./bin/docker-entrypoint-beat.sh
    env_file: .env
    depends_on:
      - api

  web:
    image: makeplane/plane-frontend:latest
    container_name: plane-web
    restart: unless-stopped
    env_file: .env
    depends_on:
      - api

  space:
    image: makeplane/plane-space:latest
    container_name: plane-space
    restart: unless-stopped
    env_file: .env
    depends_on:
      - api

  admin:
    image: makeplane/plane-admin:latest
    container_name: plane-admin
    restart: unless-stopped
    env_file: .env
    depends_on:
      - api

  live:
    image: makeplane/plane-live:latest
    container_name: plane-live
    restart: unless-stopped
    env_file: .env
    depends_on:
      - api

  proxy:
    image: makeplane/plane-proxy:latest
    container_name: plane-proxy
    restart: unless-stopped
    ports:
      - "127.0.0.1:8090:80"
    depends_on:
      - web
      - api
      - space
      - admin
      - live

Все сервисы попадают в одну сеть, которую Compose создаёт автоматически по имени каталога проекта — отдельно объявлять сеть не нужно.

Контейнер proxy — готовый nginx, который уже знает, как раскидывать пути /, /spaces/, /god-mode/ и /api/ между web, space, admin и api: маршрутизацию писать самому не придётся, только поставить HTTPS-терминацию перед ним (раздел ниже). Тег :latest удобен для первого запуска, но перед продакшеном стоит зафиксировать конкретные версии — сверяйтесь с тегами релизов в репозитории makeplane/plane на GitHub, между релизами имена entrypoint-скриптов и переменных окружения иногда меняются.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Первый запуск и создание рабочего пространства

Поднимаем стек и следим, что миграции прошли успешно:

docker compose up -d
docker compose logs -f migrator

Контейнер migrator должен завершиться и остановиться — это нормально, он не перезапускается. Если в логах ошибки подключения к базе — почти всегда дело в несовпадении пароля между POSTGRES_PASSWORD и DATABASE_URL.

Открываем http://<IP-сервера>:8090 (после настройки домена — сразу https://plane.your-domain.example). При первом заходе Plane предлагает мастер настройки инстанса: создаёте первого администратора (email + пароль), после чего в панели /god-mode можно:

  • разрешить или запретить публичную самостоятельную регистрацию — на боевом инстансе имеет смысл выключить и заводить сотрудников приглашениями;
  • настроить SMTP для писем-приглашений и уведомлений;
  • создать первое рабочее пространство (workspace) и пригласить туда команду.

Без настроенного SMTP приглашения по email отправляться не будут — на старте можно выдавать ссылку-приглашение вручную из интерфейса, но для постоянной работы почту стоит подключить сразу.

Домен, HTTPS и доступ через прокси

Порт proxy-контейнера вынесен на 127.0.0.1:8090, а не наружу — HTTPS перед ним закрываем отдельным реверс-прокси. Проще всего связкой с Caddy, который сам получает и продлевает сертификат Let's Encrypt:

# /etc/caddy/Caddyfile
plane.your-domain.example {
    reverse_proxy 127.0.0.1:8090
}

Если Caddy на сервере ещё не настроен, разберитесь в статье про установку Caddy с авто-SSL на VPS. Сервис live работает через WebSocket, и Caddy пробрасывает заголовки Upgrade/Connection автоматически — а вот в nginx их нужно прописывать явно, иначе совместное редактирование задач будет молча не работать.

После того как домен заработал по HTTPS, поправьте в .env значения WEB_URL и CORS_ALLOWED_ORIGINS на https://plane.your-domain.example и пересоздайте зависящие контейнеры:

docker compose up -d --force-recreate web space admin api live

Хранилище файлов: MinIO вместо AWS S3

Вложения к задачам, аватары и экспортированные файлы Plane хранит не на локальном диске сервиса, а в S3-совместимом хранилище — в этой конфигурации это контейнер plane-minio. Бакет uploads должен существовать до первой загрузки файла; если он не создаётся автоматически при старте API, создайте его вручную через консоль MinIO на http://<IP-сервера>:9090 (логин/пароль — значения MINIO_ROOT_USER/MINIO_ROOT_PASSWORD из .env) либо клиентом mc:

docker run --rm --network plane_default minio/mc \
  mc alias set plane http://plane-minio:9000 plane замените_на_пароль
docker run --rm --network plane_default minio/mc \
  mc mb plane/uploads

(точное имя сети, если каталог называется иначе, посмотрите в docker network ls.)

Если на сервере уже крутится отдельный MinIO или вы предпочитаете настоящий AWS S3, контейнер plane-minio можно убрать вовсе — поменяйте AWS_S3_ENDPOINT_URL, AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY на реквизиты внешнего хранилища и снимите USE_MINIO=1. Подробнее — в статье об установке и настройке MinIO на VPS.

Резервное копирование и обновление

Данные, которые действительно нужно бэкапить, — PostgreSQL (задачи, проекты, пользователи) и каталог uploads. Redis используется как очередь и кеш, его состояние не критично для восстановления.

Дамп базы:

docker exec plane-db pg_dump -U plane plane > /opt/plane/backups/plane-$(date +%F).sql

Восстановление:

cat /opt/plane/backups/plane-2026-08-20.sql | docker exec -i plane-db psql -U plane plane

Каталог вложений — обычным tar:

tar czf /opt/plane/backups/uploads-$(date +%F).tar.gz -C /opt/plane uploads

Если на сервере уже настроен регулярный бэкап других сервисов, логичнее подключить оба каталога Plane к общему пайплайну, а не городить отдельный cron-скрипт. При переезде стека на другой сервер порядок действий с volumes описан в статье про перенос Docker-проекта на другой сервер — для Plane он не отличается от переноса любого другого стека.

Обновление — не просто pull + up, потому что миграции базы должны применяться до старта API и воркеров:

docker compose pull
docker compose up -d plane-db plane-redis plane-minio
docker compose run --rm migrator
docker compose up -d

Перед мажорным обновлением (первая цифра версии) обязательно прочитайте changelog в репозитории проекта — миграции применяются автоматически и назад без бэкапа не откатываются.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Сколько ресурсов нужно VPS для команды из 15-20 человек?

Ориентировочно 4 vCPU и 8 ГБ RAM с запасом покрывают весь стек на одном сервере. Это ориентир: реальное потребление зависит от числа проектов, вложений и того, сколько людей одновременно работают в сервисе live.

Можно подключить внешний managed PostgreSQL вместо контейнера plane-db?

Да, укажите в DATABASE_URL адрес внешней базы, а сервис plane-db уберите из compose-файла. Про свою базу на отдельном сервере — статья про установку PostgreSQL на VPS.

Обязателен ли MinIO, или можно хранить файлы прямо на диске?

Self-host Plane ожидает S3-совместимое хранилище для вложений — MinIO из стека, внешний MinIO или AWS S3, но не голую файловую систему контейнера.

Обновление без даунтайма возможно?

На одном VPS без балансировщика — нет, при пересоздании api/web будет короткий простой в несколько секунд. Для команды до пары десятков человек это обычно некритично при обновлении в нерабочее время.

Чем Plane отличается от простой канбан-доски вроде Wekan?

Уровнем сложности: Plane — полноценный трекер с эпиками, циклами и ролями доступа, а не просто доска с карточками. Если нужен именно простой канбан без спринтов, легче по ресурсам будет Wekan.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →