bolt Valebyte VPS desde $4/mes — NVMe, despliegue en 60s.

Obtener VPS arrow_forward
eco Principiante Tutorial/Cómo hacer

Metabase en un VPS: dashboards de PostgreSQL en una tarde

calendar_month Sep 25, 2026 schedule 20 min de lectura visibility 45 vistas
Metabase на VPS: дашборды по PostgreSQL за вечер
info

¿Necesitas un servidor para esta guía? Ofrecemos servidores dedicados y VPS en más de 50 países con configuración instantánea.

¿Necesitas un VPS para esta guía?

Explore otras opciones de servidores dedicados en

Metabase en un VPS: paneles de PostgreSQL en una tarde

TL;DR

Metabase se puede desplegar en un VPS en una tarde mediante Docker Compose, conectarlo a PostgreSQL y abrir los paneles mediante HTTPS. En esta guía instalaremos Metabase 0.57.x, PostgreSQL 17 y Caddy, configuraremos una base de datos de aplicación independiente, un usuario de solo lectura, copias de seguridad y una protección básica del servidor.

  • Para un equipo pequeño basta con un VPS con 2 vCPU, 4 GB de RAM y un SSD de al menos 40 GB.
  • Metabase se ejecutará en un contenedor Docker, mientras que PostgreSQL almacenará la configuración, los usuarios y las preguntas.
  • Metabase tendrá acceso a la fuente de datos analíticos mediante la red interna de Docker.
  • Caddy obtendrá automáticamente un certificado TLS de Let's Encrypt para el dominio.
  • El acceso a los datos de producción se realizará mediante un usuario independiente de PostgreSQL con permisos de solo lectura.
  • La copia de seguridad diaria de PostgreSQL y de la configuración se enviará a un almacenamiento S3 externo.

1. TL;DR

En esta sección ya se presenta el plan resumido: alojamos Metabase en un VPS, lo ejecutamos mediante Docker Compose, conectamos PostgreSQL y publicamos la interfaz mediante HTTPS. Los comandos detallados y los archivos de configuración se encuentran a continuación.

2. Contenido

El artículo está dirigido al propietario de un VPS que quiere obtener por su cuenta un sistema funcional de business intelligence sin depender obligatoriamente de un servicio en la nube. El ejemplo está orientado a Ubuntu Server 24.04 LTS, el dominio bi.example.com y un servidor limpio con una dirección IPv4 pública.

Los comandos presuponen una conexión al servidor con un usuario que tenga permisos de sudo. Si ya dispone de una base de datos PostgreSQL operativa, puede omitir la parte de creación de la base de demostración, pero aun así conviene comprobar la configuración de red y de usuarios.

3. Qué configuramos y por qué

Qué es Metabase

Metabase es una aplicación web para analizar datos y crear paneles. Se conecta a PostgreSQL, MySQL, ClickHouse y otras fuentes, y permite crear tablas, gráficos, tarjetas KPI y filtros sin escribir SQL para cada consulta.

Para un equipo, Metabase suele convertirse en un portal interno de analítica: el responsable consulta los ingresos, el especialista de marketing el embudo, el servicio de soporte el número de solicitudes y el desarrollador las métricas técnicas. A diferencia de los sistemas BI basados únicamente en SQL, Metabase ofrece un constructor visual de preguntas y, al mismo tiempo, conserva un editor SQL completo.

Qué funcionará después de la configuración

El esquema final constará de varios contenedores. Metabase se encargará de la interfaz web y los paneles, PostgreSQL de la base de datos interna de Metabase y un contenedor independiente de PostgreSQL se utilizará como fuente de datos de demostración. Caddy recibirá las solicitudes HTTPS y las reenviará a Metabase.

Componente Función Puerto
Metabase 0.57.x Interfaz web, consultas y paneles 3000 dentro de la red
PostgreSQL 17 Metadatos de Metabase 5432 dentro de la red
PostgreSQL 17 Datos analíticos de demostración 5432 dentro de la red
Caddy 2 Reverse proxy y HTTPS 80 y 443

