bolt Valebyte VPS від $4/міс — NVMe, запуск за 60 секунд.

Отримати VPS arrow_forward
eco Початковий Туторіал

Metabase на VPS: дашборди для PostgreSQL за вечір

calendar_month Sep 25, 2026 schedule 20 хв. читання visibility 55 переглядів
Metabase на VPS: дашборды по PostgreSQL за вечер
info

Потрібен сервер для цього гайду? Ми пропонуємо виділені сервери та VPS у 50+ країнах з миттєвим налаштуванням.

Потрібен сервер для цього гайду?

Розгорніть VPS або виділений сервер за хвилини.

Metabase на VPS: дашборди з PostgreSQL за вечір

TL;DR

Metabase можна розгорнути на VPS за один вечір за допомогою Docker Compose, підключити до PostgreSQL і відкрити дашборди через HTTPS. У цьому посібнику ми встановимо Metabase 0.57.x, PostgreSQL 17 і Caddy, налаштуємо окрему базу застосунку, користувача лише для читання, резервне копіювання та базовий захист сервера.

  • Для невеликої команди достатньо VPS із 2 vCPU, 4 ГБ RAM і SSD від 40 ГБ.
  • Metabase запускатиметься в Docker-контейнері, а PostgreSQL — зберігатиме налаштування, користувачів і запитання.
  • Джерело аналітичних даних буде доступне Metabase через внутрішню Docker-мережу.
  • Caddy автоматично отримає TLS-сертифікат Let's Encrypt для домену.
  • Доступ до робочих даних здійснюватиметься через окремого користувача PostgreSQL із правами лише на читання.
  • Щоденний бекап PostgreSQL і конфігурації надсилатиметься до зовнішнього S3-сховища.

1. TL;DR

У цьому розділі вже наведено короткий план: ми розміщуємо Metabase на VPS, запускаємо його через Docker Compose, підключаємо PostgreSQL і публікуємо інтерфейс через HTTPS. Докладні команди та конфігураційні файли наведено нижче.

2. Зміст

Стаття розрахована на власника VPS, який хоче самостійно отримати робочу систему бізнес-аналітики без обов’язкової залежності від хмарного сервісу. Приклад орієнтований на Ubuntu Server 24.04 LTS, домен bi.example.com і чистий сервер із публічною IPv4-адресою.

Команди передбачають підключення до сервера під користувачем із правами sudo. Якщо у вас уже є робоча база PostgreSQL, частину зі створення демонстраційної бази можна пропустити, але мережеву конфігурацію та конфігурацію користувачів усе одно варто перевірити.

3. Що ми налаштовуємо і навіщо

Що таке Metabase

Metabase — вебзастосунок для аналізу даних і створення дашбордів. Він підключається до PostgreSQL, MySQL, ClickHouse та інших джерел, після чого дає змогу створювати таблиці, графіки, KPI-картки та фільтри без написання SQL для кожного запиту.

Для команди Metabase зазвичай стає внутрішнім порталом аналітики: керівник переглядає виручку, маркетолог — воронку, служба підтримки — кількість звернень, а розробник — технічні метрики. На відміну від BI-систем, побудованих лише навколо SQL, Metabase пропонує візуальний конструктор запитань, але водночас зберігає повноцінний SQL-редактор.

Що працюватиме після налаштування

Підсумкова схема складатиметься з кількох контейнерів. Metabase відповідатиме за вебінтерфейс і дашборди, PostgreSQL — за внутрішню базу Metabase, а окремий контейнер PostgreSQL використовуватиметься як демонстраційне джерело даних. Caddy прийматиме HTTPS-запити й передаватиме їх Metabase.

Компонент Призначення Порт
Metabase 0.57.x Вебінтерфейс, запити й дашборди 3000 у мережі
PostgreSQL 17 Метадані Metabase 5432 у мережі
PostgreSQL 17 Демонстраційні аналітичні дані 5432 у мережі
Caddy 2 Reverse proxy і HTTPS 80 і 443

Self-hosted або хмарний Metabase

Хмарний варіант не потребує обслуговування ОС, Docker, TLS і резервних копій. Він підходить, якщо важливіше швидко почати аналізувати дані, ніж контролювати інфраструктуру. Однак вартість зростає разом із кількістю користувачів, обсягом функцій і вимогами до корпоративного доступу.

Self-hosted Metabase на VPS дає контроль над розміщенням даних, мережею, версіями та витратами. Такий варіант корисний, якщо в компанії є спеціаліст, здатний оновлювати контейнери, перевіряти бекапи та реагувати на інциденти. Важливо розуміти: VPS не перетворює систему на повністю керований сервіс. Відповідальність за безпеку й відновлення залишається на власнику.

Як Metabase працює з PostgreSQL

У Metabase є власна application database. У ній зберігаються користувачі, права доступу, налаштування підключень, збережені запитання та структура колекцій. Для production не слід використовувати вбудовану H2-базу: вона призначена переважно для простого тестування та міграції.

Робоча база даних підключається окремо. Metabase не копіює автоматично всі таблиці на VPS, а виконує запити до джерела під час відкриття запитання або дашборда. Тому пропускна здатність і затримка мережі між Metabase і PostgreSQL впливають на швидкість візуалізацій.

4. Яка конфігурація VPS потрібна для цього завдання

Ресурси залежать не стільки від самого інтерфейсу Metabase, скільки від кількості одночасних запитів і швидкості PostgreSQL. Metabase може працювати на невеликому сервері, якщо дашборди відкривають кілька людей, а запити використовують індекси. Великі таблиці, десятки користувачів і часте оновлення графіків потребуватимуть більше CPU та пам’яті.

Сценарій CPU RAM Диск Мережа
Тестування та особистий проєкт 1–2 vCPU 2–4 ГБ 30–40 ГБ SSD 100 Мбіт/с
Невелика команда до 15 користувачів 2–4 vCPU 4–8 ГБ 60–100 ГБ SSD 100–500 Мбіт/с
Кілька десятків користувачів 4–8 vCPU 8–16 ГБ 100–250 ГБ NVMe 500 Мбіт/с і вище

Практична конфігурація

Для описаного у статті сценарію розумна відправна точка — 2 vCPU, 4 ГБ RAM, 60 ГБ SSD або NVMe, одна публічна IPv4 і канал від 100 Мбіт/с. На диску зберігатимуться Docker-образи, PostgreSQL, логи й тимчасові файли. Якщо джерело даних розміщене на іншому сервері, основний обсяг сховища потрібен не для таблиць, а для операційної системи, бекапів і кешу.

Можна взяти відповідний VPS із такими характеристиками або вибрати аналогічний сервер в іншого провайдера. Під час вибору перевірте, що дозволені вхідні з’єднання на 80 і 443, доступне встановлення Docker і є можливість робити знімки диска або підключати додаткове сховище.

Коли потрібен dedicated

Виділений сервер виправданий, якщо PostgreSQL уже обробляє важкі аналітичні запити, дані займають сотні гігабайт, потрібні гарантовані IOPS або на одному сервері планується запуск кількох сервісів. Dedicated також зручний для ETL, локального сховища резервних копій і великих materialized views.

Для звичайного Metabase перехід на dedicated сам собою не вирішує проблему повільних графіків. Спочатку потрібно перевірити плани виконання SQL, індекси, агрегації та кешування. Якщо запит виконується дві хвилини через повне сканування таблиці, додаткові ядра не завжди дадуть помітний результат.

Вибір локації

Локація впливає на затримку до PostgreSQL і користувачів. Якщо база даних і Metabase розташовані в різних регіонах, кожен інтерактивний запит отримує додаткову мережеву затримку. Для невеликих таблиць це майже непомітно, а для великої кількості послідовних запитів — уже суттєво.

Розміщуйте Metabase у тому самому регіоні, де розташована основна база або застосунок, який постачає дані. Для користувачів із кількох країн обирайте регіон із прийнятною затримкою або використовуйте CDN лише для статичного вмісту, не намагаючись кешувати персоналізовані відповіді Metabase.

5. Підготовка сервера

Створення користувача та оновлення Ubuntu

Нижче передбачається Ubuntu Server 24.04 LTS. Підключіться через SSH під користувачем, створеним провайдером, і замініть deploy на потрібне ім’я. Не закривайте поточну SSH-сесію до перевірки входу під новим користувачем.

# Обновляем индекс пакетов и устанавливаем исправления безопасности
sudo apt update && sudo apt full-upgrade -y

# Создаём отдельного администратора для повседневной работы
sudo adduser deploy

# Разрешаем пользователю выполнять административные команды
sudo usermod -aG sudo deploy

З локального комп’ютера скопіюйте публічний SSH-ключ. Команда не переносить приватний ключ на сервер, а додає лише відкритий ключ до файлу авторизації.

# Выполняем на локальном компьютере
ssh-copy-id deploy@SERVER_IP

