BemiDB is an open-source Snowflake and Fivetran alternative bundled together. It seamlessly connects to different data sources, syncs data in a compressed columnar format to S3, and allows you to run complex queries using its Postgres-compatible analytical query engine.

CREATE USER catalog_user LOGIN PASSWORD 'password';
CREATE DATABASE catalog OWNER catalog_user;
See AWS IAM policy example
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::[AWS_S3_BUCKET]",
"arn:aws:s3:::[AWS_S3_BUCKET]/*"
]
}
]
}
# Configured catalog database URL (host.docker.internal allows connecting to localhost from a container)
export CATALOG_DATABASE_URL=postgres://catalog_user:password@host.docker.internal:5432/catalog
# AWS S3 environment variables (data will be stored in s3://bemidb-bucket/iceberg/*)
export AWS_REGION=us-west-1
export AWS_S3_BUCKET=bemidb-bucket
export AWS_ACCESS_KEY_ID=[...]
export AWS_SECRET_ACCESS_KEY=[...]
docker run \
-e SOURCE_POSTGRES_DATABASE_URL=postgres://user:password@host.docker.internal:5432/source \
-e DESTINATION_SCHEMA_NAME=postgres \
-e AWS_REGION -e AWS_S3_BUCKET -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e CATALOG_DATABASE_URL \
ghcr.io/bemihq/bemidb:latest syncer-postgres
docker run \
-p 54321:54321 \
-e AWS_REGION -e AWS_S3_BUCKET -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e CATALOG_DATABASE_URL \
ghcr.io/bemihq/bemidb:latest server
# List all tables
psql postgres://localhost:54321/bemidb -c "SELECT table_schema, table_name FROM information_schema.tables"
# Query a table
psql postgres://localhost:54321/bemidb -c "SELECT COUNT(*) FROM postgres.[table_name]"
docker run \
-e SOURCE_AMPLITUDE_API_KEY=[...] \
-e SOURCE_AMPLITUDE_SECRET_KEY=[...] \
-e SOURCE_AMPLITUDE_START_DATE=2025-01-01 \
-e DESTINATION_SCHEMA_NAME=amplitude \
-e AWS_REGION -e AWS_S3_BUCKET -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e CATALOG_DATABASE_URL \
ghcr.io/bemihq/bemidb:latest syncer-amplitude
docker run \
-e SOURCE_ATTIO_API_ACCESS_TOKEN=[...] \
-e DESTINATION_SCHEMA_NAME=attio \
-e AWS_REGION -e AWS_S3_BUCKET -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e CATALOG_DATABASE_URL \
ghcr.io/bemihq/bemidb:latest syncer-attio
curl -X POST "https://dialpad.com/api/v2/webhooks" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer [DIALPAD_API_KEY]" \
-d '{
"hook_url": "https://[YOUR_DOMAIN]/[YOUR_WEBHOOK_ENDPOINT]",
"secret": "[YOUR_WEBHOOK_SECRET]"
}'
curl -X POST "https://dialpad.com/api/v2/subscriptions/sms" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer [DIALPAD_API_KEY]" \
-d '{
"direction": "all",
"enabled": true,
"endpoint_id": "[WEBHOOK_ID]",
"include_internal": false,
"status": false
}'
See example code in Node.js
import express from 'express';
import bodyParser from 'body-parser';
import { jwtVerify } from 'jose';
import { connect, JSONCodec } from 'nats';
const app = express();
app.use(bodyParser.json());
app.post('/dialpad-webhook', async (req, res) => {
const { payload } = await jwtVerify(request.body, new TextEncoder().encode('[YOUR_WEBHOOK_SECRET]'), { algorithms: ['HS256'] });
const jsonCodec = JSONCodec();
const natsConnection = await connect({ servers: "nats://host.docker.internal:4222" });
const jetstreamManager = await natsConnection.jetstreamManager();
await jetstreamManager.streams.add({ name: 'bemidb', subjects: ['bemidb.dialpad'] });
await jetstreamManager.jetstream().publish('bemidb.dialpad', jsonCodec.encode(payload));
});
app.listen(3000, () => console.log('Server is running on port 3000'));
docker run \
-e NATS_URL=nats://host.docker.internal:4222 \
-e NATS_JETSTREAM_STREAM=bemidb \
-e NATS_JETSTREAM_SUBJECT=bemidb.dialpad \
-e NATS_JETSTREAM_CONSUMER_NAME=bemidb-dialpad \
-e DESTINATION_SCHEMA_NAME=dialpad \
-e AWS_REGION -e AWS_S3_BUCKET -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e CATALOG_DATABASE_URL \
ghcr.io/bemihq/bemidb:latest syncer-dialpad
By default, BemiDB syncs all tables from the Postgres database. To include and sync only specific tables from your Postgres database:
docker run \
-e SOURCE_POSTGRES_DATABASE_URL=postgres://user:password@host.docker.internal:5432/source \
-e SOURCE_POSTGRES_INCLUDE_TABLES=public.table1,public.table2 \ # A comma-separated list of tables to include
-e DESTINATION_SCHEMA_NAME=postgres \
-e AWS_REGION -e AWS_S3_BUCKET -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e CATALOG_DATABASE_URL \
ghcr.io/bemihq/bemidb:latest syncer-postgres
To exclude specific tables during the sync:
docker run \
-e SOURCE_POSTGRES_DATABASE_URL=postgres://user:password@host.docker.internal:5432/source \
-e SOURCE_POSTGRES_EXCLUDE_TABLES=public.audit_log,public.cache \ # A comma-separated list of tables to exclude
-e DESTINATION_SCHEMA_NAME=postgres \
-e AWS_REGION -e AWS_S3_BUCKET -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e CATALOG_DATABASE_URL \
ghcr.io/bemihq/bemidb:latest syncer-postgres
BemiDB can work with various S3-compatible object storage solutions, such as MinIO.
You can run MinIO locally:
minio server ./minio-data
# API: http://172.17.0.3:9000 http://127.0.0.1:9000
# WebUI: http://172.17.0.3:9001 http://127.0.0.1:9001
Create a bucket named bemidb-bucket in MinIO:
mc alias set local http://localhost:9000 minioadmin minioadmin123
mc mb local/bemidb-bucket --ignore-existing
Export and use environment variables when starting BemiDB:
export AWS_REGION=us-west-1
export AWS_S3_BUCKET=bemidb-bucket
export AWS_ACCESS_KEY_ID=minioadmin
export AWS_SECRET_ACCESS_KEY=minioadmin123
export AWS_S3_ENDPOINT=http://localhost:9000
syncer-amplitude command options| Environment variable | Default value | Description |
|---|---|---|
DESTINATION_SCHEMA_NAME |
Required | Schema name in BemiDB to sync data to. |
SOURCE_AMPLITUDE_API_KEY |
Required | Amplitude API key for authentication. |
SOURCE_AMPLITUDE_SECRET_KEY |
Required | Amplitude secret key for authentication. |
SOURCE_AMPLITUDE_START_DATE |
2025-01-01 |
Start date for syncing data from Amplitude in YYYY-MM-DD format. |
syncer-attio command options| Environment variable | Default value | Description |
|---|---|---|
DESTINATION_SCHEMA_NAME |
Required | Schema name in BemiDB to sync data to. |
SOURCE_ATTIO_API_ACCESS_TOKEN |
Required | Attio API access token for authentication. |
syncer-dialpad command options| Environment variable | Default value | Description |
|---|---|---|
DESTINATION_SCHEMA_NAME |
Required | Schema name in BemiDB to sync data to. |
NATS_URL |
Required | NATS server URL for connecting to receive Dialpad SMS records. |
NATS_JETSTREAM_STREAM |
Required | NATS JetStream stream name. |
NATS_JETSTREAM_SUBJECT |
Required | NATS JetStream subject name. |
NATS_JETSTREAM_CONSUMER_NAME |
Required | NATS JetStream consumer name. |
NATS_FETCH_TIMEOUT_SECONDS |
30 |
Timeout in seconds for fetching messages from NATS. |
syncer-postgres command options| Environment variable | Default value | Description |
|---|---|---|
DESTINATION_SCHEMA_NAME |
Required | Schema name in BemiDB to sync data to. |
SOURCE_POSTGRES_DATABASE_URL |
Required | Postgres database URL to sync data from. |
SOURCE_POSTGRES_INCLUDE_TABLES |
List of tables to include in sync. Comma-separated schema.table. |
|
SOURCE_POSTGRES_EXCLUDE_TABLES |
List of tables to exclude from sync. Comma-separated schema.table. |
server command options| Environment variable | Default value | Description |
|---|---|---|
BEMIDB_HOST |
0.0.0.0 |
Host for BemiDB to listen on |
BEMIDB_PORT |
54321 |
Port for BemiDB to listen on |
BEMIDB_DATABASE |
bemidb |
Database name |
BEMIDB_USER |
Database user. Allows any if empty | |
BEMIDB_PASSWORD |
Database password. Allows any if empty |
| Environment variable | Default value | Description |
|---|---|---|
CATALOG_DATABASE_URL |
Required | Postgres database URL for the catalog |
AWS_REGION |
Required | AWS region |
AWS_S3_BUCKET |
Required | AWS S3 bucket name |
AWS_ACCESS_KEY_ID |
Required | AWS access key ID |
AWS_SECRET_ACCESS_KEY |
Required | AWS secret access key |
AWS_S3_ENDPOINT |
s3.amazonaws.com |
Custom S3 endpoint URL |
BEMIDB_LOG_LEVEL |
INFO |
Log level: ERROR, WARN, INFO, DEBUG, TRACE |
BEMIDB_DISABLE_ANONYMOUS_ANALYTICS |
false |
Disable collection of anonymous usage metadata |
BemiDB consists of the following main components packaged in a single Docker image:
$ claude mcp add BemiDB \
-- python -m otcore.mcp_server <graph>