📚 Cookbook — Recipe 01

Migrate to PostgreSQL

Move BizNode from SQLite to PostgreSQL for production scale, concurrent bots, and VPS hosting — without losing a single row of data.

⏱ ~20 minutes 🔨 Intermediate 🔒 Data-safe 🔄 Reversible
1

SQLite vs PostgreSQL — When to Switch

BizNode ships with SQLite because it works out of the box with zero configuration. For most single-operator deployments it is the right choice. Switch to PostgreSQL when you hit these conditions:

SituationSQLitePostgreSQL
Single bot on one machineIdealOverkill
Multiple bot containers simultaneouslyLocking errors possibleRequired
Hosted on a VPS / cloud serverWorksRecommended
10,000+ rows per tableWorks but slower queriesBetter
Managed DB (Supabase, Neon, Railway)Not compatibleSupported
pg_dump backups / replicationNot availableBuilt-in
💡
Real example A digital agency in Chennai ran 8 BizNode bots from one SQLite file. Concurrent writes from 8 bot containers caused "database is locked" errors. After migrating to a single PostgreSQL instance on a ₹800/month VPS, all 8 bots run simultaneously with zero errors.
2

Prerequisites Checklist

Complete every item before starting the migration.

PostgreSQL 14+ installed and running Windows: postgresql.org/download | Or use a managed service: Supabase, Neon, Railway, ElephantSQL
psycopg2-binary Python package installed
pip install psycopg2-binary
BizNode dashboard accessible at http://localhost:7777 You will update settings there in Step 6.
SQLite database manually backed up Copy memory/biznode.db to a safe location. The migration script never deletes SQLite data, but have a copy before you start.
Bot and dashboard containers stopped
docker compose stop bot dashboard
This prevents writes to SQLite while you migrate.
3

Create the PostgreSQL Database

Connect to your PostgreSQL server and create the database and user. Open a terminal and run:

Using psql (command line)

# Connect as the postgres superuser
psql -U postgres

-- Inside psql:
CREATE DATABASE biznode;
CREATE USER biznode_user WITH PASSWORD 'choose_a_strong_password';
GRANT ALL PRIVILEGES ON DATABASE biznode TO biznode_user;

-- PostgreSQL 15+ also requires this:
\c biznode
GRANT ALL ON SCHEMA public TO biznode_user;

\q

Using pgAdmin (GUI)

  1. Open pgAdmin → right-click Databases → Create → Database → name it biznode
  2. Right-click Login Roles → Create → Login/Group Role → name it biznode_user, set a password
  3. Right-click the biznode database → Properties → Security → add biznode_user with ALL privileges

Using a managed service (Supabase / Neon / Railway)

Create a new project. Copy the connection details — host, port, database name, user, password — you will use them in Step 5.

💡
The migration script creates the schema automatically You do not need to run any CREATE TABLE SQL manually. The script calls BizNode's init_db() which creates all tables on the PostgreSQL database before inserting any data.
4

Backup Your SQLite Data First

Always take a full backup before any migration. Two options:

Option A — Dashboard Backup (recommended)

  1. Open http://localhost:7777
  2. Go to Backup section in the left navigation
  3. Click Run Backup Now on your Local or cloud provider
  4. Wait for the green confirmation — backup is encrypted and stored in backups/

Option B — Manual file copy

# Windows PowerShell — from your BizNode folder
Copy-Item memory\biznode.db memory\biznode.db.pre-migration-backup

# Or copy to an external location
Copy-Item memory\biznode.db "C:\Backups\biznode-$(Get-Date -f yyyyMMdd-HHmmss).db"
Stop the bot before backing up for consistency If the bot is running and writing to SQLite, the backup may catch a mid-write state. Run docker compose stop bot before backing up for a clean snapshot.
5

Run the Migration Script

BizNode includes scripts/migrate_to_postgresql.py — it reads every table from SQLite and inserts the data into PostgreSQL. It is safe to run multiple times (uses ON CONFLICT DO NOTHING).

Step 5a — Dry run first (preview only)

cd "C:\path\to\your\BizNode"
python scripts/migrate_to_postgresql.py --dry-run

Expected output — shows what will be migrated without writing anything:

[DRY-RUN] agent_identity : 1 row would be migrated [DRY-RUN] backup_config : 1 row would be migrated [SKIP] notes : 0 rows [SKIP] leads : 0 rows ... DRY-RUN complete. No data was written.

Step 5b — Run the actual migration

python scripts/migrate_to_postgresql.py ^
  --pg-host localhost ^
  --pg-port 5432 ^
  --pg-db   biznode ^
  --pg-user biznode_user ^
  --pg-password your_password

Expected output:

============================================================ BizNode — SQLite to PostgreSQL Migration ============================================================ [OK] SQLite : memory/biznode.db [OK] PostgreSQL: localhost:5432/biznode Creating schema on PostgreSQL (init_db)... [OK] Schema created (all tables exist) Migrating 27 tables... [OK] agent_identity : 1 rows read, 1 inserted [OK] backup_config : 1 rows read, 1 inserted [SKIP] notes : 0 rows [SKIP] leads : 0 rows ... Verification — row counts Table SQLite PostgreSQL Status --------------------------------- -------- ---------- -------- agent_identity 1 1 OK backup_config 1 1 OK ============================================================ Migration complete. Rows read from SQLite : 2 Rows inserted (new only) : 2 [OK] All row counts match. ============================================================
All row counts match = migration succeeded If any row shows MISMATCH, check the specific table for errors above and re-run the script — it is safe to run again.
💡
You can also set env vars instead of flags
set DB_TYPE=postgresql
set DB_HOST=localhost
set DB_NAME=biznode
set DB_USER=biznode_user
set DB_PASSWORD=your_password
python scripts/migrate_to_postgresql.py
6

