Skip to content

Managed Databases ​

Frostmoln runs managed PostgreSQL and MySQL instances for you — the platform provisions the virtual machine, attaches persistent storage, configures the server, and gives you a ready-to-use connection. You manage your data; we manage the infrastructure underneath.

Choosing a type and version ​

Pick PostgreSQL or MySQL at create time. Each type offers several versions, and the create dropdown is the source of truth for what you can launch right now — currently around PostgreSQL 14–18 and MySQL 8.0 / 8.4.

Every version carries a lifecycle status:

StatusLaunchable?Meaning
currentYesNewest fully-supported release
supportedYesFully supported
deprecatedYes (marked)Still launchable, shown with a (deprecated) marker — plan to move off it
eolNoEnd of life — hidden from the create dropdown

The recommended version for each type is marked (Recommended). End-of-life and preview versions are not offered for new instances.

Sizing ​

Instance sizes use the db.gp1.<size> flavor family (general-purpose):

FlavorvCPURAM
db.gp1.small14 GB
db.gp1.medium28 GB
db.gp1.large416 GB
db.gp1.xlarge832 GB
db.gp1.2xlarge1664 GB

You choose storage separately (20–1000 GB). The create dialog shows the estimated cost before you confirm.

Create an instance ​

In the portal, go to Databases → Instances → Create and choose the type, version, flavor, storage size, and the VPC + subnet the instance attaches to. Optionally enable High Availability for PostgreSQL.

From the CLI ​

Lifecycle commands are type-specific (fm database postgres … / fm database mysql …); the cross-type fm database … group lists across types.

bash
fm database postgres instance create \
  --name app-db \
  --version 16 \
  --flavor db.gp1.medium \
  --storage 100 \
  --vpc my-vpc \
  --subnet my-subnet

# MySQL is symmetric:
fm database mysql instance create --name app-db --version 8.4 --flavor db.gp1.small --storage 40 --vpc my-vpc --subnet my-subnet

# Cross-type views:
fm database instance list
fm database version list

With Terraform ​

hcl
resource "frostmoln_postgres_instance" "app" {
  name             = "app-db"
  version          = "16"
  flavor_id        = "db.gp1.medium"
  storage_gb       = 100
  vpc_id           = frostmoln_vpc.main.id
  subnet_id        = frostmoln_subnet.main.id
  ha_enabled       = true
}

Connect ​

After provisioning, the instance detail page reveals the connection details behind a Show Credentials button:

  • Host — the instance's private address
  • Port — 5432 (PostgreSQL) or 3306 (MySQL)
  • Username — pgadmin (PostgreSQL) or mysqladmin (MySQL)
  • Password — generated for you (rotatable)
  • a ready-to-use connection URI

Databases are reachable from instances on the same VPC; the create flow opens the database port in the instance's security group. Connect like any standard client:

bash
psql "host=<host> port=5432 user=pgadmin dbname=postgres"

TIP

Treat the admin credentials like any secret — store them in Secrets or your application's secret store, not in source control.

High availability ​

High availability runs a synchronous standby alongside the primary. While synchronous replication is armed, a commit the primary has acknowledged to your client is already on the standby's disk, so a failover loses nothing that was acknowledged.

  • PostgreSQL only. A MySQL instance cannot be created with high availability — the request is rejected.
  • Chosen at create time, and fixed for the life of the instance. High availability cannot be added to a running instance or removed from one; changing the setting afterwards is rejected. To move an existing database onto a highly available instance, create a new instance and migrate your data.
  • Protection is across hosts, not across availability zones. The pair shares the instance's VPC and subnet, and therefore one availability zone. The two nodes are normally placed on separate physical hosts; when host capacity is scarce the pair may share one, so host separation is best-effort rather than guaranteed. High availability across availability zones is not offered.

Enable it in the portal's create form, with --ha on the CLI, or with ha_enabled = true in Terraform.

HA status ​

A highly available instance reports an HA status on its detail page and in the instance list:

StatusMeaning
healthyA synchronous standby is streaming and protecting the primary's writes.
degradedOne node is serving and nothing is protecting its writes — the standby is unavailable, or is being rebuilt after a failover.
provisioningThe pair is still being built.
failing_overA failover is in progress.
no_standbyThe instance has no standby (see below).

What is guaranteed, and when ​

A commit is protected against a failover only if it was acknowledged while synchronous replication was armed. The HA status reports that state with a short delay — up to about a minute — so a status reading healthy is not by itself proof that the commit you just made is already on the standby.

If the standby stops streaming, writes block briefly and then continue asynchronously: the platform drops synchronous replication so the instance keeps accepting writes, and reports it as degraded. That is a deliberate trade — the database stays available, and the price is that commits acknowledged during the degraded window are not yet on the standby and can be lost if a failover happens before the standby catches up. Once the standby is back in sync the status returns to healthy and new commits are protected again.

WARNING

Watch the HA status for anything that must not lose a write. degraded is the window in which a failover can cost you acknowledged commits.

Failover ​

A failover is requested by you — from the portal, with the fm CLI, or through the API. The platform watches the pair continuously and reports its HA status; promoting the standby is a decision you make, not something the platform does on its own.

A failover is accepted only while the HA status is healthy — failing over to a standby that is not in sync would discard acknowledged writes. It also carries a budget: at most 2 failovers per instance per 24 hours, with at least 30 minutes between attempts. An instance that has failed over twice in a day has a fault that another failover does not fix.

Your connection endpoint address does not change. That is the whole point of the design — you do not re-point your application, resolve a new name, or update a connection string. Open connections are briefly interrupted and must be re-established, so your client or connection pool needs the usual reconnect logic; after that, everything connects to the same address again.

Instances created before high availability launched

A small number of instances were created with the high-availability flag set before a standby was actually provisioned for them. They report no_standby: there is one node, and no failover protection. no_standby does not resolve on its own, and high availability cannot be added afterwards — create a new instance with high availability and migrate to it.

Backups and restore ​

PostgreSQL and MySQL instances support scheduled and on-demand backups. They are opt-in: the portal's create form ticks Enable Automated Backups for you, but on the CLI you pass --backup and in Terraform you set backup_enabled = true — otherwise the instance is created without them. The schedule is a cron expression (0 2 * * * by default). Each backup is encrypted with your tenant's own backup key before it leaves the instance.

Retention is minimum 35 days, maximum 90. Backups are written to immutable, object-locked storage that holds every object for 35 days, so a shorter retention could not delete anything sooner — the API rejects it. Deleting a backup yourself takes effect for you immediately; the stored object is purged once its immutability window has passed.

In the portal, use the Backups tab on the instance. A backup can only be taken while the instance is running. Revoking your tenant's backup key (fm database backup-key revoke) disables backups and restores for every managed database in the tenant until you re-enable it.

Instances created before backups launched

The in-guest backup agent ships inside the instance image, so an instance created before managed backups went live does not carry it: a backup on such an instance stays in creating and never completes. Create a new instance to get backups.

bash
fm database postgres backup list <instance-id>
fm database postgres backup create <instance-id> --name pre-migration
fm database postgres backup delete <instance-id> <backup-id>
hcl
resource "frostmoln_postgres_instance" "app" {
  # … create arguments as above …
  backup_enabled        = true
  backup_schedule       = "0 2 * * *"
  backup_retention_days = 35
}

Restore ​

Restoring is non-destructive: it provisions a new instance from the source and leaves the source untouched. PostgreSQL instances can also restore to any second inside a restore window — see Point-in-time recovery.

bash
fm database postgres restore <instance-id> --backup <backup-id> --target-name app-db-restored

While a restore from an instance is still running — from a backup or to a point in time — the source cannot be deleted: deleting it would make the restore fail. The portal, fm and Terraform all get 409 restore_from_instance_in_progress. Wait until the restored instance is running (or error, if the restore failed), then delete the source.

Deleting an instance is permanent

Deleting an instance removes it and its storage volume. Take a backup first if you may need the data — and keep an independent logical dump somewhere durable such as object storage if you want a copy outside the platform:

bash
pg_dump   "host=<host> user=pgadmin dbname=app"  > app.sql   # PostgreSQL
mysqldump -h <host> -u mysqladmin app            > app.sql   # MySQL

Point-in-time recovery ​

Point-in-time recovery (PITR) restores a PostgreSQL instance to any second inside its restore window, not only to the instant a backup was taken. The platform archives the instance's write-ahead log continuously, so while archiving is running at most five minutes of writes are at risk (see When archiving pauses). Like every restore, it builds a new instance and leaves the source untouched.

"Restorable to" — the newest moment you can actually recover to — normally trails real time by a few minutes. On a lightly-written instance it can trail longer, because the platform only advances it once it can prove no later commit is missing. An idle instance can show no window at all after a base backup until it next receives a write.

PITR is PostgreSQL only and requires backups.

bash
# The window: "Restorable from" and "Restorable to"
fm database postgres instance get <instance-id>

fm database postgres restore <instance-id> \
  --to-time 2026-09-20T14:30:00Z --target-name app-db-recovered

Give exactly one of --backup and --to-time.

Aim inside the window, not at its edge

"Restorable from" is the later of your oldest usable base backup — the oldest one taken after any interruption in archiving — and the retention cut-off. Once retention is what bounds it, the value advances in real time, so a moment copied from instance get can already have fallen outside the window by the time the restore is submitted.

Which instances can use it ​

PITR is on by default for new PostgreSQL instances created with backups. A restore — from a backup or to a point in time — produces a PITR-capable instance too, as long as the source had backups on and was not highly available.

Whether an instance can ever do PITR is stamped when it is created, and that stamp is permanent:

  • Instances created before PITR launched cannot do it. Restoring one produces a new instance that can.
  • Instances created with high availability cannot do it, and cannot be given it later. HA and PITR are an either/or choice made at create time, and restoring does not change it — a restore copies the source's high availability, so the new instance cannot do PITR either. To move a highly available database onto PITR you must create a new instance without high availability and migrate your data.
  • An instance created with PITR switched off (--no-pitr on the CLI, pitr_enabled = false in Terraform) can never be given it. Restoring it produces a new instance that can.

Turning it off, and back on ​

On an instance that is PITR-capable you can switch PITR off later — in the portal, or with pitr_enabled = false in Terraform. (fm has no instance update command.) Switching it off — or turning backups off, which closes it the same way:

  • stops archiving within a few minutes, and the instance then keeps no restorable window — a point-in-time restore is refused;
  • keeps the base backups and archived log already taken, and they stay billed until retention expires and they are reaped. Retention is a minimum of 35 days in immutable storage, so turning PITR off does not stop the charge immediately;
  • starts a 24-hour cooldown. Turning PITR back on — or turning backups back on, which has the same effect — inside those 24 hours is refused, and the refusal names the time you may retry.

When you do turn it back on, the window starts again at the next base backup. It does not reach back across the interruption.

Base backups and the schedule ​

On a PITR instance the scheduled backup is a base backup: a physical copy that the archived log is replayed onto. It runs on your own backup schedule — there is no separate PITR cadence. On-demand backups you take yourself stay logical (full) and portable.

If you set no schedule, the platform picks one from the instance's storage size; larger volumes need longer between scheduled backups. If you set a schedule the platform cannot sustain at that size, the request is refused with a message naming the minimum interval for your size. Leave the portal's schedule field empty to mean "platform default for this size".

Resizing re-checks the schedule. If the platform chose the schedule, it re-picks one for the new size and reports it back. If you wrote the schedule yourself, a new size that no longer sustains it is refused — send a schedule that does in the same request (with Terraform; the portal's and fm's resize take only the size), and the resize and the new schedule take effect together.

A resize is refused while any backup is running. Wait for it to finish, then retry.

Restore time grows with the interval between base backups, because more log has to be replayed: roughly 16 seconds per GB to extract the base, plus roughly 2.3 hours for each busy day of log, before the time spent fetching it. A daily base is on the order of a couple of hours; a weekly one, on the order of 16 hours.

When archiving pauses ​

Each tenant has a cap on how many bytes of PITR data it may retain at once. When a tenant reaches it, the platform stops accepting new log segments for that tenant's instances, and the instance reports archiving as paused with the reason tenant_cap — fm database postgres instance get prints "Archiving paused", and the API and Terraform carry the reason itself. The platform can also pause archiving fleet-wide during an incident, which shows as platform_disabled.

A capped tenant has no restore window

While a tenant is over the cap, its PITR instances have no restore window at all, and a point-in-time restore is refused. The window does not come back until the tenant is back under the cap.

Scheduled base backups keep running meanwhile. Each is a full copy of the volume and is retained and billed like any other, but none of them re-forms the window while the cap binds. Older base backups expire on their own retention clock, so a capped tenant gets back under the cap without intervention only when that expiry outpaces the new bases.

To recover sooner, turn PITR off on the instance producing the most log, make its backup schedule less frequent, or ask support to raise your tenant's cap.

Base backups in the backup list ​

Base backups appear in the backup list with type base, next to your full logical backups. They are managed by the platform: you cannot delete one — the request is refused — and you cannot create one on demand. They are removed automatically once a newer base supersedes them and retention has passed; a base that never became usable expires on its own retention clock.

PITR storage — base backups and archived log — is metered as the same database_backup line as your other managed-database backups. Prices come from the platform's pricing catalog and are shown in the portal; this guide doesn't quote amounts. See Billing.

Errors you may see ​

CodeWhat it meansWhat to do
pitr_unavailable_for_instanceThis instance cannot do PITR. The reason says which: type (PostgreSQL only), ha (high availability), legacy (created before PITR, or created with it off), not_enabled (capable, but PITR or backups are currently off)For not_enabled, turn PITR and backups on; the window starts at the next base backup. For legacy, restore to a new instance. ha and type cannot be changed on an existing instance, and a restore copies both.
pitr_window_unavailableThere is no restore window — the first base backup is still running, a gap restarted it, or the tenant is over its capCheck the window again later. Don't retry on a timer.
pitr_out_of_windowThe moment you asked for is outside the window. The error carries the window's boundsPick a moment inside the bounds it reports, and submit again.
invalid_inputA backup and a point in time together, or neither; a base row given as a backup to restore from; PITR without backups; a schedule below the floor for the instance's sizeRead the message — it names which.
conflictbackup_running: a resize while a backup is running. pitr_cooldown: re-enabling inside the 24-hour cooldown, with the time you may retry. base_managed: deleting a base backupWait and retry, except for base_managed — base backups are not deletable.

With Terraform ​

hcl
resource "frostmoln_postgres_instance" "app" {
  # … create arguments as above …
  backup_enabled = true
  pitr_enabled   = true
  # backup_schedule left unset: the platform picks one for this storage size
}

Full attribute reference: frostmoln_postgres_instance.

  • Write pitr_enabled explicitly. The platform stamps it at create and the stamp is permanent, so an instance created without PITR can never be given it. Leave it out and you get whatever the platform resolves, which may be on — and billed.
  • The window attributes show as "known after apply" on any changing plan. latest_restorable_time advances with every archived segment, so it is never the same at plan time and apply time. That is by design — quote the values, don't pin to them.
  • restore_from is create-only. Pointing it at a different source or instant replaces the resource, which destroys the restored database. Adding it to an instance that already exists is refused, and importing a restored instance records nothing.
  • Removing restore_from is not a no-op. It is a single in-place update that clears the record Terraform kept. Nothing changes on the platform, and the next plan is clean.
  • The restore target is built from the source's own shape, so the source must be a PostgreSQL instance and version, flavor_id, ha_enabled, vpc_id and subnet_id must equal the source's. storage_gb may be larger, never smaller. Terraform reads the source first and refuses with the mismatch named, rather than quietly creating an empty database instead.

After a failed restore, refresh before you read the plan

A restore first creates the new instance, then applies the remaining differences (storage, backups, schedule, pitr_enabled) as one update. If that update fails, Terraform's state records what the plan asked for, not what the platform actually has — and the resource is deliberately not tainted, because tainting it would destroy the restored database on the next apply.

The divergence only surfaces on a refresh. terraform plan -refresh=false shows no difference at all. Run terraform plan (or terraform refresh) and apply the in-place update it then reports.

PostgreSQL extensions ​

PostgreSQL instances can enable extensions from the platform's extension catalog — TimescaleDB, pg_stat_statements, pg_trgm and pgcrypto today, more as the catalog grows. The preload-required ones (TimescaleDB, pg_stat_statements) pay a restart window, and the platform runs one operation at a time. See PostgreSQL extensions.

Stop, start and restart ​

bash
fm database postgres instance stop    <instance-id>
fm database postgres instance start   <instance-id>
fm database postgres instance restart <instance-id>
  • Stop shuts the server down cleanly. The instance shows stopping, then stopped. Compute is not charged while it is stopped; point-in-time recovery storage is.
  • Start boots a stopped instance: starting, then running, at the same address with the same data.
  • Restart reboots a running instance: restarting, then running. Every connection is dropped while it reboots; clients should reconnect.
  • Each takes a minute or two. fm waits for the instance to settle; pass --no-wait to return as soon as the action is accepted. If an action fails, the instance shows error. Contact support, who can retry it.
  • High availability: a high-availability instance cannot be stopped yet (restart works). Nothing can be stopped or restarted during a failover, a running backup, or a credential rotation. Wait for it to finish, then retry.
  • Read replicas keep running while their primary is stopped, and catch up when it starts again.

Lifecycle and limits ​

  • Versions are fixed for the life of an instance — in-place upgrades are not yet available. To move to a newer major version, create a new instance on that version and migrate your data (e.g. with pg_dump / mysqldump).
  • High availability is fixed for the life of an instance — it is chosen at create time and cannot be added or removed later (see above).
  • Deletion is permanent and removes the storage volume (see above).
  • An instance lives in one VPC/subnet (one region).