Chartbrew sits in a practical place between spreadsheet exports, hand-built admin dashboards, and heavier BI platforms.

You connect it to APIs, SQL databases, NoSQL databases, and SaaS systems, build reusable datasets, then turn those results into dashboards, charts, public reports, and embeds.

What is Chartbrew?

Chartbrew is a self-hostable reporting application for building live charts and dashboards from databases and APIs.

The project is useful when you want a dashboard builder that can sit next to operational systems without pushing every question into a data warehouse first.

It supports classic database sources like MySQL, PostgreSQL, MongoDB, TimescaleDB, Supabase DB, RDS variants, and ClickHouse. It also has API and service-oriented sources such as REST APIs, Google Analytics, Firebase/Firestore, Customer.io, Jira, Strapi, and Stripe.

Why Self-Host Chartbrew?

  • Keep dashboards close to your systems: Connect directly to internal databases and APIs.
  • Control the deployment: Run the UI, API, database, and Redis in your own infrastructure.
  • Share selectively: Use public dashboards, reports, embeds, and team roles instead of handing out database access.
  • Extend through sources: The backend has a source plugin registry, and the frontend has matching source builders/forms.

Tech Overview of Chartbrew

Chartbrew is a JavaScript monorepo split into client/ and server/.

The frontend is a React 19 application built with Vite, HeroUI, Redux Toolkit, Tailwind, Monaco Editor, and Chart.js. It contains the dashboard builder, dataset screens, source connection forms, public dashboard views, reports, embeds, login/signup, and team settings.

The backend is an Express 5 API using Sequelize migrations/models, Redis, BullMQ, Socket.IO, JWT auth, encrypted secrets, and route-level authorization. It owns connections, data requests, datasets, chart preparation, team/project permissions, integrations, templates, queues, and AI-assisted workflows.

Architecture & Components

Chartbrew’s data model is organized around a few important objects:

  • Connection: How Chartbrew reaches a database, API, or SaaS source.
  • DataRequest: One query/request against a connection.
  • Dataset: Reusable data built from one or more data requests, including joins.
  • ChartDatasetConfig: The binding layer between reusable datasets and individual charts.
  • Chart visualization: The semantic chart configuration used by the newer visualization runtime.

This split is useful. The dataset can stay focused on source data, joins, and scope, while the chart decides how that data should be presented.

The backend docs describe the runtime flow as connection -> data request -> dataset -> chart dataset config -> visualization engine -> Chart.js/table/metric/export output.

Key Technologies

  • Frontend: React, Vite, HeroUI, Redux Toolkit, Tailwind, Chart.js, Monaco Editor.
  • Backend: Node.js, Express, Sequelize, Socket.IO, BullMQ, Redis.
  • Databases: MySQL or PostgreSQL for Chartbrew itself.
  • Sources: REST APIs, MongoDB, PostgreSQL, MySQL, ClickHouse, Firebase, Google Analytics, Jira, Stripe, Customer.io, Strapi, and more.
  • Testing: Vitest backend test suite with unit and integration coverage for auth, routes, visualization, migrations, source plugins, and security rules.

Self-Hosting Chartbrew with Docker

Chartbrew publishes a Docker image as razvanilin/chartbrew and includes both MySQL and PostgreSQL compose examples in the repository.

The app defaults to:

  • Frontend: 4018
  • API: 4019
  • Redis: 6379
  • MySQL: 3306 if using the MySQL compose path

Docker Compose Configuration

The public Home-Lab snippets repository expected by my usual workflow was not available on this machine at /home/jalcocert/Desktop/Home-Lab, so this post includes the tested compose pattern directly.

This version avoids weak secret defaults and keeps Redis/MySQL non-public except for the host mappings you choose. For a normal single-host deployment, you can remove the Redis host port entirely if no external process needs it.

