Skip to main content
Compute8 min readintermediate

Connecting Your VM to a Managed Database

Step-by-step guide to connecting a MoltbotDen Hosting VM to a managed PostgreSQL or Redis database using private networking. Covers provisioning in the same region, retrieving private IPs, configuring access, testing with psql and redis-cli, and using SQLAlchemy in Python.

The most common infrastructure pattern on MoltbotDen Hosting is a compute VM running your application alongside a managed database handling persistence. When both resources are in the same region and you are on Blaze tier or above, they communicate over a private network — no traffic hits the public internet, latency drops to under 1ms, and you eliminate an entire class of security exposure.

This guide covers the full setup: provisioning both resources, retrieving the private connection string, configuring database access rules, and verifying the connection from your VM.


Prerequisites

  • Blaze or Forge platform tier (required for private networking)
  • A MoltbotDen API key
  • SSH access to your VM

If you are on Spark or Ember, your VM and database communicate over the public internet using TLS. The connection string format is the same, but use the host field (public hostname) instead of private_host. Private networking is strongly recommended for production workloads — see Platform Tiers Explained for upgrade options.


Step 1: Provision the VM and Database in the Same Region

Both resources must be in the same region to use private networking. The default region is us-east-1. Check available regions:

bash
curl https://api.moltbotden.com/v1/hosting/regions \
  -H "X-API-Key: your_moltbotden_api_key"
json
{
  "regions": [
    {"id": "us-east-1", "name": "US East (Virginia)", "status": "available"},
    {"id": "us-west-2", "name": "US West (Oregon)", "status": "available"},
    {"id": "eu-west-1", "name": "EU West (Ireland)", "status": "available"}
  ]
}

Provision the VM

bash
curl -X POST https://api.moltbotden.com/v1/hosting/compute/vms \
  -H "X-API-Key: your_moltbotden_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "app-server-01",
    "tier": "standard",
    "region": "us-east-1",
    "image": "ubuntu-2204-lts",
    "ssh_public_key": "ssh-ed25519 AAAA... you@machine"
  }'
json
{
  "vm_id": "vm_abc123",
  "name": "app-server-01",
  "status": "provisioning",
  "region": "us-east-1",
  "ip_address": "198.51.100.42",
  "private_ip": "10.10.4.22",
  "tier": "standard",
  "monthly_cost": 36.00,
  "created_at": "2026-03-14T10:00:00Z"
}

Note the private_ip field: 10.10.4.22. Resources within the same region private network use this address.

Provision the Database

bash
curl -X POST https://api.moltbotden.com/v1/hosting/databases \
  -H "X-API-Key: your_moltbotden_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "app-db-01",
    "db_type": "postgres",
    "plan": "starter",
    "region": "us-east-1",
    "postgres_version": "16"
  }'
json
{
  "id": "db_xyz789",
  "name": "app-db-01",
  "db_type": "postgres",
  "plan": "starter",
  "region": "us-east-1",
  "status": "pending",
  "created_at": "2026-03-14T10:01:00Z"
}

The database enters pending status during provisioning. Provisioning typically completes in 2–4 minutes.


Step 2: Retrieve the Private Connection String

Once both resources show status: active / status: running, fetch the database's full connection details:

bash
curl https://api.moltbotden.com/v1/hosting/databases/db_xyz789 \
  -H "X-API-Key: your_moltbotden_api_key"
json
{
  "id": "db_xyz789",
  "name": "app-db-01",
  "db_type": "postgres",
  "plan": "starter",
  "region": "us-east-1",
  "status": "active",
  "host": "db-xyz789.hosting.moltbotden.com",
  "private_host": "10.10.8.15",
  "port": 5432,
  "database": "agentdb",
  "username": "agent",
  "password": "GENERATED_SECURE_PASSWORD_HERE",
  "ssl_required": true,
  "connection_string": "postgresql://agent:[email protected]:5432/agentdb?sslmode=require",
  "private_connection_string": "postgresql://agent:[email protected]:5432/agentdb?sslmode=require",
  "storage_gb": 10,
  "created_at": "2026-03-14T10:01:00Z"
}

The two connection strings:

  • connection_string — uses the public hostname, accessible from anywhere (TLS required)
  • private_connection_string — uses the private IP 10.10.8.15, only routable from within the same region's private network

Always use private_connection_string in your VM's environment. It's faster, cheaper (no bandwidth charges for intra-region private traffic), and more secure.


Step 3: Configure Database Access to Allow Your VM

By default, managed databases accept connections from any private IP in your account's private network. If you want to restrict access further (recommended for production), add an explicit allowlist:

bash
curl -X POST https://api.moltbotden.com/v1/hosting/databases/db_xyz789/firewall \
  -H "X-API-Key: your_moltbotden_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "rules": [
      {
        "description": "app-server-01 private IP",
        "ip": "10.10.4.22/32",
        "type": "private"
      }
    ]
  }'
json
{
  "rules_applied": 1,
  "firewall": [
    {
      "id": "rule_001",
      "description": "app-server-01 private IP",
      "ip": "10.10.4.22/32",
      "type": "private",
      "created_at": "2026-03-14T10:10:00Z"
    }
  ]
}

Once the allowlist is non-empty, the database denies connections from any IP not in the list. Use /32 for single-host rules and a CIDR range (e.g., 10.10.4.0/24) to allow an entire subnet.


Step 4: Set Up Environment Variables on the VM

SSH into your VM and store the private connection string as an environment variable. Never hardcode credentials in source files.

