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

Получить VPS arrow_forward
eco Начальный Туториал

Metabase на VPS: дашборды по PostgreSQL за вечер

calendar_month Sep 25, 2026 schedule 20 мин. чтения visibility 54 просмотров
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. Что мы настраиваем и зачем

Схема: 3. Что мы настраиваем и зачем
Схема: 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-конфиг нужен под эту задачу

Схема: 4. Какой VPS-конфиг нужен под эту задачу
Схема: 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. Подготовка сервера

Схема: 5. Подготовка сервера
Схема: 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. Установка ПО — пошагово

Схема: 6. Установка ПО — пошагово
Схема: 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 и проверка

Схема: 7. Конфигурация, HTTPS и проверка
Схема: 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. Бэкапы и обслуживание

Схема: 8. Бэкапы и обслуживание
Схема: 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. Troubleshooting и 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. Выводы и следующие шаги

Схема: 10. Выводы и следующие шаги
Схема: 10. Выводы и следующие шаги

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

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

Был ли этот гайд полезен?

Ваш отзыв помогает нам улучшать гайды.

Поделиться записью:

Отправьте гайд тому, кому он может пригодиться.

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.