MongoDB Database Self-Hosting

Self-Hosting MongoDB 8 on Witchly: Setup, Connection, and Persistence

By Witchly Team · · 9 min read

If your project stores anything more interesting than a row of users — Discord bot guild settings, a leaderboard, user-generated content with weird shapes, analytics events that don’t fit a fixed schema — there’s a good chance MongoDB is a better fit than a relational database. It’s schema-less, JSON-native, and handles deeply nested documents without making you flatten everything first.

This post covers what MongoDB 8 actually is, when self-hosting beats a managed service, and how to deploy a production-ready instance on Witchly’s Elite Applications tier.

What MongoDB is, in one paragraph

MongoDB is a document database. Instead of rows in tables, it stores documents (JSON-like objects) in collections. Each document can have any shape — different fields, nested arrays, embedded sub-documents — and MongoDB indexes and queries across them efficiently.

You read and write through the MongoDB wire protocol, usually via a language driver (mongoose for Node.js, pymongo for Python, the official driver for everything else). Queries look like JSON filters: { user: "alice", role: "admin" } returns every matching document.

It’s a good fit when:

  • Your data has variable shape (Discord guild settings where every guild has different enabled features)
  • You’re doing a lot of reads and your access patterns are simple lookups
  • You don’t need cross-collection joins (or are happy joining in application code)
  • You’re storing things that look like API payloads — JSON in, JSON out

It’s not a good fit when:

  • You need strict transactional integrity across many records (use Postgres instead)
  • You have a fixed schema and want the database to enforce it (use a SQL DB)
  • Your queries involve heavy multi-table joins (Postgres again)

For most Discord bots, web app backends, analytics ingestion pipelines, and game-server-adjacent data stores, MongoDB is the easier path.

Why self-host instead of using a managed service

MongoDB Atlas is the official managed offering, and it’s solid. But for hobby and small-team projects:

  • Atlas’s free tier (M0) is shared. You get 512 MB storage and shared compute. Anything more is metered.
  • Egress is metered. Every byte your bot reads from Atlas counts toward your monthly transfer cap.
  • Backups on free tier are limited. Point-in-time recovery is paid.
  • Latency depends on Atlas’s regions. Pick wrong and your bot’s read latency suffers.

Self-hosting on Witchly:

  • Predictable monthly cost ($2-8/mo depending on plan).
  • All your egress stays inside Witchly’s network if your bot is on the same platform — no external transfer at all.
  • You control the version. Want to stay on MongoDB 8 indefinitely? You can. Want to update on your timeline? Yours.
  • The admin dashboard you already use for game servers manages your DB too. No second control panel.

The trade-off is the usual self-hosting reality: you’re on the hook for backups, monitoring, and config. For most projects, that’s a fair trade for the cost and control gains.

Plan requirements

MongoDB 8 is Elite-only on Witchly. The free tier doesn’t include it because storage-heavy workloads with long-lived persistence need dedicated resources we can guarantee.

Deploy from the Applications category → MongoDB 8 in the deploy wizard. Pick the Elite plan that matches your data size:

PlanRAMDiskBest for
The Helper ($2/mo)1 GBsmallTiny bot, dev/staging environments, side projects
The Daemon ($4/mo)2 GBmediumProduction bot, web app backend, ~hundreds of MB working set
The Orchestrator ($8/mo)4 GBlargeHeavier production workloads, multi-collection apps

MongoDB’s RAM hunger comes from its working set — the indexes and frequently-accessed documents it keeps in memory for fast queries. Plan slightly above your estimated working set, not your total disk.

Deploying

In the dashboard’s deploy wizard:

  1. Pick the Applications category
  2. Choose MongoDB 8
  3. Set the server name (e.g., “myproject-mongo”)
  4. Pick your Elite plan
  5. The Configure step will surface required environment variables — most importantly the root password and the database/user names
  6. Deploy

After provisioning, the Console tab shows MongoDB’s startup output. When you see:

{"t":...,"s":"I","c":"NETWORK","msg":"Waiting for connections","attr":{"port":27017}}

(or whichever port the egg picked from the Applications port range), the database is up.

Witchly’s Applications port ranges have UFW firewall rules and panel allocations registered, so the public address shown in the Network tab is reachable from the internet.

The connection string

Your app connects via a MongoDB URI:

mongodb://username:[email protected]:27017/databasename?authSource=admin

The pieces:

  • username / password — what you set in the Configure step
  • your-server.witchly.host — from your Network tab in the dashboard
  • port — also from the Network tab (Applications port range)
  • databasename — the specific DB inside Mongo (Mongo can hold many)
  • authSource=admin — tells the driver which DB to authenticate against; almost always admin unless you specifically created the user inside another DB

Plug this URI into your app’s environment variable (MONGO_URI, DATABASE_URL, whatever your codebase uses).

What mongod.conf knobs to know

The egg ships a sensible mongod.conf. Most users never edit it. The exceptions:

Bind IP

By default, MongoDB binds to all interfaces (0.0.0.0) so your remote app can connect. If you ever needed to restrict it (e.g., bot lives on the same Witchly network and you want zero external access), you’d change:

net:
  bindIp: 0.0.0.0