Update Settings via the Dashboard

Tell BizNode to connect to PostgreSQL instead of SQLite through the Setup section.

1
Open the dashboard

Navigate to http://localhost:7777

2
Go to Setup → Database section

In the left navigation, click Setup. Scroll to the Database group.

3
Change the settings

Update each field:

DB_TYPE     = postgresql
DB_HOST     = localhost          (or your VPS IP / managed service hostname)
DB_PORT     = 5432
DB_NAME     = biznode
DB_USER     = biznode_user
DB_PASSWORD = your_password
4
Click Save Settings

The settings are written to your .env file immediately.

Settings take effect only after restart The running containers read DB_TYPE at startup. Saving in the dashboard writes to .env — you must restart the containers in Step 7 for the change to take effect.
7

Restart and Verify

Restart all containers

cd "C:\path\to\your\BizNode"
docker compose up -d --force-recreate

The --force-recreate flag ensures containers re-read the updated .env values.

Check container logs for errors

docker compose logs --tail=40 dashboard
docker compose logs --tail=40 bot

Look for a clean startup. If you see psycopg2.OperationalError the credentials in .env don't match PostgreSQL. Double-check host, port, user, and password.

Verify from command line

python -c "
import os
os.environ['DB_TYPE']     = 'postgresql'
os.environ['DB_HOST']     = 'localhost'
os.environ['DB_NAME']     = 'biznode'
os.environ['DB_USER']     = 'biznode_user'
os.environ['DB_PASSWORD'] = 'your_password'
from memory.db_adapter import get_connection
conn = get_connection()
cur = conn.cursor()
cur.execute('SELECT COUNT(*) as n FROM agent_identity')
print('agent_identity rows:', cur.fetchone())
conn.close()
print('PostgreSQL connection: OK')
"

Send a test message to the bot

Open Telegram and send /start to your bot. A response confirms the bot is connected to PostgreSQL and running correctly.

Check row counts match

python scripts/migrate_to_postgresql.py --dry-run

Run the dry-run again — it now reads from PostgreSQL (since DB_TYPE=postgresql is set) and will show current row counts for confirmation.

Keep memory/biznode.db until fully confirmed Leave the original SQLite file in place for at least 48 hours after migration. If anything goes wrong you can roll back to SQLite in seconds. Only delete it once you are fully satisfied with PostgreSQL.
8

Add PostgreSQL to Docker Compose (Optional)

If you want PostgreSQL to run as a Docker container alongside BizNode instead of a separate installation, add this service to your docker-compose.yml:

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: biznode
      POSTGRES_USER: biznode_user
      POSTGRES_PASSWORD: your_password
    volumes:
      - ./memory/postgres:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    restart: unless-stopped

Then add postgres to the depends_on list of the bot and dashboard services:

  dashboard:
    depends_on:
      - qdrant
      - ollama
      - postgres     # ← add this

  bot:
    depends_on:
      - qdrant
      - ollama
      - postgres     # ← add this
Use the service name as DB_HOST inside Docker When PostgreSQL runs as a Docker service, other containers reach it by its service name — not localhost. Set:
DB_HOST=postgres
localhost inside a container refers to that container itself, not the host machine or other containers.

Then start everything:

docker compose up -d

PostgreSQL data is persisted in memory/postgres/ which is mounted as a volume — it survives container restarts and upgrades.

9

Rollback to SQLite

If anything goes wrong, reverting to SQLite takes under a minute.

1
Open Dashboard → Setup → Database

Change DB_TYPE back to sqlite. Click Save Settings.

Or edit .env directly: set DB_TYPE=sqlite

2
Restart services
docker compose up -d --force-recreate
3
Done — SQLite is active again

The original memory/biznode.db file was never modified. All your original data is intact.

10

FAQ

Will I lose data if I run the migration script twice?
No. The script uses ON CONFLICT DO NOTHING for every INSERT. Rows that already exist in PostgreSQL are silently skipped. Running it again after a partial failure is safe.
What about the Qdrant knowledge base — does that migrate too?
No. Qdrant vector data is stored in memory/qdrant/ on disk — completely separate from SQLite. It is unaffected by this migration. If you move to a different machine, copy the memory/qdrant/ folder separately or re-seed using the seeder scripts.
I get "duplicate key violates unique constraint" errors after migration.
This happens if rows were inserted after migration and the PostgreSQL SERIAL sequence did not get reset. The migration script resets sequences automatically, but if you ran a partial migration manually, fix it by running:

SELECT setval(pg_get_serial_sequence('table_name', 'id'), COALESCE(MAX(id), 1)) FROM table_name;

Run this for each affected table.
Does it work with Managed PostgreSQL (Supabase, Neon, Railway)?
Yes. Set DB_HOST to the managed service's hostname, use the provided port, database name, user, and password. The adapter uses standard psycopg2 — compatible with any PostgreSQL 14+ server.
Can I run SQLite and PostgreSQL simultaneously?
No. DB_TYPE is read at module import time by memory/db_adapter.py. All connections use one backend at a time. You pick one and restart to switch.
Do I need to run the migration script again after updates?
No. After migrating, init_db() runs at every startup using CREATE TABLE IF NOT EXISTS — new tables from new releases are created automatically in PostgreSQL, just like they were in SQLite.
What about MySQL instead of PostgreSQL?
BizNode also supports MySQL. The process is identical — install pymysql instead of psycopg2-binary, create the MySQL database, and set DB_TYPE=mysql. The migration script works the same way since it uses raw psycopg2 or raw sqlite3 for connections, not the adapter.

Migration complete?

Your node is now running on PostgreSQL. No data was lost, rollback is always available.