Directus в Docker Compose: готовый файл
Directus — не совсем headless CMS в привычном смысле: он не хранит контент в своей схеме, а надстраивается поверх уже существующей базы данных и превращает её таблицы в REST и GraphQL API с готовой админкой. Если у вас есть рабочая PostgreSQL или MySQL база от старого проекта, ERP-системы или самописного бэкенда — Directus подключается к ней за один docker compose up -d, ничего не мигрируя и не трогая существующие данные. Ниже — рабочий docker-compose.yml и пошаговая настройка под эту задачу.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Чем Directus отличается от Strapi и других headless CMS
Большинство headless CMS (Strapi, KeystoneJS, Payload) проектируют схему контента внутри себя и генерируют под неё таблицы в базе. Directus работает в обратную сторону: он делает интроспекцию (introspection) уже существующей схемы — читает таблицы, столбцы, внешние ключи — и на их основе строит API и интерфейс админки. Собственных данных Directus почти не хранит: он добавляет в базу только свой набор системных таблиц с префиксом directus_ (пользователи, роли, права, настройки полей) рядом с вашими существующими таблицами, не изменяя и не переименовывая их.
Это решает конкретную задачу: у вас уже есть база с данными — каталог товаров, CRM-таблицы, логи заказов — и нужен человеко-понятный интерфейс для редактирования и API для фронтенда, но переписывать бэкенд под чужую схему CMS не хочется. Directus такой миграции не требует.
Если наоборот — проект стартует с нуля, Strapi часто удобнее: развитый плагинный рынок, предсказуемая модель ролей. Directus выигрывает там, где база данных первична, а CMS — надстройка над ней.
Готовый docker-compose.yml
Ниже — минимальный рабочий стек: контейнер Directus, Redis для кеша и WebSocket-синхронизации между инстансами (не обязателен для одного контейнера, но не мешает на будущее) и volume для загружаемых файлов. Саму базу данных сюда сознательно не добавляем — Directus в этом сценарии подключается к уже существующей базе, которая может жить на другом сервере, в управляемом облачном сервисе или в отдельном контейнере на этой же машине.
services:
directus:
image: directus/directus:11
container_name: directus
restart: unless-stopped
ports:
- "127.0.0.1:8055:8055"
volumes:
- directus_uploads:/directus/uploads
- directus_extensions:/directus/extensions
- ./templates:/directus/templates
environment:
KEY: ${DIRECTUS_KEY}
SECRET: ${DIRECTUS_SECRET}
ADMIN_EMAIL: ${ADMIN_EMAIL}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
DB_CLIENT: pg
DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT}
DB_DATABASE: ${DB_DATABASE}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
DB_SSL__REJECT_UNAUTHORIZED: "false"
PUBLIC_URL: https://cms.example.com
CORS_ENABLED: "true"
CORS_ORIGIN: "https://example.com"
WEBSOCKETS_ENABLED: "true"
CACHE_ENABLED: "true"
CACHE_STORE: redis
REDIS: redis://redis:6379
RATE_LIMITER_ENABLED: "true"
RATE_LIMITER_STORE: redis
RATE_LIMITER_REDIS: redis://redis:6379
STORAGE_LOCATIONS: local
STORAGE_LOCAL_ROOT: /directus/uploads
depends_on:
- redis
redis:
image: redis:7-alpine
container_name: directus-redis
restart: unless-stopped
volumes:
- redis_data:/data
volumes:
directus_uploads:
directus_extensions:
redis_data:
Если у вас нет своей PostgreSQL и нужна новая база рядом с Directus в том же compose-стеке — добавьте сервис db на образе postgres:16-alpine с volume под /var/lib/postgresql/data и укажите DB_HOST: db вместо внешнего адреса. Но раз задача — «обёртка поверх существующей базы», в 90% случаев DB_HOST указывает на уже работающий сервер: IP другого VPS, сокет управляемой БД в облаке или контейнер, поднятый отдельным compose-проектом на том же хосте (тогда нужна общая docker-сеть, см. ниже).
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверФайл .env и генерация ключей
Directus требует два секрета — KEY и SECRET, которые используются для подписи токенов и шифрования. Их нужно сгенерировать один раз и никогда не менять на проде: смена ключей инвалидирует все выданные токены и зашифрованные значения (например, пароли для сторонних интеграций, если такие настроены).
openssl rand -hex 32 # используем как DIRECTUS_KEY
openssl rand -hex 32 # используем как DIRECTUS_SECRET
Файл .env рядом с docker-compose.yml:
DIRECTUS_KEY=вставьте_первый_рандомный_хеш
DIRECTUS_SECRET=вставьте_второй_рандомный_хеш
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=сложный_пароль_минимум_16_символов
DB_HOST=10.0.0.5
DB_PORT=5432
DB_DATABASE=my_existing_db
DB_USER=directus_ro
DB_PASSWORD=пароль_от_бд
ADMIN_EMAIL и ADMIN_PASSWORD действуют только при первом запуске — Directus создаёт по ним первого администратора и роль Administrator. После первого старта эти переменные можно смело убрать из .env, они больше не используются повторно.
Для MySQL/MariaDB вместо DB_CLIENT: pg укажите DB_CLIENT: mysql, порт по умолчанию 3306. Directus одинаково хорошо работает поверх PostgreSQL, MySQL, MariaDB, SQLite, MS SQL и Oracle — но для интроспекции существующей схемы стабильнее всего ведут себя PostgreSQL и MySQL, это стоит учитывать при выборе.
Подключение к существующей базе без миграции
Ключевой момент всей задачи — не дать Directus «переизобрести» вашу схему. При первом запуске он подключается к базе по параметрам DB_*, читает список таблиц и столбцов и создаёт рядом только свои системные таблицы (directus_users, directus_roles, directus_permissions, directus_fields, directus_relations и ещё около двух десятков). Ваши таблицы остаются как есть — ни одна колонка не переименовывается и не удаляется.
Практическая последовательность:
- Поднимите стек:
docker compose up -d. - Зайдите в админку по адресу
http://your-server-ip:8055(или через домен, если уже настроен reverse-proxy) и войдите подADMIN_EMAIL/ADMIN_PASSWORD. - Откройте раздел Settings → Data Model. Все таблицы существующей базы уже видны, но помечены как невидимые для приложения (
hidden), пока вы явно не подтвердите их использование. - Для каждой нужной таблицы включите её в интерфейсе, задайте человекочитаемые названия полей (это не меняет сами столбцы — только их отображение в UI и в ответах API через
translations), настройте типы отображения (rich text, image, relation). - Если в базе есть внешние ключи, Directus сам предложит превратить их в связи (Many to One, One to Many, Many to Many) — подтвердите нужные, остальные можно оставить как обычные поля.
Если сервер с базой данных находится не в этом же compose-стеке, а в отдельном контейнере на том же хосте, подключите Directus к его docker-сети явно:
networks:
default:
name: directus_net
external_db_net:
external: true
name: имя_сети_вашей_бд
services:
directus:
networks:
- default
- external_db_net
Для базы данных на другом сервере проще и безопаснее пробросить порт через WireGuard или SSH-туннель, чем открывать PostgreSQL/MySQL наружу без ограничений — подробно о настройке базы на отдельном сервере есть в статье про установку PostgreSQL на VPS.
Роли, права доступа и API
По умолчанию в Directus две системные роли: Administrator (полный доступ ко всему, включая настройки) и Public (анонимный доступ, изначально без прав — ни одна коллекция не отдаётся без токена). Для реальных сценариев создаются кастомные роли под задачи: например, роль editor с правом читать и редактировать контент, но без доступа к настройкам пользователей, или роль api-readonly только с правом read на конкретные коллекции — её токен выдаётся фронтенду для публичного вывода данных.
Права настраиваются на уровне коллекции, а внутри неё — на уровне отдельных полей и даже строк через условные фильтры (например, «редактор видит только записи, где author_id равен его собственному ID»). Это удобно, когда в существующей базе уже есть поле, разграничивающее данные по клиентам или филиалам — можно построить multi-tenant доступ без единой строчки кода.
API доступен сразу в двух видах без дополнительной настройки:
GET https://cms.example.com/items/products
GET https://cms.example.com/items/products/42
POST https://cms.example.com/graphql
Для REST — обычные query-параметры (?filter, ?sort, ?fields, ?limit), для GraphQL — стандартная схема, сгенерированная автоматически из вашей модели данных. Токен доступа берётся в Settings → Access Tokens для сервисных ролей или выпускается через /auth/login для пользовательских сессий.
Продакшен: nginx, SSL и обновление
В compose-файле выше порт Directus проброшен только на 127.0.0.1:8055 — наружу он не смотрит, доступ снаружи идёт через reverse-proxy. Конфиг nginx с проксированием и поддержкой WebSocket (нужен для live-обновлений в админке):
server {
listen 443 ssl http2;
server_name cms.example.com;
ssl_certificate /etc/letsencrypt/live/cms.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cms.example.com/privkey.pem;
client_max_body_size 50m;
location / {
proxy_pass http://127.0.0.1:8055;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Подробнее про настройку nginx как reverse-proxy и выпуск сертификата — в отдельной статье про nginx-прокси на VPS.
Обновление Directus — это смена тега образа и рестарт:
docker compose pull directus
docker compose up -d directus
docker compose logs -f directus
Перед мажорным обновлением стоит прочитать changelog релиза: изредка требуется явная миграция схемы (directus database migrate:latest), которая обычно выполняется автоматически при старте новой версии — но логи первого запуска после апдейта проверить не помешает.
Резервное копирование в этой схеме сводится к бэкапу самой базы данных (она же хранит и ваш контент, и системные таблицы Directus) плюс volume directus_uploads с файлами:
pg_dump -h $DB_HOST -U $DB_USER -d $DB_DATABASE -F c -f directus_full_$(date +%F).dump
docker run --rm -v directus_directus_uploads:/data -v $(pwd):/backup alpine \
tar czf /backup/uploads_$(date +%F).tar.gz -C /data .
Для регулярных автоматических бэкапов удобнее вынести это в отдельный сервис — например, по инструкции про BorgBackup на VPS, который умеет дедупликацию и шифрование архивов «из коробки».
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Directus изменит структуру моей существующей базы данных?
Нет. Он только добавляет собственные таблицы с префиксом directus_ рядом с вашими и не трогает существующие столбцы, индексы или данные. Единственное, что он может предложить — создать внешние ключи для связей, которых раньше не было, но это подтверждается вручную для каждой связи.
Можно ли подключить Directus к базе, которая уже используется другим приложением одновременно?
Да, это штатный сценарий. Directus не блокирует таблицы монопольно — он читает и пишет через обычные SQL-запросы, как любое другое приложение с доступом к базе. Стоит только завести отдельного пользователя БД с правами, ограниченными нужными таблицами, чтобы разграничить доступ.
Что если в базе нет первичных ключей или они составные?
Directus требует у каждой таблицы, которую он должен показывать в интерфейсе, единственный первичный ключ (обычный id или UUID). Таблицы без первичного ключа или с составным ключом можно оставить недоступными в UI, но использовать через прямые SQL-вьюхи — либо добавить суррогатный первичный ключ в существующую таблицу.
Нужен ли Redis обязательно?
Нет, без него Directus работает с in-memory кешем и WebSocket-очередью — этого достаточно для одного контейнера с несколькими пользователями. Redis становится нужен, когда вы масштабируете Directus на несколько инстансов за балансировщиком: без общего Redis они не будут синхронизировать кеш и realtime-события между собой.
Как ограничить, какие таблицы вообще видит Directus?
В интерфейсе — тем же переключателем hidden в Data Model, но радикальнее — на уровне БД: выдайте пользователю DB_USER, под которым подключается Directus, права только на нужные таблицы через GRANT SELECT, INSERT, UPDATE ON таблица TO directus_user;, тогда лишние таблицы он даже не увидит при интроспекции.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →