DevelopersManaged databases

Managed databases

Managed databases run on Hostwolf database servers, separate from your app servers. Your apps reach them over TLS, so every copy of an app, on any server and in any city, uses the same data. That is what lets the app tier scale out.

  • Engines: PostgreSQL, MariaDB (MySQL-compatible) and Redis.
  • Security: TLS is required, and a database only accepts connections from your team's servers.
  • Connection pooling: PostgreSQL and MariaDB come with a pooler, so thousands of app connections share a few database connections.
  • Storage: grows by itself; storage above the plan is billed per GB.
  • Backups: nightly, kept for 7 days, stored off-site. PostgreSQL also has point-in-time recovery for the last 7 days.
  • Deleting: billing stops at once; support can restore a deleted database for 7 days.
  • Billing: hourly (the monthly price ÷ 730), on your next server invoice. You need an active server subscription first.

Plans and prices#

hw $API/managed-databases/catalog | jq
PlanMemoryStorageApp connections (pooled)Direct connectionsServer
small256 MB5 GB1,00010shared
medium512 MB10 GB2,00040shared
large4 GB80 GB5,000100dedicated
xlarge12 GB200 GB10,000200dedicated, 8 vCPU

The catalog lists the regions (for example vultr:ams for Amsterdam) with the monthly and hourly price of each plan, in USD, and the limits above (pooled_clients, direct_connections, pool_size). Pick the region closest to your app servers. Prices on 11 October 2026:

PlanAmsterdam (vultr:ams)Newark (vultr:ewr)
small$3.88/month$3.88/month
medium$7.74/month$7.74/month
large$62.13/month$62.13/month
xlarge$229.24/monthnot sold

Create and connect#

DB=$(hw -X POST $API/managed-databases \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"engine": "postgres", "plan": "small", "region": "vultr:ams"}' | jq -r .uuid)

# A few minutes later: status goes creating → ready
hw $API/managed-databases/$DB | jq -r .status

# Save the connection URLs on an app, then redeploy it
hw -X POST $API/managed-databases/$DB/attach -d "{\"application_uuid\": \"$APP\"}"
hw -X POST $API/applications/$APP/start
  • attach saves the pooled URL as DATABASE_URL and the direct URL as DATABASE_DIRECT_URL on the app (Redis: REDIS_URL). The URLs never pass through your script.
  • attach never replaces a variable silently. When the app already has DATABASE_URL (or REDIS_URL) with another value, nothing is saved and the answer is 409 with conflicts and a free suggested_env_key. Send "env_key": "DATABASE_URL_2" to save under that name (the direct URL becomes DATABASE_DIRECT_URL_2), or "overwrite": true to replace them.
  • The Idempotency-Key header (a UUID) makes the call safe to retry: the same key never creates a second database. Using a key again with different settings answers 409.
  • Database actions are limited to 10 a minute.

Pooled and direct URLs#

Every PostgreSQL and MariaDB database has two URLs to the same data:

Pooled (url, DATABASE_URL)Direct (direct_url, DATABASE_DIRECT_URL)
PostgreSQLport 6432, PgBouncer in transaction modeport 5432
MariaDBport 6033, ProxySQLport 3306
Connectionsup to the plan's pooled limit; above the pool they wait (up to 120 s) instead of failingthe plan's direct limit (10 on small)
Use it foryour app: web requests, workers, every copy of the appmigrations, LISTEN/NOTIFY, session settings (SET), advisory locks, temporary tables and long transactions

Use the pooled URL in your app. With the direct URL, keep the app's pool within the plan's direct limit, counting every copy of the app. Prepared statements work through the pooler. Run migrations with the direct URL, for example DATABASE_URL=$DATABASE_DIRECT_URL npx prisma migrate deploy as the post-deployment command (app settings, Deployment lifecycle). The pre-deployment command runs in the copy that is still running, before the new build starts: it would run the previous release's migrations, and it is skipped on the first deploy.

To read the URLs yourself, the token needs Read sensitive data:

hw $API/managed-databases/$DB/connection | jq
# {"env_key": "DATABASE_URL", "url": "postgresql://…@db-….db.app.hostwolf.net:6432/…?sslmode=require",
#  "direct_url": "postgresql://…@db-….db.app.hostwolf.net:5432/…?sslmode=require", "direct_connections": 10}
EngineURL format
PostgreSQLpostgresql://user:password@host:port/db?sslmode=require
MariaDBmysql://user:password@host:port/db?ssl-mode=REQUIRED
Redisrediss://user:password@host:port/0

The host name (db-<id>.db.app.hostwolf.net) stays the same through plan upgrades and moves. Certificates are public (Let's Encrypt), so no CA file is needed: sslmode=verify-full and ssl-mode=VERIFY_IDENTITY work with your system's certificate store, on the pooled and the direct port. Connections from outside your team's servers, including your own computer, are refused. To inspect data, use a shell on one of your servers (the console's Terminal) or a tool running in one of your apps.

Storage that grows#

The database server's disk grows by itself before it fills up, without a restart. Your plan includes its storage (5, 10, 80 or 200 GB). Above that, each whole GB is billed by the hour: $0.13 a month for 1 GB, $1.24 for 10 GB, $12.38 for 100 GB. extra_storage on the database shows the current extra GB and price, and metrics shows its size.

Upgrading a plan#

When a database is busier than its plan in 3 of 4 hours (CPU, connections, or a full Redis), advice says which plan fits and the team admins get an email. Nothing changes until you upgrade. upgrades lists the plans it can change to now:

hw $API/managed-databases/$DB | jq '.upgrades, .advice'
# [{"plan": "medium", "how": "in_place"}, {"plan": "large", "how": "move"}]

hw -X POST $API/managed-databases/$DB/upgrade -d '{"plan": "large"}'
# 422 {"message": "The large plan costs $62.13/month (now $3.88), billed hourly from the change. … Send \"confirm\": true to go ahead.", "confirm_required": true, "quote": {…}}

hw -X POST $API/managed-databases/$DB/upgrade -d '{"plan": "large", "confirm": true}'   # 202
  • in_place: small → medium keeps running. large → xlarge moves the disk to a bigger server, with about a minute of downtime.
  • move: the database is copied to another server. It is unavailable during the copy (about 20 seconds for 200,000 rows), then the host name points to the new server.
  • The new price applies from the change. Plans never go down.

Redis memory#

A Redis database holds at most its plan's memory (256 MB, 512 MB, 4 GB or 12 GB). The eviction policy decides what happens when it is full:

PolicyWhen memory is fullUse it for
allkeys-lruRemoves the least recently used keyscaches
noeviction (default)Refuses writes with an error; reads keep workingqueues, sessions, data that must not disappear
volatile-lru, allkeys-lfu, volatile-lfu, allkeys-random, volatile-random, volatile-ttlThe other Redis policiesspecial cases

Choose it when you create the database ("eviction_policy": "allkeys-lru"), or change it at any time; it applies at once, without a restart:

hw -X PATCH $API/managed-databases/$DB -d '{"eviction_policy": "allkeys-lru"}'

metrics shows the memory used, the maximum and the keys evicted so far.

Backups#

hw -X POST $API/managed-databases/$DB/backups   # an extra backup now (202)

Nightly backups run anyway. In the console, Restore points on the database lists them.

PostgreSQL can be restored to any minute of the last 7 days from the console: the restore makes a new database as it was at that moment, with its own URL, billed like any database. The current database keeps running. To restore a MariaDB or Redis backup, write to support with the database uuid and the time.

Deleting#

hw -X DELETE $API/managed-databases/$DB

Billing stops right away. Apps that used it keep the old DATABASE_URL until you change it.

Errors you may see#

StatusMeaning
403Verify your email address in the console first, or the token lacks a permission.
409The database isn't ready yet, or the Idempotency-Key was used for a different database.
422Unknown engine, plan or region, no active server subscription, an upgrade it can't take now, or confirm missing.
503Managed databases are briefly unavailable. Nothing was changed; try again.
Stuck, or missing an endpoint? Tell us at app.hostwolf.net/contact or [email protected].