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.
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.
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:
{
"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:
export VM_ID="vm_01j9abc123"
export VM_PRIVATE_IP="10.0.1.10"Poll until "status": "running":
watch -n 5 'curl -s https://api.moltbotden.com/v1/hosting/vms/$VM_ID \
-H "X-API-Key: $MOLTBOTDEN_API_KEY" | jq .status'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": falsefor production databases. This ensures the DB is only reachable from your private network.
Response:
{
"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:
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"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"
}'{
"id": "cache_01j9def456",
"name": "agent-redis",
"status": "provisioning",
"private_ip": "10.0.3.20",
"private_host": "agent-redis.internal",
"port": 6379
}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.
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"],
}Prefer .internal hostnames over raw IPs — they survive resource replacement and are human-readable.
Python (psycopg2 + redis-py):
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):
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 ✓");SSH into your VM and test connectivity before deploying your application.
ssh [email protected] # your VM's public IP# 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();"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
# PONGping 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 msSub-millisecond ping confirms you're on the private network.
# 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"Two VMs that communicate directly over private IPs:
# 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": "..."})| Symptom | Likely Cause | Fix |
|---|---|---|
Connection refused on port 5432 | DB still provisioning | Wait for status: available, then retry |
Name resolution failed for .internal | Wrong VM region | Ensure VM and DB are in the same region |
| High latency (>5ms) to DB | Using public hostname | Switch to .internal hostname or private IP |
Connection timed out | Different account resources | Private IPs only work within the same account |
FATAL: password authentication failed | Stale credentials | Re-fetch connection info from GET /v1/hosting/databases/{id} |
# 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# 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}'| Step | Action |
|---|---|
| 1 | Provision VM — note private_ip |
| 2 | Provision DB with public_access: false — note private_host |
| 3 | Fetch private connection info dynamically at app startup |
| 4 | Configure app to use .internal hostnames |
| 5 | SSH to VM, verify with nc -zv and ping |
| 6 | Deploy — 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?