Directus на VPS: админка и API поверх существующей базы — установка и Docker Compose
Directus — это не обычная CMS, а «обёртка» над уже существующей SQL-базой. Он подключается к вашим таблицам как обычный клиент, читает их структуру и превращает в REST и GraphQL API с современной админкой — без миграции данных и без дублирующей схемы. Если у вас есть рабочая база от старого сайта, CRM или самописного бэкенда, Directus даёт ей человеческий интерфейс за час. Свой сервер здесь важен по двум причинам: данные не уезжают в чужой SaaS, а вы полностью контролируете доступ, бэкапы и юрисдикцию.
Что понадобится
- VPS под Ubuntu 24.04 LTS: минимум 1 vCPU / 1 ГБ RAM / 20 ГБ SSD, комфортно — 2 vCPU / 2–4 ГБ / 40 ГБ SSD;
- домен и SSL-сертификат: админка и API должны открываться только по HTTPS через reverse proxy;
- существующая база данных. Разработчики рекомендуют PostgreSQL, также поддерживаются MySQL/MariaDB, SQLite, MS SQL и Oracle;
- Node.js актуальной LTS-версии, git и пакет build-essential.
Если СУБД живёт на том же сервере, память под неё считайте отдельно: 1 ГБ на связку Directus + PostgreSQL под реальной нагрузкой мало. Подобрать подходящую конфигурацию можно в нашем каталоге VPS.
Установка пошагово
Шаг 1. Готовим систему. Обновляем пакеты, создаём отдельного пользователя (под root работать не стоит даже на тестовом сервере) и настраиваем firewall: наружу нужны только SSH, 80 и 443, а порт 8055 Directus слушает локально.
apt update && apt upgrade -y
adduser directus
usermod -aG sudo directus
su - directus
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Шаг 2. Ставим Node.js через nvm. Версия из репозитория Ubuntu обычно устаревшая, поэтому берём актуальную LTS-ветку через nvm.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install --lts
nvm use --lts
node -v
npm -v
sudo apt install -y build-essential git
Шаг 3. Готовим доступ к существующей базе. Directus должен уметь не только читать и писать данные, но и создавать свои служебные таблицы, поэтому прав ему нужно больше, чем обычному клиенту.
sudo -u postgres psql
CREATE USER directus_user WITH ENCRYPTED PASSWORD 'сложный_пароль';
GRANT ALL PRIVILEGES ON DATABASE legacy_shop TO directus_user;
\c legacy_shop
GRANT ALL ON SCHEMA public TO directus_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO directus_user;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO directus_user;
\q
Начиная с PostgreSQL 15 схема public не даёт права CREATE никому, кроме владельца базы. Без строки GRANT ALL ON SCHEMA public установка упадёт на этапе bootstrap с ошибкой прав. Перед первым запуском обязательно снимите дамп: Directus не должен ломать существующие таблицы, но бэкап на продовой базе — не опция.
pg_dump -U postgres legacy_shop > ~/legacy_shop_backup_$(date +%F).sql
Шаг 4. Ставим Directus вручную, а не через мастер. Мастер create directus-project создаёт новую базу с нуля, нам же нужно указать данные существующей.
mkdir -p /opt/directus
sudo chown directus:directus /opt/directus
cd /opt/directus
npm init -y
npm install directus
Шаг 5. Генерируем секреты и создаём .env. Значения KEY и SECRET используются для подписи токенов и шифрования: их генерируют один раз и на проде не меняют.
KEY=$(openssl rand -hex 16)
SECRET=$(openssl rand -hex 32)
cat > .env <<EOF
KEY=${KEY}
SECRET=${SECRET}
DB_CLIENT=pg
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=legacy_shop
DB_USER=directus_user
DB_PASSWORD=сложный_пароль
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=другой_сложный_пароль
PUBLIC_URL=https://directus.example.com
PORT=8055
HOST=127.0.0.1
EOF
Значение DB_CLIENT зависит от СУБД: pg для PostgreSQL, mysql для MySQL/MariaDB, sqlite3 для SQLite, mssql для MS SQL, oracledb для Oracle.
Шаг 6. Инициализация и запуск. Команда bootstrap создаёт служебные таблицы directus_* в вашей базе, не затрагивая остальные.
npx directus bootstrap
npx directus start
Убедившись, что сервер поднялся на порту 8055, остановите его (Ctrl+C) и оформите автозапуск через systemd.
[Unit]
Description=Directus
After=network.target postgresql.service
[Service]
Type=simple
User=directus
WorkingDirectory=/opt/directus
ExecStart=/home/directus/.nvm/versions/node/v20.*/bin/node /opt/directus/node_modules/.bin/directus start
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
Путь к бинарнику node уточните командой which node под пользователем directus и подставьте точный путь вместо маски. Затем включите сервис.
sudo systemctl daemon-reload
sudo systemctl enable --now directus
sudo systemctl status directus
Логи в реальном времени смотрите через journalctl -u directus -f. Если сервис не стартует, чаще всего виноват неверный путь к node или права на файл .env.
Шаг 7. Nginx и SSL. Directus слушает 127.0.0.1:8055 и наружу не смотрит — доступ организуем через reverse proxy с пробросом WebSocket для realtime-функций админки.
server {
listen 80;
server_name directus.example.com;
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;
client_max_body_size 50M;
}
}
Параметр client_max_body_size увеличен под загрузку файлов: по умолчанию nginx режет тело запроса на 1 МБ, и медиатека падает с ошибкой 413. Сертификат выпускаем через certbot.
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d directus.example.com
После этого обновите PUBLIC_URL на https и перезапустите сервис, иначе часть ссылок в письмах и админке будет генерироваться с неверной схемой.
Docker Compose
Вариант в контейнерах проще в поддержке и обновлении. Стек ниже подключается к уже существующей базе (её сознательно не добавляем — она может жить на другом сервере), а Redis нужен для кеша и WebSocket-синхронизации между инстансами.
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:
Рядом с файлом создайте .env.
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=пароль_от_бд
Хеши сгенерируйте через openssl rand -hex 32. Учтите: ADMIN_EMAIL и ADMIN_PASSWORD работают только при первом запуске — они создают первого администратора, дальше их можно убрать из .env. Для MySQL замените DB_CLIENT на mysql и порт на 3306. Поднимаем стек командой docker compose up -d, после чего админка доступна на 127.0.0.1:8055.
Частые ошибки и решения
ECONNREFUSED 127.0.0.1:5432 или KnexTimeoutError. Directus не дотянулся до базы. Если PostgreSQL в отдельном контейнере в той же compose-сети, DB_HOST должен быть именем сервиса (db), а не 127.0.0.1 — это классическая ошибка при переносе с локальной разработки на сервер.
permission denied for schema public. Не хватает прав на создание служебных таблиц на PostgreSQL 15+. Выдайте пользователю Directus команду GRANT ALL ON SCHEMA public — пример в шаге 3.
Коллекции не отображаются. Существование таблицы в базе ещё не делает её коллекцией: Directus не знает о ней, пока вы не скажете «управляй этой таблицей» в Settings → Data Model. Проверить список можно запросом SELECT collection, icon, singleton FROM directus_collections.
Белый экран за nginx. Почти всегда виноват PUBLIC_URL. Если он остался http://localhost:8055, админка грузит статику с localhost из браузера пользователя — отсюда белый экран и 404 по JS/CSS.
413 Payload Too Large при загрузке файлов. Nginx по умолчанию режет тело запроса на 1 МБ. Поднимите client_max_body_size до 50–100M.
403 вместо данных. Публичная роль Public по умолчанию не имеет доступа ни к одной коллекции — это защита от случайной утечки. Для отладки выпустите статический токен сервисной роли и проверьте запрос напрямую:
curl -H "Authorization: Bearer <токен>" https://cms.example.com/items/products?limit=5
Если так тоже 403 — дело в правах роли, а не во фронтенде или куках сессии.
Контейнер перезапускается по кругу. Гонка старта: Directus поднимается раньше, чем база готова принимать соединения. Добавьте в compose depends_on с condition: service_healthy и healthcheck для БД.
Бэкап и обновление
Directus хранит метаданные (коллекции, поля, права) в тех же directus_*-таблицах, что и ваши данные, поэтому бэкап — это минимум три слоя: база, загруженные файлы и .env.
Критично сохранить KEY и SECRET вместе с базой. SECRET подписывает JWT-сессии, а часть настроек (например, SSO) зашифрована с привязкой к этим значениям. Восстановите дамп на сервер с другими ключами — зашифрованные настройки станут нечитаемыми, а все сессии разлогинятся. Поэтому .env бэкапится вместе с базой, одним архивом, без исключений.
docker exec -t directus-db pg_dump -U directus -d directus --format=custom > /backups/directus/db_$(date +%F_%H%M).dump
pg_restore --list /backups/directus/db_2026-08-30_0300.dump | head -20
Формат custom даёт сжатие из коробки и позволяет восстанавливать выборочно. Вторая команда сразу проверяет целостность дампа — если она падает, бэкап нерабочий, и лучше узнать это сейчас, а не в момент аварии. Медиатеку бэкапим отдельным архивом.
docker run --rm -v directus_uploads:/data -v /backups/directus:/backup alpine tar czf /backup/uploads_$(date +%F).tar.gz -C /data .
Полезен и schema snapshot — экспорт структуры коллекций, полей и прав в YAML, отдельно от данных.
docker exec directus npx directus schema snapshot ./snapshot.yaml
Это не замена дампу, а второй слой: его применяют на чистой базе командой schema apply, чтобы поднять новое окружение с той же конфигурацией. Обновление Directus в Docker — это смена тега образа и перезапуск.
docker compose pull directus
docker compose up -d directus
docker compose logs -f directus
Перед апгрейдом снимите бэкап: если миграция таблиц directus_* прервётся (например, контейнер убьют по OOM), схема останется в промежуточном состоянии, и надёжнее поднять базу из дампа, чем чинить её вручную.
Итог
После установки пройдитесь по Settings → Data Model и включите нужные таблицы как коллекции (Managed by Directus) — это единственный ручной шаг миграции. Затем настройте роли: Administrator для вас, Public для анонимного доступа и отдельные сервисные роли с токенами для фронтенда. API сразу доступен в двух видах — REST (/items/products) и GraphQL (/graphql). Если планируете несколько инстансов Directus, переключите файлы на S3-совместимое хранилище и поставьте CDN перед трансформациями изображений. И не забудьте про cron на бэкапы базы, файлов и .env. Для всего этого хватит недорогого сервера — подобрать подходящий можно в каталоге VPS.