Metabase self-hosted o en la nube

La opción en la nube no requiere mantener el sistema operativo, Docker, TLS ni las copias de seguridad. Es adecuada si lo más importante es comenzar rápidamente a analizar los datos, en lugar de controlar la infraestructura. Sin embargo, el coste aumenta junto con el número de usuarios, el volumen de funciones y los requisitos de acceso corporativo.

El Metabase self-hosted en un VPS ofrece control sobre la ubicación de los datos, la red, las versiones y los costes. Esta opción es útil si la empresa cuenta con un especialista capaz de actualizar los contenedores, comprobar las copias de seguridad y responder ante incidentes. Es importante entender que un VPS no convierte el sistema en un servicio completamente gestionado. La responsabilidad de la seguridad y la recuperación sigue recayendo en el propietario.

Cómo trabaja Metabase con PostgreSQL

Metabase tiene su propia application database. En ella se almacenan los usuarios, los permisos de acceso, la configuración de las conexiones, las preguntas guardadas y la estructura de las colecciones. En producción no se debe utilizar la base de datos H2 integrada: está destinada principalmente a pruebas sencillas y migraciones.

La base de datos de trabajo se conecta por separado. Metabase no copia automáticamente todas las tablas al VPS, sino que ejecuta las consultas en la fuente cuando se abre una pregunta o un panel. Por ello, el ancho de banda y la latencia de red entre Metabase y PostgreSQL influyen en la velocidad de las visualizaciones.

4. Qué configuración de VPS se necesita para esta tarea

Los recursos dependen menos de la propia interfaz de Metabase que del número de consultas simultáneas y de la velocidad de PostgreSQL. Metabase puede funcionar en un servidor pequeño si los paneles los abren pocas personas y las consultas utilizan índices. Las tablas grandes, decenas de usuarios y la actualización frecuente de gráficos requerirán más CPU y memoria.

Escenario CPU RAM Disco Red
Pruebas y proyecto personal 1–2 vCPU 2–4 GB SSD de 30–40 GB 100 Mbit/s
Equipo pequeño de hasta 15 usuarios 2–4 vCPU 4–8 GB SSD de 60–100 GB 100–500 Mbit/s
Varias decenas de usuarios 4–8 vCPU 8–16 GB NVMe de 100–250 GB 500 Mbit/s o más

Configuración práctica

Para el escenario descrito en el artículo, un punto de partida razonable es 2 vCPU, 4 GB de RAM, 60 GB de SSD o NVMe, una IPv4 pública y un canal de al menos 100 Mbit/s. En el disco se almacenarán las imágenes de Docker, PostgreSQL, los registros y los archivos temporales. Si la fuente de datos está alojada en otro servidor, la mayor parte del almacenamiento no será necesaria para las tablas, sino para el sistema operativo, las copias de seguridad y la caché.

Puede contratar un VPS adecuado con estas características o elegir un servidor equivalente de otro proveedor. Al elegirlo, compruebe que se permiten conexiones entrantes en los puertos 80 y 443, que es posible instalar Docker y que se pueden crear snapshots del disco o conectar almacenamiento adicional.

Cuándo se necesita un dedicated

Un servidor dedicado está justificado si PostgreSQL ya procesa consultas analíticas pesadas, los datos ocupan cientos de gigabytes, se necesitan IOPS garantizados o se planea ejecutar varios servicios en un mismo servidor. Un dedicated también resulta útil para ETL, almacenamiento local de copias de seguridad y grandes materialized views.

Para un Metabase normal, cambiar a un dedicated no resuelve por sí mismo el problema de los gráficos lentos. Primero hay que comprobar los planes de ejecución de SQL, los índices, las agregaciones y el almacenamiento en caché. Si una consulta tarda dos minutos debido a un escaneo completo de la tabla, añadir núcleos no siempre producirá una mejora apreciable.

Elección de la ubicación

La ubicación influye en la latencia hasta PostgreSQL y los usuarios. Si la base de datos y Metabase están en regiones diferentes, cada consulta interactiva tendrá una latencia de red adicional. Para tablas pequeñas esto resulta casi imperceptible, pero para muchas consultas secuenciales ya es significativo.

Ubique Metabase en la misma región que la base de datos principal o la aplicación que proporciona los datos. Para usuarios de varios países, elija una región con una latencia aceptable o utilice una CDN únicamente para el contenido estático, sin intentar almacenar en caché las respuestas personalizadas de Metabase.

5. Preparación del servidor

Creación del usuario y actualización de Ubuntu

A continuación se presupone Ubuntu Server 24.04 LTS. Conéctese por SSH con el usuario creado por el proveedor y sustituya deploy por el nombre necesario. No cierre la sesión SSH actual hasta comprobar el acceso con el nuevo usuario.

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

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

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

Desde el ordenador local, copie la clave SSH pública. El comando no transfiere la clave privada al servidor, sino que añade únicamente la clave pública al archivo de autorización.

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

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

Restricción de SSH

Después de comprobar correctamente el acceso mediante clave, desactive la autenticación mediante contraseña y el acceso de root. Antes de modificar el archivo, asegúrese de que la clave funciona realmente en otra ventana del terminal.

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

En el comando anterior, el espacio en el nombre del directorio no es válido. Utilice la variante correcta:

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

Inserte los siguientes parámetros:

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

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

Firewall y fail2ban

Abra SSH, HTTP y HTTPS. Si SSH funciona en un puerto no estándar, indique ese puerto en lugar del 22. UFW no debería bloquear una sesión SSH ya establecida, pero aun así conviene respetar el orden de las acciones: primero permitir SSH y después activar el 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

Cree una configuración local de fail2ban. Bloqueará las direcciones que fallen repetidamente al iniciar sesión mediante 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

Sincronización de la hora y parámetros básicos

La hora correcta es importante para TLS, los registros y las tareas cron. En Ubuntu normalmente ya se utiliza systemd-timesyncd, por lo que basta con comprobar su estado.

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

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

6. Instalación del software: paso a paso

Instalación de Docker Engine y Compose

Para production, utilice el repositorio oficial de Docker, no el paquete obsoleto del repositorio estándar de Ubuntu. En el momento de preparar esta guía, la pila objetivo es Docker Engine 27.x o una versión compatible más reciente y 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"

Cierre la sesión SSH y vuelva a conectarse para actualizar el grupo del usuario.

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

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

Creación de la estructura del proyecto

Todos los archivos del proyecto se ubicarán en /opt/metabase. Los secretos se almacenarán en el archivo .env, que no debe añadirse a Git ni publicarse en el directorio web.

# Создаём каталоги проекта и хранения 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

Preparación de variables de entorno

Genere contraseñas aleatorias. La contraseña de la aplicación Metabase no debe coincidir con la contraseña del usuario de la base de datos analítica. Para production, guarde una copia de los secretos en un gestor de contraseñas; de lo contrario, la recuperación tras perder el VPS será complicada.

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

Cree /opt/metabase/.env con el siguiente contenido. Sustituya todos los valores después del signo igual.

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

La variable MB_ENCRYPTION_SECRET_KEY se utiliza para cifrar valores confidenciales en la configuración de Metabase. No la cambie después del primer inicio sin comprender las consecuencias: las configuraciones de conexión guardadas podrían quedar inaccesibles.

Inicialización de la base de datos de demostración

Si ya dispone de PostgreSQL, este paso puede adaptarse al esquema existente. Para una comprobación independiente, cree un pequeño conjunto de tablas con pedidos, clientes y productos.

# Создаём 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