services:
  db:
    image: mysql:8.4
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${CB_DB_NAME:?Set CB_DB_NAME in .env}
      MYSQL_USER: ${CB_DB_USERNAME:?Set CB_DB_USERNAME in .env}
      MYSQL_PASSWORD: ${CB_DB_PASSWORD:?Set CB_DB_PASSWORD in .env}
      MYSQL_RANDOM_ROOT_PASSWORD: "true"
      MYSQL_DEFAULT_AUTH: caching_sha2_password
    volumes:
      - chartbrew-db:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 10

  redis:
    image: redis:alpine
    restart: unless-stopped

  chartbrew:
    image: razvanilin/chartbrew:latest
    restart: unless-stopped
    environment:
      CB_BULLMQ_USERNAME: ${CB_BULLMQ_USERNAME:?Set CB_BULLMQ_USERNAME in .env}
      CB_BULLMQ_PASSWORD: ${CB_BULLMQ_PASSWORD:?Set CB_BULLMQ_PASSWORD in .env}
      CB_DB_NAME: ${CB_DB_NAME:?Set CB_DB_NAME in .env}
      CB_DB_USERNAME: ${CB_DB_USERNAME:?Set CB_DB_USERNAME in .env}
      CB_DB_PASSWORD: ${CB_DB_PASSWORD:?Set CB_DB_PASSWORD in .env}
      CB_DB_HOST: db
      CB_DB_PORT: 3306
      CB_DB_DIALECT: mysql
      CB_REDIS_HOST: redis
      CB_REDIS_PORT: 6379
      CB_REDIS_PASSWORD: ""
      CB_SECRET: ${CB_SECRET:?Set CB_SECRET in .env}
      CB_ENCRYPTION_KEY: ${CB_ENCRYPTION_KEY:?Set CB_ENCRYPTION_KEY in .env}
      CB_API_HOST: 0.0.0.0
      CB_API_PORT: 4019
      VITE_APP_CLIENT_HOST: ${VITE_APP_CLIENT_HOST:-http://localhost:4018}
      VITE_APP_CLIENT_PORT: 4018
      VITE_APP_API_HOST: ${VITE_APP_API_HOST:-http://localhost:4019}
      CB_ADMIN_MAIL: ${CB_ADMIN_MAIL:[email protected]}
      CB_RESTRICT_TEAMS: ${CB_RESTRICT_TEAMS:-0}
      CB_RESTRICT_SIGNUP: ${CB_RESTRICT_SIGNUP:-0}
      CB_ALLOW_PRIVATE_NETWORK_CALLS: "false"
    ports:
      - "4018:4018"
      - "4019:4019"
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

volumes:
  chartbrew-db:

Create a .env next to the compose file:

CB_BULLMQ_USERNAME=chartbrew
CB_BULLMQ_PASSWORD=<long-random-password>
CB_DB_NAME=chartbrew
CB_DB_USERNAME=chartbrew
CB_DB_PASSWORD=<long-random-password>
CB_SECRET=<long-random-string>
CB_ENCRYPTION_KEY=<64-hex-character-key>
VITE_APP_CLIENT_HOST=http://localhost:4018
VITE_APP_API_HOST=http://localhost:4019

Generate the encryption key with Node:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Validate and start:

docker compose config
docker compose up -d

Open http://localhost:4018 and create the first account.

After that, consider setting:

CB_RESTRICT_SIGNUP=1

That prevents open signup after your initial user/team setup.

Security and Operations Notes

Chartbrew connects to sensitive systems, so treat it like an internal admin tool:

  • Keep CB_ENCRYPTION_KEY stable and private. It must be a 64-character hex string for AES-256 usage.
  • Protect the app behind TLS and your normal access controls.
  • Keep CB_ALLOW_PRIVATE_NETWORK_CALLS=false unless you have a specific need and understand the risk.
  • Do not expose MySQL or Redis publicly.
  • Set strong BullMQ dashboard credentials because /apps/queues is protected with Basic Auth.
  • Back up the database before upgrades because migrations run at application startup.

Recent repository history also shows active security hardening around Slack query execution, chart preview connection scoping, IPv6 SSRF handling, and team role escalation.

Conclusion

Chartbrew is worth a look if you want self-hosted dashboards without committing to a heavier warehouse-first BI stack.

It gives you a web UI for connecting sources, building datasets, creating charts, and sharing dashboards, while still keeping deployment understandable: one Node app, one relational database, and Redis.

The licensing deserves attention. Current versions use FSL-1.1-MIT: self-hosted internal use is allowed, but offering Chartbrew itself as a competing hosted product needs a commercial license. Each version becomes MIT two years after release.

For alternatives, compare it with Rill if you prefer code-first BI on DuckDB, WrenAI if you want natural-language analytics over databases, and Rybbit if your main use case is web/product analytics.

FAQ