# Проверяем вход новым пользователем
ssh deploy@SERVER_IP

Обмеження SSH

Після успішної перевірки входу за ключем забороніть автентифікацію за паролем і вхід root. Перед зміною файлу переконайтеся, що ключ справді працює в окремому вікні термінала.

# Открываем конфигурацию SSH
sudo nano /etc/ssh/sshd_config.d/ hardening.conf

У наведеній вище команді пробіл в імені каталогу неприпустимий. Використовуйте правильний варіант:

# Создаём отдельный файл настроек SSH
sudo nano /etc/ssh/sshd_config.d/hardening.conf

Вставте такі параметри:

PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
X11Forwarding no
# Проверяем синтаксис конфигурации SSH
sudo sshd -t

# Применяем настройки без завершения текущих подключений
sudo systemctl reload ssh

Firewall і fail2ban

Відкрийте SSH, HTTP і HTTPS. Якщо SSH працює на нестандартному порту, вкажіть його замість 22. UFW не має блокувати вже встановлену SSH-сесію, але порядок дій усе одно краще дотримуватися: спочатку дозволити SSH, потім увімкнути firewall.

# Устанавливаем базовые инструменты и защиту от перебора паролей
sudo apt install -y ca-certificates curl gnupg git jq unzip ufw fail2ban

# Разрешаем SSH и веб-трафик
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Включаем firewall
sudo ufw --force enable

# Проверяем активные правила
sudo ufw status verbose

Створіть локальну конфігурацію fail2ban. Вона блокуватиме адреси, які багаторазово помиляються під час входу в SSH.

# Создаём настройки jail для SSH
sudo tee /etc/fail2ban/jail.d/sshd.local > /dev/null <<'EOF'
[sshd]
enabled = true
backend = systemd
port = 22
maxretry = 5
findtime = 10m
bantime = 1h
EOF

# Запускаем fail2ban и добавляем его в автозагрузку
sudo systemctl enable --now fail2ban

# Проверяем состояние SSH-защиты
sudo fail2ban-client status sshd

Синхронізація часу та базові параметри

Правильний час важливий для TLS, логів і cron-завдань. В Ubuntu зазвичай уже використовується systemd-timesyncd, тому достатньо перевірити його стан.

# Проверяем синхронизацию времени
timedatectl status

# Проверяем свободное место, память и загрузку
df -h
free -h
uptime

6. Встановлення ПЗ — покроково

Встановлення Docker Engine і Compose

Для production використовуйте офіційний репозиторій Docker, а не застарілий пакет зі стандартного репозиторію Ubuntu. На момент підготовки посібника цільовий стек — Docker Engine 27.x або новіший сумісний реліз і Docker Compose v2.

# Создаём каталог для ключей APT
sudo install -m 0755 -d /etc/apt/keyrings

# Загружаем официальный ключ Docker
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# Разрешаем чтение ключа пакетным менеджером
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Добавляем официальный репозиторий Docker для Ubuntu 24.04
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Устанавливаем Docker Engine, CLI и Compose plugin
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Разрешаем пользователю deploy обращаться к Docker без sudo
sudo usermod -aG docker "$USER"

Вийдіть із SSH і підключіться знову, щоб оновилася група користувача.

# Проверяем версии Docker и Compose
docker version
docker compose version

# Проверяем запуск тестового контейнера
docker run --rm hello-world

Створення структури проєкту

Усі файли проєкту розмістимо в /opt/metabase. Секрети зберігатимуться у файлі .env, який не слід додавати до Git і публікувати у вебкаталозі.

# Создаём каталоги проекта и хранения Caddy
sudo mkdir -p /opt/metabase/{postgres-init,analytics-init,caddy}
sudo chown -R "$USER":"$USER" /opt/metabase
cd /opt/metabase

# Создаём файл переменных окружения с закрытыми правами
touch .env
chmod 600 .env

Підготовка змінних середовища

Згенеруйте випадкові паролі. Пароль застосунку Metabase не повинен збігатися з паролем користувача аналітичної бази. Для production зберігайте копію секретів у менеджері паролів, інакше відновлення після втрати VPS буде ускладнене.

# Генерируем три независимых случайных значения
openssl rand -base64 32
openssl rand -base64 32
openssl rand -base64 32

Створіть /opt/metabase/.env із таким вмістом. Замініть усі значення після знака рівності.

POSTGRES_DB=metabase
POSTGRES_USER=metabase
POSTGRES_PASSWORD=CHANGE_ME_APP_DB_PASSWORD

