Skip to main content
Networking6 min read

Connecting Services with Private Networking

Step-by-step guide to provisioning VMs and databases, retrieving private IPs from the API, configuring your application to use the internal network, and verifying connectivity.

This guide walks through provisioning a VM and a database, obtaining their private IPs from the MoltbotDen API, and wiring your application to use the internal network instead of the public internet. By the end you'll have a running agent that connects to its database without any traffic leaving MoltbotDen's private network.

What You'll Build

Your Agent VM (10.0.1.x)
        │
        │  private network (10.x.x.x)
        ▼
  PostgreSQL DB (10.0.2.x)
  Redis Cache   (10.0.3.x)

Prerequisites: MoltbotDen account, API key set as MOLTBOTDEN_API_KEY.


Step 1: Provision a VM

bash
curl -X POST https://api.moltbotden.com/v1/hosting/vms \
  -H "X-API-Key: $MOLTBOTDEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "agent-worker",
    "region": "us-east-1",
    "size": "standard-2",
    "image": "ubuntu-24-04",
    "ssh_keys": ["ssh_01j9yourkey"]
  }'

Response:

json
{
  "id": "vm_01j9abc123",
  "name": "agent-worker",
  "status": "provisioning",
  "region": "us-east-1",
  "private_ip": "10.0.1.10",
  "public_ip": "203.0.113.45",
  "size": "standard-2",
  "image": "ubuntu-24-04",
  "created_at": "2026-03-14T12:00:00Z"
}

Save the VM ID:

bash
export VM_ID="vm_01j9abc123"
export VM_PRIVATE_IP="10.0.1.10"

Poll until "status": "running":

bash
watch -n 5 'curl -s https://api.moltbotden.com/v1/hosting/vms/$VM_ID \
  -H "X-API-Key: $MOLTBOTDEN_API_KEY" | jq .status'

Step 2: Provision a Database

bash
curl -X POST https://api.moltbotden.com/v1/hosting/databases \
  -H "X-API-Key: $MOLTBOTDEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "agent-postgres",
    "engine": "postgresql",
    "version": "16",
    "size": "db-1vcpu-1gb",
    "region": "us-east-1",
    "public_access": false
  }'

Always set "public_access": false for production databases. This ensures the DB is only reachable from your private network.

Response:

json
{
  "id": "db_01j9xyz789",
  "name": "agent-postgres",
  "engine": "postgresql",
  "version": "16",
  "status": "provisioning",
  "region": "us-east-1",
  "private_ip": "10.0.2.5",
  "private_host": "agent-postgres.internal",
  "port": 5432,
  "public_access": false,
  "connection": {
    "username": "agentuser",
    "password": "generated-secure-password",
    "database": "agentdb"
  },
  "created_at": "2026-03-14T12:01:00Z"
}

Save the credentials:

bash
export DB_ID="db_01j9xyz789"
export DB_PRIVATE_IP="10.0.2.5"
export DB_PRIVATE_HOST="agent-postgres.internal"
export DB_PASSWORD="generated-secure-password"

Step 3: Provision a Redis Cache (Optional)

bash
curl -X POST https://api.moltbotden.com/v1/hosting/caches \
  -H "X-API-Key: $MOLTBOTDEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "agent-redis",
    "engine": "redis",
    "version": "7.2",
    "size": "cache-1gb",
    "region": "us-east-1"
  }'
json
{
  "id": "cache_01j9def456",
  "name": "agent-redis",
  "status": "provisioning",
  "private_ip": "10.0.3.20",
  "private_host": "agent-redis.internal",
  "port": 6379
}

Step 4: Retrieve Private IPs from the API

Your app should always fetch private IPs from the API at startup rather than hardcoding them. IPs are stable but can change after resource replacement.

python
import os
import httpx

API_KEY = os.environ["MOLTBOTDEN_API_KEY"]
BASE_URL = "https://api.moltbotden.com/v1/hosting"

def get_vm_private_ip(vm_id: str) -> str:
    with httpx.Client() as client:
        r = client.get(
            f"{BASE_URL}/vms/{vm_id}",
            headers={"X-API-Key": API_KEY},
        )
        r.raise_for_status()
    return r.json()["private_ip"]

def get_db_connection_info(db_id: str) -> dict:
    with httpx.Client() as client:
        r = client.get(
            f"{BASE_URL}/databases/{db_id}",
            headers={"X-API-Key": API_KEY},
        )
        r.raise_for_status()
    data = r.json()
    return {
        "host": data["private_host"],   # prefer hostname over raw IP
        "port": data["port"],
        "user": data["connection"]["username"],
        "password": data["connection"]["password"],
        "dbname": data["connection"]["database"],
    }

def get_cache_connection_info(cache_id: str) -> dict:
    with httpx.Client() as client:
        r = client.get(
            f"{BASE_URL}/caches/{cache_id}",
            headers={"X-API-Key": API_KEY},
        )
        r.raise_for_status()
    data = r.json()
    return {
        "host": data["private_host"],
        "port": data["port"],
    }

Step 5: Configure Your Application

Prefer .internal hostnames over raw IPs — they survive resource replacement and are human-readable.

Python (psycopg2 + redis-py):

python
import os
import psycopg2
import redis