En el SQL de demostración, la contraseña debe coincidir con ANALYTICS_PASSWORD en el archivo .env. Para un proyecto real, es mejor crear el usuario con un comando independiente o usar plantillas para el SQL, de modo que el secreto no se duplique en varios archivos.

Docker Compose

Cree el archivo docker-compose.yml. Las versiones de las imágenes se fijan mediante etiquetas para que una actualización inesperada no cambie el comportamiento del sistema. Antes de actualizar, revise primero las release notes de Metabase y haga una copia de seguridad.

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

En Metabase 0.57.x, la imagen actual puede tener una etiqueta de patch más reciente. Antes de iniciar, compruebe la disponibilidad de la etiqueta elegida en el registro oficial. Para production es importante fijar una versión específica y no utilizar latest.

7. Configuración, HTTPS y comprobación

Configuración de Caddy

Antes de iniciar Caddy, cree un registro DNS A para bi.example.com que apunte a la dirección IP pública del VPS. Si utiliza IPv6, asegúrese de que el registro AAAA también apunte a este servidor y de que el firewall permita tráfico 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

Si la utilidad dig no está instalada, añada el paquete dnsutils. El resultado debe contener la dirección de su VPS.

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

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

Primer inicio

Antes de iniciar, compruebe el archivo Compose y las variables de entorno. Docker Compose sustituirá los valores de .env. Los errores en los nombres de las variables suelen provocar que el contenedor se inicie con una contraseña vacía o que no pueda conectarse a PostgreSQL.

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

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

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

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

El primer inicio de Metabase puede tardar varios minutos: el contenedor aplica migraciones a la application database. Abra https://bi.example.com en el navegador. Caddy solicitará un certificado de Let's Encrypt automáticamente si el DNS ya se ha actualizado y los puertos 80/443 son accesibles desde el exterior.

Configuración inicial de Metabase

En el asistente de primer inicio, cree una cuenta administrativa con una contraseña única y larga. No utilice la contraseña del VPS ni de PostgreSQL. Indique la zona horaria de trabajo, la moneda y el nombre de la organización; después, agregue una conexión a la base de datos analítica.

Campo de Metabase Valor
Database type PostgreSQL
Host analytics-db
Port 5432
Database name analytics
Username analytics_reader
Password Valor de ANALYTICS_PASSWORD

El nombre analytics-db es el nombre DNS del servicio dentro de la red Docker. No indique localhost: dentro del contenedor de Metabase, se refiere al propio contenedor de Metabase, no a PostgreSQL.

Comprobación de la conexión con PostgreSQL

Puede comprobar la red y los permisos desde el contenedor de Metabase o un cliente temporal de PostgreSQL. Primero, asegúrese de que la base de datos responde.

# Проверяем состояние контейнеров
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;"

El nombre de la red puede variar si el directorio del proyecto tiene otro nombre. Averigüe el nombre real con el comando docker network ls. Para comprobar la aplicación, utilice una solicitud HTTP desde el propio servidor.

# Проверяем локальный 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

Creación del primer dashboard

En la sección New, cree una pregunta basada en la tabla orders. Para una tarjeta simple de ingresos, seleccione la suma del campo amount con el filtro status = paid. Para un gráfico de ventas, agrupe la suma por día del campo created_at. A continuación, guarde las preguntas en una colección y combínelas en un nuevo dashboard.

Para una pregunta SQL puede utilizar la siguiente consulta. Es mejor añadir el parámetro de fecha mediante la interfaz de Metabase, para que los usuarios puedan cambiar el período sin editar el 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;

Permisos de acceso

El administrador debe crear grupos de usuarios y darles acceso solo a las colecciones necesarias. No convierta a todos los empleados en administradores: los permisos de administrador permiten modificar conexiones, usuarios y la configuración de todo el sistema.

