Skip to main content

PostgreSQL

Volvox.Bot requires PostgreSQL 17 or later. The bot stores all configuration, conversation history, moderation cases, AI feedback, analytics, and other state in PostgreSQL.

Connection string

Set DATABASE_URL to a standard PostgreSQL connection string:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/volvoxbot

SSL configuration

Use the DATABASE_SSL environment variable to control TLS behavior:
ValueBehavior
false / off / disableSSL disabled
no-verifySSL enabled, server certificate not verified
true / on / requireSSL enabled with full certificate verification
(unset)Auto-detected: disabled for localhost, enabled with verification for all other hosts
For Docker Compose deployments the Compose file sets DATABASE_SSL=false automatically since PostgreSQL runs on the internal Docker network.

Connection pool

You can tune the connection pool with these optional variables:
VariableDefaultDescription
PG_POOL_SIZE5Maximum number of pool connections
PG_IDLE_TIMEOUT_MS30000Time (ms) before an idle connection is closed
PG_CONNECTION_TIMEOUT_MS10000Time (ms) to wait for a new connection
PG_SLOW_QUERY_MS100Queries slower than this threshold are logged

Migrations

Volvox.Bot uses node-pg-migrate to manage schema changes. Migration files live in the migrations/ directory. The bot automatically runs pending migrations on startup, so you do not need to run them manually for normal operation. Manual migration commands are useful for verifying your database before first start or for rolling back changes.

Running migrations

# Apply all pending migrations
pnpm migrate

# Roll back the most recent migration
pnpm migrate:down

# Create a new migration file
pnpm migrate:create my-migration-name
The migration runner tracks applied migrations in the pgmigrations table. If a migration has already been applied, it is skipped.
Running pnpm migrate:down rolls back only the most recently applied migration. Run it multiple times to roll back further. Always back up your database before rolling back in production.

Migration files

All migration files are in migrations/. Each file exports an up function that applies the change and a down function that reverses it.
Creates the full initial table set for the bot:
  • config — Guild and global key-value configuration store
  • conversations — AI conversation message history
  • mod_cases — Moderation action records (warn, kick, ban, etc.)
  • mod_scheduled_actions — Pending timed moderation actions (tempbans, timeouts)
  • memory_optouts — Users who have opted out of AI memory
  • ai_usage — Per-call AI token usage and cost tracking
  • logs — Structured application log storage
  • bot_restarts — Restart history with uptime and version
  • help_topics — Guild-managed help topic content
  • scheduled_messages — Scheduled and recurring message jobs
  • starboard_posts — Starred messages forwarded to the starboard channel
  • polls — Poll questions, options, and votes
  • snippets — Guild code snippet library
  • github_feed_state — Last-seen event ID per GitHub repository feed
  • afk_status — Active AFK entries per user per guild
  • afk_pings — Pings received while a user was AFK
  • reputation — XP, level, and engagement counters per user per guild
  • user_stats — Message counts, reactions, and activity tracking
  • showcases — Community project showcase entries
  • showcase_votes — One vote per user per showcase
  • reviews — Code review requests
  • challenge_solves — Daily challenge completion records
  • flagged_messages — Messages flagged for review
  • tickets — Support ticket threads
  • audit_logs — Admin action history
  • reminders — User reminders with optional recurrence
Adds a discord_message_id column to the conversations table. This stores the native Discord message ID alongside each row so the dashboard can generate clickable jump URLs for individual messages.
Creates the ai_feedback table to store thumbs-up / thumbs-down reactions on AI-generated messages. Includes a per-user per-message unique constraint to prevent duplicate feedback. Gated behind ai.feedback.enabled in the guild config.
Adds composite and trigram indexes to improve query performance:
  • idx_ai_feedback_guild_created — Composite index for feedback trend and recency queries
  • idx_conversations_content_trgm — GIN trigram index on conversations.content for fast ILIKE search (installs pg_trgm)
  • idx_conversations_guild_created — Two-column index for the default 30-day conversation listing
  • idx_flagged_messages_guild_message — Index for flagged message detail and flag endpoints