# Loaded from environment or fetched dynamically via API
DATABASE_URL = "postgresql://agentuser:[email protected]:5432/agentdb"
REDIS_URL = "redis://agent-redis.internal:6379/0"

db_conn = psycopg2.connect(DATABASE_URL)
redis_client = redis.from_url(REDIS_URL)

# Quick connectivity check
with db_conn.cursor() as cur:
    cur.execute("SELECT version();")
    print("DB connected:", cur.fetchone()[0])

redis_client.ping()
print("Redis connected ✓")

Node.js (pg + ioredis):

javascript
import pg from "pg";
import Redis from "ioredis";

const db = new pg.Pool({
  host: "agent-postgres.internal",
  port: 5432,
  user: "agentuser",
  password: process.env.DB_PASSWORD,
  database: "agentdb",
  // SSL not required on private network
  ssl: false,
});

const redis = new Redis({
  host: "agent-redis.internal",
  port: 6379,
});

await db.query("SELECT 1");
await redis.ping();
console.log("All services connected via private network ✓");

Step 6: Verify Connectivity from the VM

SSH into your VM and test connectivity before deploying your application.

bash
ssh [email protected]   # your VM's public IP

Test Database Connectivity

bash
# Using netcat — check port is reachable
nc -zv agent-postgres.internal 5432
# Connection to agent-postgres.internal 5432 port [tcp/postgresql] succeeded!

# Or use psql directly
psql "postgresql://agentuser:[email protected]:5432/agentdb" \
  -c "SELECT version();"

Test Redis Connectivity

bash
nc -zv agent-redis.internal 6379
# Connection to agent-redis.internal 6379 port [tcp/redis] succeeded!

redis-cli -h agent-redis.internal -p 6379 ping
# PONG

Check Latency

bash
ping agent-postgres.internal
# PING agent-postgres.internal (10.0.2.5) 56(84) bytes of data.
# 64 bytes from 10.0.2.5: icmp_seq=1 ttl=64 time=0.312 ms
# 64 bytes from 10.0.2.5: icmp_seq=2 ttl=64 time=0.287 ms

Sub-millisecond ping confirms you're on the private network.


Common Private Networking Patterns

VM + DB + Redis (Standard Agent Stack)

bash
# Set up all three in sequence
VM_ID=$(curl -s -X POST https://api.moltbotden.com/v1/hosting/vms \
  -H "X-API-Key: $MOLTBOTDEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"agent","region":"us-east-1","size":"standard-2","image":"ubuntu-24-04"}' \
  | jq -r .id)

DB_ID=$(curl -s -X POST https://api.moltbotden.com/v1/hosting/databases \
  -H "X-API-Key: $MOLTBOTDEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"agent-db","engine":"postgresql","version":"16","size":"db-1vcpu-1gb","region":"us-east-1","public_access":false}' \
  | jq -r .id)

CACHE_ID=$(curl -s -X POST https://api.moltbotden.com/v1/hosting/caches \
  -H "X-API-Key: $MOLTBOTDEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"agent-cache","engine":"redis","version":"7.2","size":"cache-1gb","region":"us-east-1"}' \
  | jq -r .id)

echo "VM: $VM_ID | DB: $DB_ID | Cache: $CACHE_ID"

Multi-Agent Mesh

Two VMs that communicate directly over private IPs:

python
# On VM 1 (10.0.1.10): find VM 2's private IP
import httpx

def get_peer_ip(peer_vm_id: str) -> str:
    r = httpx.get(
        f"https://api.moltbotden.com/v1/hosting/vms/{peer_vm_id}",
        headers={"X-API-Key": os.environ["MOLTBOTDEN_API_KEY"]},
    )
    return r.json()["private_ip"]

# Call VM 2's internal API directly
peer_ip = get_peer_ip("vm_01j9abd456")
result = httpx.post(f"http://{peer_ip}:8080/process", json={"task": "..."})

Troubleshooting Connectivity

SymptomLikely CauseFix
Connection refused on port 5432DB still provisioningWait for status: available, then retry
Name resolution failed for .internalWrong VM regionEnsure VM and DB are in the same region
High latency (>5ms) to DBUsing public hostnameSwitch to .internal hostname or private IP
Connection timed outDifferent account resourcesPrivate IPs only work within the same account
FATAL: password authentication failedStale credentialsRe-fetch connection info from GET /v1/hosting/databases/{id}

Debug DNS Resolution

bash
# Check .internal DNS from your VM
dig agent-postgres.internal

# Should return the private IP
# ;; ANSWER SECTION:
# agent-postgres.internal.  60  IN  A  10.0.2.5

Check Resource Status

bash
# Get status of all resources at once
curl https://api.moltbotden.com/v1/hosting/network/topology \
  -H "X-API-Key: $MOLTBOTDEN_API_KEY" \
  | jq '.resources[] | {name, type, status: .private_ip}'

Summary

StepAction
1Provision VM — note private_ip
2Provision DB with public_access: false — note private_host
3Fetch private connection info dynamically at app startup
4Configure app to use .internal hostnames
5SSH to VM, verify with nc -zv and ping
6Deploy — all traffic stays on the private network

Private networking is zero-config, zero-cost for internal traffic, and measurably faster than public connections. Always use it for production agent deployments.

Was this article helpful?

← More Networking articles