Si se utilizan datos confidenciales, cree vistas independientes de PostgreSQL en lugar de proporcionar acceso a las tablas de origen. Por ejemplo, una vista puede ocultar el email, el teléfono y los identificadores internos, dejando solo indicadores agregados. Asimismo, desactive o limite las consultas SQL personalizadas si la audiencia debe trabajar únicamente con modelos preparados.

8. Copias de seguridad y mantenimiento

Qué es necesario conservar

El valor principal de Metabase no se encuentra en el contenedor, sino en su application database. Contiene usuarios, colecciones, preguntas, modelos, configuraciones de conexión y permisos. Por eso es necesario guardar periódicamente la base de datos metabase, y no solo la imagen de Docker.

  • Volcado de la PostgreSQL application database de Metabase.
  • Datos de la base de datos analítica de trabajo, si se encuentra en el mismo VPS.
  • Archivo .env en un almacenamiento cifrado o protegido.
  • docker-compose.yml, Caddyfile y scripts SQL de inicialización.
  • Directorios de Caddy /data y /config, si es necesario conservar el estado de los certificados.
  • Documentación sobre DNS, versiones de las imágenes y procedimiento de recuperación.

Instalación de restic

Una copia de seguridad en el mismo disco protege únicamente frente a errores del usuario o daños en la base de datos. Si se pierde el VPS, desaparecerá junto con los datos originales. Para production utilice un bucket externo compatible con S3, un servidor independiente o un almacenamiento de objetos en otra región.

# Instalamos restic y el cliente de PostgreSQL
sudo apt install -y restic postgresql-client

# Creamos un archivo protegido con la configuración del almacenamiento remoto
sudo nano /root/.restic-env
sudo chmod 600 /root/.restic-env

Ejemplo de archivo:

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"

Script de copia de seguridad

El script primero crea los volcados de ambas bases de datos, después añade la configuración y envía todo a restic. Los volcados se almacenan temporalmente en un directorio con permisos de root y se eliminan al finalizar. El bucket S3 externo debe admitir el versionado y el acceso restringido mediante clave.

# Creamos el directorio para el script
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}"
# Hacemos que el script sea ejecutable
sudo chmod 700 /usr/local/sbin/metabase-backup.sh

# Inicializamos el repositorio de restic una sola vez
sudo bash -c 'source /root/.restic-env && restic init'

# Ejecutamos manualmente una copia de seguridad de prueba
sudo /usr/local/sbin/metabase-backup.sh

# Comprobamos la lista de instantáneas guardadas
sudo bash -c 'source /root/.restic-env && restic snapshots'

Tenga en cuenta que pasar contraseñas mediante variables de entorno resulta práctico para el ejemplo, pero un proceso con permisos suficientes podría acceder a ellas. Restrinja el acceso a los archivos de root y utilice una clave IAM con permisos únicamente para el bucket específico.

Planificador cron

Ejecute la copia de seguridad por la noche o durante un periodo de baja carga. Para bases de datos pequeñas basta con una copia diaria, pero los datos críticos suelen requerir un intervalo más corto y un archivado WAL independiente de PostgreSQL.

# Abrimos el crontab del sistema para root
sudo crontab -e

Añada la línea:

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

Comprobación de la recuperación

Una copia de seguridad que nunca se ha restaurado no puede considerarse verificada. Como mínimo una vez al mes, levante un PostgreSQL temporal, descomprima el volcado y compruebe la presencia de usuarios, colecciones y preguntas. Para los datos de trabajo, compruebe por separado varias tablas clave y las sumas de comprobación o el número de filas.

# Descargamos la última instantánea en un directorio temporal
sudo mkdir -p /tmp/metabase-restore
sudo bash -c 'source /root/.restic-env && restic restore latest --tag metabase --target /tmp/metabase-restore'

# Buscamos los volcados restaurados
find /tmp/metabase-restore -type f -name '*.sql.gz' -ls

Actualización de Metabase