Creates the voice_sessions table to track voice channel activity. Records join, leave, and move events with duration in seconds. A partial unique index prevents duplicate open sessions for the same user in the same guild after a crash. Gated behind voice.enabled in the guild config.
Creates the webhook_delivery_log table to record outbound webhook delivery attempts per endpoint. Stores the event type, payload, HTTP response code, and delivery status (success, failed, pending). Endpoint configurations live in the per-guild config JSON.
Creates the command_usage table for slash command analytics. Each row records the guild, user, command name, channel, and timestamp. Installs pgcrypto for UUID primary keys. Includes composite indexes for guild time-series, command popularity, per-user history, and channel-filtered analytics queries.
Creates the guild_command_aliases table. Each row maps a custom short name (e.g. w) to an existing bot command (e.g. warn) for a specific guild. Stores the Discord slash command ID returned after registering the alias so it can be cleanly removed later.
Creates two tables for reaction role menus:
  • reaction_role_menus — One row per role menu message posted in Discord
  • reaction_role_entries — One row per emoji → role mapping attached to a menu
Creates the role_menu_templates table for reusable reaction role menu templates. Supports built-in templates (is_builtin = true) and custom guild templates. Shared templates (is_shared = true) are visible to all guilds.
Creates the temp_roles table to track role assignments with an expiry time. The bot scheduler polls this table and removes roles when they expire. Includes a partial index for efficient pending-expiry queries.
Creates the warnings table for the comprehensive warning system. Each warning has a severity (low, medium, high), a point value, an optional expiry date, and a reference to its parent mod case. Includes partial indexes for active warnings and expiry polling.
A no-op migration that occupies slot 012 to keep the numbering sequence monotonically increasing. This exists because two migrations were originally given the 004 prefix during concurrent development and were later renumbered to avoid conflicts.
Adds a user_tag column to the audit_logs table and creates an additional idx_audit_logs_guild_user index for filtering audit log entries by admin user within a guild.

Redis

Redis is optional but strongly recommended. When REDIS_URL is not set, the bot logs a notice and falls back to in-memory state with no distributed caching.

What Redis is used for

  • Config caching — Per-guild configuration is cached in Redis to avoid database reads on every message
  • Rate limiting — Sliding-window rate limits for AI responses and commands
  • Discord API response caching — Reduces repeated API calls for member and role data
  • Session storage — OAuth2 session tokens for the web dashboard
  • Reputation/XP — Short-lived counters before flushing to PostgreSQL

Connection string

REDIS_URL=redis://localhost:6379
For Redis with authentication:
REDIS_URL=redis://:your_password@localhost:6379
For Redis with TLS:
REDIS_URL=rediss://localhost:6380
The bot uses ioredis with automatic reconnection. If Redis becomes unavailable at runtime, the bot degrades gracefully — caching is disabled but the bot continues operating.
In Docker Compose, REDIS_URL is automatically set to redis://redis:6379 pointing at the internal redis service. You do not need to set it in .env for Docker deployments.

Backup and restore

Backing up PostgreSQL

Use pg_dump to create a plain-SQL backup:
pg_dump -U postgres -d volvoxbot > backup.sql
For a compressed backup:
pg_dump -U postgres -d volvoxbot -Fc -f backup.dump
For Docker Compose:
docker compose exec db pg_dump -U postgres volvoxbot > backup.sql

Restoring PostgreSQL

From a plain-SQL backup:
psql -U postgres -d volvoxbot < backup.sql
From a compressed backup:
pg_restore -U postgres -d volvoxbot backup.dump
For Docker Compose:
docker compose exec -T db psql -U postgres volvoxbot < backup.sql
Restore operations overwrite existing data. Always take a backup of the current database before restoring from an older snapshot.

Backing up Redis

Redis persists data to disk via RDB snapshots. In the Docker Compose setup the redisdata volume stores these snapshots. To force a manual snapshot:
docker compose exec redis redis-cli BGSAVE
Copy the RDB file from the container:
docker compose cp redis:/data/dump.rdb ./redis-backup.rdb

Build docs developers (and LLMs) love