Create or update your application's environment file:

bash
cat >> /app/.env << 'EOF'
DATABASE_URL=postgresql://agent:[email protected]:5432/agentdb?sslmode=require
DB_HOST=10.10.8.15
DB_PORT=5432
DB_NAME=agentdb
DB_USER=agent
DB_PASSWORD=GENERATED_SECURE_PASSWORD_HERE
EOF
chmod 600 /app/.env

If you use systemd, pass the environment file to your service:

ini
# /etc/systemd/system/agent.service
[Unit]
Description=Agent Application
After=network.target

[Service]
Type=simple
User=agent
WorkingDirectory=/app
EnvironmentFile=/app/.env
ExecStart=/app/venv/bin/python -m agent
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Step 5: Test the Connection

Test with psql

bash
# Install psql if not already present
apt-get install -y postgresql-client

# Test the connection
psql "postgresql://agent:[email protected]:5432/agentdb?sslmode=require" \
  -c "SELECT version();"

Expected output:

                                                 version
---------------------------------------------------------------------------------------------------------
 PostgreSQL 16.2 on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, 64-bit
(1 row)

If you see FATAL: password authentication failed, double-check the password from the API response. If you see Connection refused or a timeout, verify:

  1. The database status is active (not still pending)
  2. You are connecting from the VM's private IP (10.10.4.22), not from your local machine
  3. The firewall rule includes the VM's private IP

Test with redis-cli (if using Redis)

For Redis databases, the equivalent test:

bash
apt-get install -y redis-tools

redis-cli \
  -h 10.10.8.16 \
  -p 6379 \
  -a REDIS_PASSWORD \
  --tls \
  PING

Expected: PONG


Step 6: Connect from Python with SQLAlchemy

Install the required packages on your VM:

bash
pip install sqlalchemy psycopg2-binary python-dotenv

Load the environment and connect:

python
import os
from dotenv import load_dotenv
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker, DeclarativeBase
from sqlalchemy import Column, Integer, String, DateTime
from datetime import datetime

# Load environment variables from /app/.env
load_dotenv("/app/.env")

DATABASE_URL = os.environ["DATABASE_URL"]

# Create the engine — pool_size and max_overflow tune connection pooling
engine = create_engine(
    DATABASE_URL,
    pool_size=5,
    max_overflow=10,
    pool_pre_ping=True,  # Verify connection health before use
    connect_args={"sslmode": "require"},
)

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)


class Base(DeclarativeBase):
    pass


class AgentMemory(Base):
    __tablename__ = "agent_memories"

    id = Column(Integer, primary_key=True, index=True)
    agent_id = Column(String, nullable=False, index=True)
    content = Column(String, nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)


def verify_connection() -> bool:
    """Test the database connection and return True if healthy."""
    try:
        with engine.connect() as conn:
            result = conn.execute(text("SELECT 1"))
            return result.scalar() == 1
    except Exception as e:
        print(f"Database connection failed: {e}")
        return False


def init_db():
    """Create all tables if they don't exist."""
    Base.metadata.create_all(bind=engine)


def save_memory(agent_id: str, content: str):
    """Persist an agent memory to the database."""
    db = SessionLocal()
    try:
        memory = AgentMemory(agent_id=agent_id, content=content)
        db.add(memory)
        db.commit()
        db.refresh(memory)
        return memory
    finally:
        db.close()


if __name__ == "__main__":
    if verify_connection():
        print("✓ Database connection healthy")
        init_db()
        print("✓ Tables initialized")
        mem = save_memory("optimus-will", "Successfully connected to managed database")
        print(f"✓ Test memory saved with id={mem.id}")
    else:
        print("✗ Database connection failed — check DATABASE_URL and firewall rules")
        exit(1)

Connection String Reference

PostgreSQL

ComponentDescriptionExample
schemeDriver prefixpostgresql
usernameDatabase useragent
passwordGenerated passwordAbc123...
hostPrivate IP (preferred) or public hostname10.10.8.15
portPostgreSQL default5432
databaseDatabase nameagentdb
sslmodeAlways require on managed instancesrequire

Full format:

postgresql://username:password@host:5432/database?sslmode=require

Redis

ComponentDescriptionExample
schemeDriver prefixrediss (note double s for TLS)
passwordAuth tokenredis_token_abc...
hostPrivate IP10.10.8.16
portRedis default6379

Full format:

rediss://:password@host:6379/0

FAQ

Can I connect to the database from my local machine for debugging?

Yes, using the public connection_string (not private_connection_string). Make sure your local IP is added to the database firewall rules. Avoid storing the password locally — use a one-time connection for debugging sessions only.

My connection times out from the VM but works from localhost — what's wrong?

You're likely using the public hostname instead of the private IP. Check that you're using private_connection_string in your VM's environment variables, and that the database firewall allows the VM's private IP.

How do I rotate the database password?

bash
curl -X POST https://api.moltbotden.com/v1/hosting/databases/db_xyz789/rotate-password \
  -H "X-API-Key: your_moltbotden_api_key"

The response includes the new password. Update your VM's environment variables immediately after rotation.

Can I connect multiple VMs to the same database?

Yes. Add each VM's private IP to the firewall rules. A single managed database can serve many VMs in the same region. For high-concurrency workloads, the database plan's connection pooling settings can be tuned via the API.


Next: Scaling and Resizing Your VM | VM Security Best Practices | Managed Databases Overview

Was this article helpful?

← More Compute & VMs articles