No actualice production automáticamente cada vez que aparezca un nuevo Docker-tag. Primero haga una copia de seguridad, lea la lista de cambios y pruebe la nueva versión en una copia de la application database. Para un equipo pequeño basta con un maintenance window de 10–20 minutos.

# Guardamos las versiones actuales de las imágenes y el estado de los contenedores
cd /opt/metabase
docker compose images
docker compose ps

# Creamos una copia de seguridad antes de la actualización
sudo /usr/local/sbin/metabase-backup.sh

# Descargamos las nuevas imágenes de las versiones seleccionadas
docker compose pull

# Reiniciamos los servicios con las imágenes actualizadas
docker compose up -d

# Supervisamos las migraciones de Metabase
docker compose logs -f --tail=200 metabase

Si la nueva versión requiere una migración de la base de datos, revertir el contenedor sin restaurar la base puede ser inseguro. Para instalaciones importantes, primero cree un snapshot del VPS o restaure el volcado en una instancia de prueba independiente.

Supervisión de recursos

Controle el espacio libre, la memoria y el tiempo de ejecución de las consultas. PostgreSQL y los logs de Docker pueden llenar gradualmente el disco. Añada la rotación de logs de Docker, especialmente si Caddy o Metabase funcionan con un nivel detallado de registro.

# Comprobamos el consumo de recursos de los contenedores
docker stats --no-stream

# Comprobamos el tamaño de los datos de Docker
docker system df

# Buscamos los directorios que ocupan espacio
sudo du -xh /var/lib/docker /opt/metabase | sort -h | tail -n 20

9. Troubleshooting y FAQ

¿Por qué aparece el error 502 Bad Gateway?

Primero compruebe el estado del contenedor de Metabase con el comando docker compose ps y los últimos mensajes mediante docker compose logs --tail=200 metabase. Si el contenedor se reinicia, probablemente haya un error de conexión con la application database, una contraseña incorrecta o falta de memoria. Si Metabase funciona, compruebe que Caddy utiliza el nombre metabase y no localhost:3000. Después de modificar el Caddyfile, reinicie Caddy.

¿Por qué Caddy no obtiene el certificado de Let's Encrypt?

Asegúrese de que el registro DNS del dominio ya apunta a la IP pública del VPS. Los puertos 80 y 443 deben estar permitidos en UFW y en el firewall externo del proveedor. Si existe un registro AAAA incorrecto, Let's Encrypt puede intentar conectarse mediante IPv6 a otro servidor. Compruebe docker compose logs caddy y después ejecute curl -I http://bi.example.com desde un equipo externo. No utilice un dominio cerrado a Internet sin DNS-challenge.

Metabase no se conecta a PostgreSQL y muestra el error “connection refused”

En Docker Compose, en el campo Host debe indicar el nombre del servicio analytics-db y el puerto 5432. No indique la IP del contenedor: puede cambiar después de un reinicio. Compruebe docker compose ps, el healthcheck de la base y los logs mediante docker compose logs analytics-db. Si la base se inicializó anteriormente, cambiar las variables POSTGRES_PASSWORD no modifica automáticamente la contraseña ya creada. En ese caso, cambie la contraseña mediante un comando SQL dentro de PostgreSQL.

¿Por qué el usuario analytics_reader recibe permission denied?

Compruebe los permisos del esquema, las tablas existentes y los default privileges. El SQL del directorio analytics-init solo se ejecuta al crear por primera vez un volumen PostgreSQL vacío. Si el volumen ya existe, el nuevo archivo SQL no se ejecuta automáticamente. Conceda los permisos manualmente: GRANT USAGE ON SCHEMA public TO analytics_reader y GRANT SELECT ON ALL TABLES IN SCHEMA public TO analytics_reader. Para las tablas nuevas, configure ALTER DEFAULT PRIVILEGES en nombre del propietario de las tablas.

Los dashboards se cargan lentamente. ¿Qué debo comprobar?