To a specific address. For most users, leave it.

Storage engine settings

storage:
  wiredTiger:
    engineConfig:
      cacheSizeGB: 0.5

WiredTiger is MongoDB’s default storage engine. The cache size defaults to ~50% of RAM, which is sensible. Lower it if your plan’s RAM is being competed for by other things; raise it if your queries are I/O-bound and disk reads are showing up in metrics.

Authentication

security:
  authorization: enabled

The egg enables authentication out of the box. Don’t disable it. A MongoDB instance with authorization: disabled and any public exposure is a security incident waiting to happen.

After editing mongod.conf via the File Manager, restart the server in the Console tab. MongoDB picks up config changes only on restart.

Indexing — the one performance thing you have to think about

Most slow MongoDB queries are slow because they’re full-collection scans on a missing index. Adding an index is usually a one-line fix.

When you query like:

db.users.find({ guildId: "12345" })

MongoDB has to scan every document unless guildId has an index. To create one:

db.users.createIndex({ guildId: 1 })

Now find({ guildId: ... }) is a fast B-tree lookup.

Two index patterns that cover 90% of small-app cases:

  1. Single-field indexes on common query fields (userId, guildId, email).
  2. Compound indexes when you query multiple fields together. db.events.createIndex({ userId: 1, timestamp: -1 }) is great for “show me Alice’s events in reverse-chronological order.”

Don’t index fields you never query. Indexes cost write performance and disk.

Backups: the boring but critical part

The Witchly backup system snapshots your server’s whole data directory, but the cleanest way to backup MongoDB specifically is using mongodump — it creates a logical backup that’s portable to any MongoDB instance, not just a Witchly-snapshot.

Schedule a daily mongodump task via the Schedules tab. Example command:

mongodump --uri="mongodb://admin:[email protected]:27017" --out=/home/container/backups/$(date +%Y%m%d)

The dumps land in your server’s /backups/ directory; rotate manually or with a small cron-driven cleanup script.

Combine that with the dashboard’s auto-backup feature (which snapshots the whole disk including those dumps) and you have:

  • Tier 1: A logical, portable backup created by mongodump daily
  • Tier 2: A whole-disk snapshot created by Witchly’s auto-backup
  • Tier 3: (Optional) Download backups off-platform weekly for true disaster recovery

For most projects, daily mongodump plus Witchly auto-backup is more than enough.

Connecting from a Discord bot

Most Node.js Discord bots use either the official mongodb driver or mongoose (an ORM-like wrapper). With the connection URI from your dashboard, both look identical:

import mongoose from 'mongoose'

await mongoose.connect(process.env.MONGO_URI!)

const GuildConfig = mongoose.model('GuildConfig', new mongoose.Schema({
  guildId: { type: String, required: true, unique: true, index: true },
  prefix:  { type: String, default: '!' },
  enabledModules: { type: [String], default: [] },
}))

// Save:
await GuildConfig.findOneAndUpdate(
  { guildId },
  { $set: { prefix: '?' } },
  { upsert: true }
)

For Python bots:

from pymongo import MongoClient

client = MongoClient(os.environ["MONGO_URI"])
db = client["mybot"]

# Save
db.guild_configs.update_one(
    {"guildId": guild_id},
    {"$set": {"prefix": "?"}},
    upsert=True
)

Both drivers maintain a connection pool automatically. Don’t open a new connection per query.

Production checklist

Before pointing real users or a real bot at your MongoDB:

  • Authentication is enabled (security.authorization: enabled in mongod.conf — default in the egg).
  • The root password is something other than the default. Change it via the Configure step or by entering MongoDB shell and running db.changeUserPassword(...).
  • Indexes exist on every field you query frequently. Run db.collection.find({...}).explain('executionStats') to check.
  • A scheduled mongodump runs daily.
  • The dashboard’s auto-backup is enabled.
  • You’ve tested a restore at least once. (Dump → restore to a dev DB → verify data matches.)
  • You’ve set up scheduled auto-restarts (Schedules tab, weekly is fine — MongoDB doesn’t need restarts as often as JVM apps).

When to consider scaling up

A single-node MongoDB on Witchly’s Daemon ($4/mo, 2 GB RAM) can comfortably serve:

  • A Discord bot in a few hundred guilds with light per-guild data
  • A small web app with thousands of users
  • An analytics pipeline ingesting tens of thousands of events per day

You’ll start hitting limits when:

  • Working set (frequently-accessed data) doesn’t fit in RAM. Indicators: query latency rises, disk I/O spikes.
  • Write throughput exceeds what one node can handle. Rare on hobby projects but real for higher-traffic apps.

The fix is usually: upgrade your plan for more RAM and disk. MongoDB’s horizontal-scaling features (sharding, replica sets) are powerful but operationally involved — most projects never need them.

Wrapping up

MongoDB 8 on Witchly’s Elite tier is a clean, predictable, dollars-a-month database for any project that benefits from JSON-shaped storage. Authentication is on by default, the dashboard handles backups and monitoring, and you connect to it the same way you’d connect to any other Mongo instance — there’s no Witchly-specific weirdness.

Deploy it from the Applications pricing page, or read the MongoDB setup doc for the dashboard walkthrough.