ANALYTICS_DB=analytics
ANALYTICS_USER=analytics_reader
ANALYTICS_PASSWORD=CHANGE_ME_ANALYTICS_PASSWORD

MB_ENCRYPTION_SECRET_KEY=CHANGE_ME_LONG_RANDOM_SECRET
MB_SITE_URL=https://bi.example.com
TZ=UTC

Змінна MB_ENCRYPTION_SECRET_KEY використовується для шифрування чутливих значень у конфігурації Metabase. Не змінюйте її після першого запуску без розуміння наслідків: збережені налаштування підключень можуть стати недоступними.

Ініціалізація демонстраційної бази

Якщо у вас уже є PostgreSQL, цей крок можна адаптувати під наявну схему. Для самостійної перевірки створіть невеликий набір таблиць із замовленнями, клієнтами та товарами.

# Создаём SQL-файл начальной схемы аналитической базы
cat > analytics-init/001-schema.sql <<'EOF'
CREATE TABLE customers (
    id BIGSERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    country TEXT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    customer_id BIGINT NOT NULL REFERENCES customers(id),
    amount NUMERIC(12,2) NOT NULL,
    status TEXT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

INSERT INTO customers (name, country)
SELECT
    'Customer ' || n,
    CASE n % 4
        WHEN 0 THEN 'RU'
        WHEN 1 THEN 'KZ'
        WHEN 2 THEN 'DE'
        ELSE 'PL'
    END
FROM generate_series(1, 100) AS n;

INSERT INTO orders (customer_id, amount, status, created_at)
SELECT
    (random()  99 + 1)::bigint,
    round((random()  490 + 10)::numeric, 2),
    CASE
        WHEN n % 10 = 0 THEN 'cancelled'
        ELSE 'paid'
    END,
    now() - ((random()  180)::int || ' days')::interval
FROM generate_series(1, 3000) AS n;

CREATE INDEX orders_created_at_idx ON orders (created_at);
CREATE INDEX orders_customer_id_idx ON orders (customer_id);
CREATE INDEX orders_status_idx ON orders (status);

CREATE USER analytics_reader WITH PASSWORD 'CHANGE_ME_ANALYTICS_PASSWORD';
GRANT CONNECT ON DATABASE analytics TO analytics_reader;
GRANT USAGE ON SCHEMA public TO analytics_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO analytics_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO analytics_reader;
EOF

У демонстраційному SQL пароль має збігатися з ANALYTICS_PASSWORD у файлі .env. Для реального проєкту краще створювати користувача окремою командою або шаблонізувати SQL, щоб секрет не дублювався в кількох файлах.

Docker Compose

Створіть файл docker-compose.yml. Версії образів зафіксовані тегами, щоб раптове оновлення не змінило поведінку системи. Перед оновленням спочатку ознайомтеся з release notes Metabase і зробіть бекап.

services:
  metabase-db:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      TZ: ${TZ}
    volumes:
      - metabase_db_data:/var/lib/postgresql/data
    networks:
      - internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5

  analytics-db:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${ANALYTICS_DB}
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      TZ: ${TZ}
    volumes:
      - analytics_db_data:/var/lib/postgresql/data
      - ./analytics-init:/docker-entrypoint-initdb.d:ro
    networks:
      - internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d ${ANALYTICS_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5

  metabase:
    image: metabase/metabase:v0.57.0
    restart: unless-stopped
    depends_on:
      metabase-db:
        condition: service_healthy
      analytics-db:
        condition: service_healthy
    environment:
      MB_DB_TYPE: postgres
      MB_DB_DBNAME: ${POSTGRES_DB}
      MB_DB_PORT: 5432
      MB_DB_USER: ${POSTGRES_USER}
      MB_DB_PASS: ${POSTGRES_PASSWORD}
      MB_DB_HOST: metabase-db
      MB_ENCRYPTION_SECRET_KEY: ${MB_ENCRYPTION_SECRET_KEY}
      MB_SITE_URL: ${MB_SITE_URL}
      JAVA_TIMEZONE: ${TZ}
    expose:
      - "3000"
    networks:
      - internal

  caddy:
    image: caddy:2.9
    restart: unless-stopped
    depends_on:
      - metabase
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - internal

volumes:
  metabase_db_data:
  analytics_db_data:
  caddy_data:
  caddy_config:

networks:
  internal:
    driver: bridge

У Metabase 0.57.x актуальний образ може мати новіший patch-тег. Перед запуском перевірте доступність вибраного тега в офіційному реєстрі. Для production важливо фіксувати конкретну версію, а не використовувати latest.

7. Конфігурація, HTTPS і перевірка

Налаштування Caddy

До запуску Caddy створіть DNS-запис A для bi.example.com, що вказує на публічну IP-адресу VPS. Якщо використовується IPv6, переконайтеся, що запис AAAA також веде на цей сервер і firewall дозволяє IPv6-трафік.

# Создаём конфигурацию reverse proxy
cat > caddy/Caddyfile <<'EOF'
bi.example.com {
    encode gzip zstd
    reverse_proxy metabase:3000

    header {
        X-Content-Type-Options "nosniff"
        X-Frame-Options "SAMEORIGIN"
        Referrer-Policy "strict-origin-when-cross-origin"
    }

    log {
        output file /data/access.log
        format json
    }
}
EOF

# Проверяем, что домен резолвится в IP сервера
dig +short bi.example.com

Якщо утиліту dig не встановлено, додайте пакет dnsutils. Результат має містити адресу саме вашого VPS.

# Устанавливаем утилиту проверки DNS при необходимости
sudo apt install -y dnsutils

# Проверяем DNS-запись ещё раз
dig +short bi.example.com

Перший запуск

Перед стартом перевірте файл Compose і змінні середовища. Docker Compose підставить значення з .env. Помилки в назвах змінних часто призводять до того, що контейнер запускається з порожнім паролем або не може підключитися до PostgreSQL.

# Проверяем итоговую конфигурацию Compose
docker compose config

# Запускаем контейнеры в фоновом режиме
docker compose up -d

# Смотрим состояние всех сервисов
docker compose ps

# Смотрим последние сообщения Metabase
docker compose logs --tail=100 metabase

Перший запуск Metabase може тривати кілька хвилин: контейнер застосовує міграції application database. Відкрийте у браузері https://bi.example.com. Caddy автоматично запитає сертифікат Let's Encrypt, якщо DNS уже оновився, а порти 80/443 доступні ззовні.

Початкове налаштування Metabase

У майстрі першого запуску створіть адміністративний обліковий запис з унікальним довгим паролем. Не використовуйте пароль від VPS або PostgreSQL. Вкажіть робочий часовий пояс, валюту та назву організації, після чого додайте підключення до аналітичної бази.

Поле Metabase Значення
Database type PostgreSQL
Host analytics-db
Port 5432
Database name analytics
Username analytics_reader
Password Значення ANALYTICS_PASSWORD

Ім’я analytics-db — це DNS-ім’я сервісу всередині Docker-мережі. Не вказуйте localhost: усередині контейнера Metabase воно означає сам контейнер Metabase, а не PostgreSQL.

Перевірка з’єднання з PostgreSQL

Перевірити мережу та права можна з контейнера Metabase або тимчасового клієнта PostgreSQL. Спочатку переконайтеся, що база відповідає.

# Проверяем состояние контейнеров
docker compose ps

# Проверяем доступность PostgreSQL из отдельного временного контейнера
docker run --rm --network metabase_internal \
  -e PGPASSWORD="$ANALYTICS_PASSWORD" postgres:17 \
  psql -h analytics-db -U analytics_reader -d analytics -c \
  "SELECT count() AS orders_count FROM orders;"

Назва мережі може відрізнятися, якщо каталог проєкту має іншу назву. Дізнайтеся фактичну назву командою docker network ls. Для перевірки застосунку використовуйте HTTP-запит із самого сервера.

# Проверяем локальный HTTPS-ответ Caddy
curl -I https://bi.example.com

# Проверяем срок и параметры сертификата
curl -vI https://bi.example.com 2>&1 | grep -E "SSL connection|subject:|expire date:"

# Проверяем логи Caddy при проблеме с TLS
docker compose logs --tail=100 caddy

Створення першого дашборда

У розділі New створіть запит на основі таблиці orders. Для простої картки виручки виберіть суму поля amount із фільтром status = paid. Для графіка продажів згрупуйте суму за днем поля created_at. Потім збережіть запити в колекцію та об’єднайте їх на новому дашборді.

Для SQL-запиту можна використати наведений нижче запит. Параметр дати краще додавати через інтерфейс Metabase, щоб користувачі могли змінювати період без редагування SQL.

SELECT
    date_trunc('day', created_at)::date AS day,
    SUM(amount) AS revenue,
    COUNT() AS paid_orders
FROM orders
WHERE status = 'paid'
GROUP BY 1
ORDER BY 1;

Права доступу

Адміністратор має створити групи користувачів і надати їм доступ лише до потрібних колекцій. Не робіть усіх співробітників адміністраторами: права адміністратора дають змогу змінювати підключення, користувачів і налаштування всієї системи.

Якщо використовуються чутливі дані, створіть окремі представлення PostgreSQL замість надання доступу до вихідних таблиць. Наприклад, представлення може приховувати email, телефон і внутрішні ідентифікатори, залишаючи лише агреговані показники. Також вимкніть або обмежте користувацькі SQL-запити, якщо аудиторія має працювати лише з підготовленими моделями.

8. Резервні копії та обслуговування

Що необхідно зберігати

Головна цінність Metabase міститься не в контейнері, а в його application database. У ній зберігаються користувачі, колекції, запитання, моделі, налаштування підключень і права. Тому потрібно регулярно зберігати базу metabase, а не лише Docker-образ.

  • Дамп PostgreSQL application database Metabase.
  • Дані робочої аналітичної бази, якщо вона розташована на цьому ж VPS.
  • Файл .env у зашифрованому або захищеному сховищі.
  • docker-compose.yml, Caddyfile та SQL-скрипти ініціалізації.
  • Каталоги Caddy /data і /config, якщо потрібно зберегти стан сертифікатів.
  • Документація щодо DNS, версій образів і процедури відновлення.

Встановлення restic

Резервна копія на той самий диск захищає лише від помилки користувача або пошкодження бази. У разі втрати VPS вона зникне разом із вихідними даними. Для production використовуйте зовнішній S3-сумісний бакет, окремий сервер або об'єктне сховище в іншому регіоні.

# Встановлюємо restic і PostgreSQL-клієнт
sudo apt install -y restic postgresql-client

# Створюємо закритий файл із налаштуваннями віддаленого сховища
sudo nano /root/.restic-env
sudo chmod 600 /root/.restic-env

Приклад файлу:

export AWS_ACCESS_KEY_ID="CHANGE_ME"
export AWS_SECRET_ACCESS_KEY="CHANGE_ME"
export RESTIC_REPOSITORY="s3:https://s3.example.net/metabase-backups"
export RESTIC_PASSWORD="CHANGE_ME_RESTIC_PASSWORD"

Скрипт резервного копіювання

Скрипт спочатку створює дампи обох баз, потім додає конфігурацію та надсилає все до restic. Дампи тимчасово зберігаються в каталозі з правами root і видаляються після завершення. Зовнішній S3-бакет має підтримувати версіонування та обмежений доступ за ключем.

# Створюємо каталог для скрипта
sudo mkdir -p /usr/local/sbin /var/backups/metabase
sudo nano /usr/local/sbin/metabase-backup.sh
#!/usr/bin/env bash
set -Eeuo pipefail

PROJECT="/opt/metabase"
BACKUP_DIR="/var/backups/metabase"
STAMP="$(date -u +%Y-%m-%dT%H-%M-%SZ)"

source /root/.restic-env
set -a
source "${PROJECT}/.env"
set +a

mkdir -p "${BACKUP_DIR}/${STAMP}"

docker compose -f "${PROJECT}/docker-compose.yml" exec -T metabase-db \
  pg_dump -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" \
  | gzip > "${BACKUP_DIR}/${STAMP}/metabase.sql.gz"

docker compose -f "${PROJECT}/docker-compose.yml" exec -T analytics-db \
  pg_dump -U postgres -d "${ANALYTICS_DB}" \
  | gzip > "${BACKUP_DIR}/${STAMP}/analytics.sql.gz"

cp "${PROJECT}/.env" "${BACKUP_DIR}/${STAMP}/.env"
cp "${PROJECT}/docker-compose.yml" "${BACKUP_DIR}/${STAMP}/docker-compose.yml"
cp "${PROJECT}/caddy/Caddyfile" "${BACKUP_DIR}/${STAMP}/Caddyfile"

restic backup "${BACKUP_DIR}/${STAMP}" --tag metabase
restic forget --tag metabase --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

rm -rf "${BACKUP_DIR:?}/${STAMP}"
# Робимо скрипт виконуваним
sudo chmod 700 /usr/local/sbin/metabase-backup.sh

# Ініціалізуємо restic-репозиторій один раз
sudo bash -c 'source /root/.restic-env && restic init'

# Запускаємо тестову резервну копію вручну
sudo /usr/local/sbin/metabase-backup.sh

# Перевіряємо список збережених знімків
sudo bash -c 'source /root/.restic-env && restic snapshots'

Зверніть увагу, що передавання паролів через змінні середовища зручне для прикладу, але доступ до них може отримати процес із достатніми правами. Обмежте доступ до root-файлів і використовуйте IAM-ключ із правами лише на конкретний бакет.

Планувальник cron

Запускайте резервне копіювання вночі або в період низького навантаження. Для невеликих баз щоденної копії достатньо, але критичні дані зазвичай потребують коротшого інтервалу та окремого WAL-архівування PostgreSQL.

# Відкриваємо системний crontab root
sudo crontab -e

Додайте рядок:

30 2    /usr/local/sbin/metabase-backup.sh >> /var/log/metabase-backup.log 2>&1

Перевірка відновлення

Резервну копію, яку жодного разу не відновлювали, не можна вважати перевіреною. Щонайменше раз на місяць запускайте тимчасовий PostgreSQL, розпаковуйте дамп і перевіряйте наявність користувачів, колекцій і запитань. Для робочих даних окремо перевірте кілька ключових таблиць і контрольні суми або кількість рядків.

# Завантажуємо останній знімок у тимчасовий каталог
sudo mkdir -p /tmp/metabase-restore
sudo bash -c 'source /root/.restic-env && restic restore latest --tag metabase --target /tmp/metabase-restore'

# Шукаємо відновлені дампи
find /tmp/metabase-restore -type f -name '*.sql.gz' -ls

Оновлення Metabase

Не оновлюйте production автоматично щоразу, коли з'являється новий Docker-тег. Спочатку створіть резервну копію, прочитайте список змін і перевірте нову версію на копії application database. Для невеликої команди достатньо maintenance window тривалістю 10–20 хвилин.

# Зберігаємо поточні версії образів і стан контейнерів
cd /opt/metabase
docker compose images
docker compose ps

# Створюємо резервну копію перед оновленням
sudo /usr/local/sbin/metabase-backup.sh

# Завантажуємо нові образи вибраних версій
docker compose pull

# Перезапускаємо сервіси з оновленими образами
docker compose up -d

# Стежимо за міграціями Metabase
docker compose logs -f --tail=200 metabase

Якщо нова версія потребує міграції бази, відкат контейнера без відновлення бази може бути небезпечним. Для важливих інсталяцій спочатку створіть snapshot VPS або відновіть дамп в окремий тестовий екземпляр.

Спостереження за ресурсами

Стежте за вільним місцем, пам'яттю та часом виконання запитів. PostgreSQL і Docker-логи можуть поступово заповнити диск. Додайте ротацію логів Docker, особливо якщо Caddy або Metabase працюють із докладним рівнем журналювання.

# Перевіряємо споживання ресурсів контейнерами
docker stats --no-stream

# Перевіряємо розміри Docker-даних
docker system df

# Шукаємо каталоги, що займають місце
sudo du -xh /var/lib/docker /opt/metabase | sort -h | tail -n 20

9. Усунення несправностей і FAQ

Чому відкривається помилка 502 Bad Gateway?

Спочатку перевірте стан контейнера Metabase командою docker compose ps та останні повідомлення docker compose logs --tail=200 metabase. Якщо контейнер перезапускається, імовірна помилка підключення до application database, неправильний пароль або нестача пам'яті. Якщо Metabase працює, перевірте, що Caddy використовує ім'я metabase, а не localhost:3000. Після зміни Caddyfile перезапустіть Caddy.

Чому Caddy не отримує сертифікат Let's Encrypt?

Переконайтеся, що DNS-запис домену вже вказує на публічний IP VPS. Порти 80 і 443 мають бути дозволені в UFW та у зовнішньому firewall провайдера. Якщо існує неправильний запис AAAA, Let's Encrypt може звертатися через IPv6 до іншого сервера. Перевірте docker compose logs caddy, потім виконайте curl -I http://bi.example.com із зовнішнього комп'ютера. Не використовуйте закритий для інтернету домен без DNS-челенджу.

Metabase не підключається до PostgreSQL з помилкою “connection refused”

У Docker Compose у полі Host потрібно вказати ім'я сервісу analytics-db і порт 5432. Не вказуйте IP контейнера: він може змінитися після перезапуску. Перевірте docker compose ps, healthcheck бази та логи docker compose logs analytics-db. Якщо базу було ініціалізовано раніше, зміна змінних POSTGRES_PASSWORD не змінює вже створений пароль автоматично. У такому разі пароль потрібно змінити SQL-командою всередині PostgreSQL.

Чому користувач analytics_reader отримує permission denied?

Перевірте права на схему, наявні таблиці та default privileges. SQL із каталогу analytics-init виконується лише під час першого створення порожнього тому PostgreSQL. Якщо том уже існує, новий SQL-файл автоматично не запускається. Надайте права вручну: GRANT USAGE ON SCHEMA public TO analytics_reader і GRANT SELECT ON ALL TABLES IN SCHEMA public TO analytics_reader. Для нових таблиць налаштуйте ALTER DEFAULT PRIVILEGES від імені власника таблиць.

Дашборди завантажуються повільно. Що перевірити?

Почніть із SQL-запитів, а не зі збільшення VPS. Виконайте проблемний запит у PostgreSQL з EXPLAIN (ANALYZE, BUFFERS) і перевірте, чи використовуються індекси за датами, зовнішніми ключами та полями фільтрації. Зменште діапазон даних, об'єднайте повторювані запити та створіть агреговані представлення. У Metabase перевірте, чи не запускається одночасно надто багато карток. Після цього аналізуйте CPU, RAM, IOPS і мережеву затримку.

Яка конфігурація VPS мінімально підійде?

Для тестового Metabase достатньо 1–2 vCPU, 2 ГБ RAM і 30 ГБ SSD, але такий сервер майже не залишає запасу для PostgreSQL, Docker і фонових завдань. Практичний мінімум для невеликої команди — 2 vCPU, 4 ГБ RAM і 40–60 ГБ SSD. Якщо на тому ж VPS розташована аналітична база, ETL або регулярні важкі запити, обирайте 4 vCPU та 8 ГБ RAM. Використовуйте SSD, а не повільний HDD.

Що обрати — VPS чи dedicated для цього завдання?

VPS підходить для більшості невеликих і середніх інсталяцій Metabase: він дешевший, швидко масштабується та не потребує керування фізичним сервером. Dedicated потрібен за великих обсягів PostgreSQL, високих вимог до IOPS, постійного ETL-навантаження або потреби гарантувати відсутність сусідів на фізичному хості. Перед переходом виміряйте реальне навантаження. Часто індекси та агреговані таблиці дають більший ефект, ніж додаткові ядра.

Чи можна відкрити Metabase напряму через порт 3000?

Технічно можна, але для production це погана практика. Користувачі підключатимуться без належного TLS, а застосунок буде напряму доступний з інтернету. Використовуйте Caddy або інший reverse proxy на портах 80/443, а порт 3000 залишайте лише всередині Docker-мережі. Не додавайте ports: "3000:3000" у Compose. Якщо такий порт уже опубліковано, видаліть його та перезапустіть контейнери.

Що робити, якщо закінчилася RAM і контейнер Metabase перезапускається?

Перевірте docker stats, free -h і системний журнал на наявність OOM killer. Спочатку зменште паралельність важких запитів, оптимізуйте PostgreSQL і зупиніть непотрібні сервіси. Тимчасовий swap на SSD може запобігти аварійному завершенню, але не замінює фізичну пам'ять. Для постійного аналітичного навантаження збільште VPS щонайменше до 8 ГБ RAM і налаштуйте моніторинг, щоб бачити проблему до відмови.

10. Висновки та наступні кроки

У результаті ми отримали self-hosted Metabase на VPS із PostgreSQL 17, Docker Compose, HTTPS через Caddy та окремим користувачем бази лише для читання. Конфігурація підходить для невеликої команди й дає змогу створювати дашборди за замовленнями, виручкою та іншими даними без ручного встановлення Java і вебсервера.

  • Налаштуйте моніторинг CPU, RAM, диска, часу відповіді PostgreSQL і строку дії сертифіката.
  • Для зростання навантаження винесіть аналітичну базу на окремий сервер, додайте read replica або агреговані таблиці.
  • Регулярно перевіряйте відновлення резервних копій і тестуйте оновлення Metabase на копії application database.

Чи був цей гайд корисним?

Ваш відгук допомагає нам покращувати гайди.

Share this post:

Надішліть гайд тому, кому він може стати в пригоді.

Telegram VKVK WhatsApp Facebook LinkedIn XX

metabase на vps: дашборди по postgresql за вечір
support_agent
Valebyte Support
Usually replies within minutes
Hi there!
Send us a message and we'll reply as soon as possible.