Empiece por las consultas SQL, no por aumentar el VPS. Ejecute la consulta problemática en PostgreSQL con EXPLAIN (ANALYZE, BUFFERS) y compruebe si se utilizan índices para las fechas, las claves externas y los campos de filtrado. Reduzca el intervalo de datos, combine las consultas repetidas y cree vistas agregadas. En Metabase, compruebe que no se ejecuten demasiadas tarjetas simultáneamente. Después analice la CPU, la RAM, los IOPS y la latencia de red.

¿Qué configuración mínima de VPS es adecuada?

Para un Metabase de prueba son suficientes 1–2 vCPU, 2 GB de RAM y 30 GB de SSD, pero ese servidor prácticamente no deja margen para PostgreSQL, Docker y las tareas en segundo plano. El mínimo práctico para un equipo pequeño es de 2 vCPU, 4 GB de RAM y 40–60 GB de SSD. Si la base de datos analítica, ETL o consultas pesadas periódicas también se encuentran en el mismo VPS, elija 4 vCPU y 8 GB de RAM. Utilice SSD, no un HDD lento.

¿Qué elegir: VPS o dedicated para esta tarea?

Un VPS es adecuado para la mayoría de las instalaciones pequeñas y medianas de Metabase: es más barato, escala rápidamente y no requiere gestionar un servidor físico. Un dedicated es necesario con grandes volúmenes de PostgreSQL, altos requisitos de IOPS, carga ETL constante o la necesidad de garantizar la ausencia de vecinos en el host físico. Antes de migrar, mida la carga real. A menudo los índices y las tablas agregadas ofrecen un efecto mayor que añadir núcleos.

¿Se puede abrir Metabase directamente en el puerto 3000?

Técnicamente sí, pero para production es una mala práctica. Los usuarios se conectarían sin un TLS adecuado y la aplicación quedaría directamente accesible desde Internet. Utilice Caddy u otro reverse proxy en los puertos 80/443, y deje el puerto 3000 únicamente dentro de la red de Docker. No añada ports: "3000:3000" en Compose. Si dicho puerto ya está publicado, elimínelo y reinicie los contenedores.

¿Qué hacer si se agota la RAM y el contenedor de Metabase se reinicia?

Compruebe docker stats, free -h y el registro del sistema para detectar la presencia de OOM killer. Primero reduzca el paralelismo de las consultas pesadas, optimice PostgreSQL y detenga los servicios innecesarios. Un swap temporal en SSD puede evitar la finalización inesperada, pero no sustituye a la memoria física. Para una carga analítica permanente, aumente el VPS como mínimo a 8 GB de RAM y configure la supervisión para detectar el problema antes del fallo.

10. Conclusiones y próximos pasos

Como resultado, hemos obtenido un Metabase self-hosted en un VPS con PostgreSQL 17, Docker Compose, HTTPS mediante Caddy y un usuario independiente de la base de datos con permisos de solo lectura. La configuración es adecuada para un equipo pequeño y permite crear dashboards sobre pedidos, ingresos y otros datos sin instalar manualmente Java ni un servidor web.

  • Configure la supervisión de la CPU, la RAM, el disco, el tiempo de respuesta de PostgreSQL y la fecha de caducidad del certificado.
  • Si aumenta la carga, traslade la base de datos analítica a un servidor independiente, añada una read replica o tablas agregadas.
  • Compruebe periódicamente la restauración de las copias de seguridad y pruebe las actualizaciones de Metabase en una copia de la application database.

¿Te fue útil esta guía?

Tus comentarios nos ayudan a mejorar nuestras guías.

Compartir esta publicación:

Envía esta guía a alguien a quien pueda resultarle útil.

Telegram VKVK WhatsApp Facebook LinkedIn XX

Metabase en VPS: dashboards de PostgreSQL en una tarde
support_agent
Valebyte Support
Usually replies within minutes
Hi there!
Send us a message and we'll reply as soon as possible.