Skip to content

RDS

Protocol: Query (XML) for management API + PostgreSQL / MySQL wire protocol for data plane Management Endpoint: POST http://localhost:4566/ Data Endpoint: localhost:<proxy-port> (TCP)

Floci manages real PostgreSQL, MySQL, and MariaDB Docker containers and proxies TCP connections to them, including IAM authentication support.

RDS Data API (rds-data) is documented separately because it uses REST JSON routes instead of the RDS Query protocol. See RDS Data API.

Supported Management Actions

Action Description
CreateDBInstance Start a new database instance
DescribeDBInstances List instances and their connection info — the list form includes DocumentDB and Neptune instances and takes an engine filter
DeleteDBInstance Stop and remove an instance
ModifyDBInstance Update instance settings
RebootDBInstance Restart a database instance
DescribeOrderableDBInstanceOptions List deterministic instance class options
DescribeEvents -
CreateDBSubnetGroup Create a DB subnet group; tags given here are readable through ListTagsForResource
DescribeDBSubnetGroups List DB subnet groups
ModifyDBSubnetGroup Update DB subnet group description and subnet list
DeleteDBSubnetGroup Delete a DB subnet group
CreateDBCluster Create an Aurora-compatible cluster
DescribeDBClusters List clusters — the list form covers the RDS family, DocumentDB and Neptune clusters included, and takes an engine filter
DeleteDBCluster Delete a cluster
ModifyDBCluster Update cluster settings
CreateDBParameterGroup Create a parameter group
DescribeDBParameterGroups List parameter groups
DeleteDBParameterGroup Delete a parameter group
ModifyDBParameterGroup Update parameter group settings
DescribeDBParameters List parameters in a group
CreateDBClusterParameterGroup Create an Aurora-compatible cluster parameter group
DescribeDBClusterParameterGroups List cluster parameter groups
DeleteDBClusterParameterGroup Delete a cluster parameter group
ModifyDBClusterParameterGroup Update cluster parameter group settings
DescribeDBClusterParameters List parameters in a cluster group
CreateOptionGroup Create an option group
DescribeOptionGroups List option groups, including the implicit default: groups
ModifyOptionGroup Add, update, or remove options in an option group
DeleteOptionGroup Delete an option group
CreateDBSnapshot Create a snapshot of a DB instance
RestoreDBInstanceFromDBSnapshot Create a new DB instance from a snapshot
DescribeDBSnapshots List DB instance snapshots
DescribeDBSnapshotAttributes Return a snapshot's restore attribute (accounts authorized to copy/restore it)
ModifyDBSnapshotAttribute Add or remove accounts authorized to copy/restore a snapshot
DescribeDBProxies List DB proxies
CreateDBProxy Create a DB proxy
ModifyDBProxy Update mutable DB proxy authentication, logging, timeout, TLS, role, and security-group settings
DeleteDBProxy Delete a DB proxy
RegisterDBProxyTargets Register a cluster or instance as a proxy target
DeregisterDBProxyTargets Remove a cluster or instance from a proxy target group
DescribeDBProxyTargetGroups List a proxy's target groups
ModifyDBProxyTargetGroup Update target-group connection-pool configuration
DescribeDBProxyTargets List a proxy target group's registered targets
DescribeDBClusterSnapshots Return an empty cluster-snapshot list (snapshots are not modeled)
DescribeGlobalClusters List global clusters — always empty, as none are modeled
AddTagsToResource Add tags to a DB resource
ListTagsForResource List tags for a DB resource
RemoveTagsFromResource Remove tags from a DB resource

CreateDBInstance stores StorageEncrypted, KmsKeyId, BackupRetentionPeriod, PreferredBackupWindow, PreferredMaintenanceWindow and CopyTagsToSnapshot, and DescribeDBInstances returns them; ModifyDBInstance changes the backup settings and the windows. The same checks as on AWS apply (KmsKeyId needs StorageEncrypted, windows are at least 30 minutes and may not overlap). KmsKeyId is accepted as a key ARN, key id, alias ARN or alias name, resolved against the KMS store in the request's region and returned as the key ARN; a key that does not exist or is not enabled is KMSKeyNotAccessibleFault. Where AWS picks a random window, Floci uses 04:00-06:00 and mon:00:00-mon:03:00 (or, when the window given on create overlaps the usual default, a 30-minute window starting where the given one ends); a window given on modify is checked against the instance's other window. Modifications apply immediately — PendingModifiedValues is not modeled.

DB snapshot tagging and lifecycle

CreateDBSnapshot accepts Tags, and TagResource/UntagResource/ListTagsForResource work against a snapshot's ARN like they do for other tagged resource types. DescribeDBSnapshotAttributes/ModifyDBSnapshotAttribute are modeled as plain in-memory state (no real cross-account sharing). DeleteDBSnapshot is not implemented, so a snapshot persists for the life of the account; Terraform's aws_db_snapshot can be created but not destroyed. Snapshots are region-scoped like DB instances and clusters: DBSnapshotArn reflects the request's signed region, and a snapshot is only visible to Describe/Tag calls signed for that same region. Aurora cluster snapshots and RDS reserved instances aren't modeled at all (DescribeDBClusterSnapshots always returns an empty list, and there's no reserved-instance API), so tagging doesn't apply to either.

Configuration

Variable Default Description
FLOCI_SERVICES_RDS_ENABLED true Enable or disable the service
FLOCI_SERVICES_RDS_MOCK false true = metadata only (no Docker container or auth proxy)
FLOCI_SERVICES_RDS_PROXY_BASE_PORT 7001 First host port in the RDS proxy range
FLOCI_SERVICES_RDS_PROXY_MAX_PORT 7099 Last host port in the RDS proxy range
FLOCI_SERVICES_RDS_ENDPOINT_HOST (auto-detected) Hostname advertised in RDS endpoints; when set in Docker, Floci advertises each proxy's published host port
FLOCI_SERVICES_RDS_DEFAULT_POSTGRES_IMAGE postgres:16-alpine Docker image for PostgreSQL instances
FLOCI_SERVICES_RDS_DEFAULT_MYSQL_IMAGE mysql:8.0 Docker image for MySQL instances
FLOCI_SERVICES_RDS_DEFAULT_MARIADB_IMAGE mariadb:11 Docker image for MariaDB instances
FLOCI_SERVICES_RDS_PROXY_HANDSHAKE_TIMEOUT_MILLIS 10000 Max time a client has to complete the startup/auth handshake before the proxy drops it
FLOCI_SERVICES_RDS_PROXY_BACKEND_CONNECT_TIMEOUT_MILLIS 5000 Max time the proxy waits for the backend TCP connect
FLOCI_SERVICES_RDS_PROXY_MAX_CONNECTIONS 100 Max concurrent connections per proxy before new ones are refused

Docker Compose

RDS requires the Docker socket and port range exposure. For private registry authentication and other Docker settings see Docker Configuration.

When Docker publishes RDS proxy ports dynamically, set FLOCI_SERVICES_RDS_ENDPOINT_HOST to the hostname used by clients. Floci inspects its own container through the Docker socket and returns the corresponding published port from DescribeDBInstances and DescribeDBClusters. Leave the setting unset to retain the auto-detected endpoint host and configured proxy port.

services:
  floci:
    image: floci/floci:latest
    ports:
      - "4566:4566"
      - "7001-7099:7001-7099"   # RDS proxy ports
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      FLOCI_SERVICES_DOCKER_NETWORK: my-project_default
      FLOCI_SERVICES_RDS_PROXY_BASE_PORT: "7001"

Without a reachable Docker daemon

A DB instance or cluster record is metadata. Its identifier, ARN, endpoint address and tags come from Floci's configuration, not from Docker. When no daemon is reachable, because Floci runs inside Docker with no socket mounted or the daemon on the host is stopped, CreateDBInstance and CreateDBCluster still succeed and the resource reaches available. DescribeDBInstances, ModifyDBInstance, the tagging APIs and DeleteDBInstance all work on that record, and Floci logs a warning naming the missing daemon.

Nothing listens behind the endpoint in that state. The backing container is retried by every operation that needs the live database, so it starts as soon as a daemon becomes reachable. Until then, RDS Data API calls fail with a modelled InternalServerErrorException that names the missing daemon. A daemon that is reachable but cannot start the container still fails CreateDBInstance outright, since that is a real error rather than a degraded mode.

Mock mode (CI / tests)

Set FLOCI_SERVICES_RDS_MOCK=true when you only need the management API shape: clusters and instances are registered as available immediately, with no Docker container or auth proxy behind them. Each resource still gets a unique endpoint port, but nothing listens on it.

# docker-compose.yml — CI / test environment
services:
  floci:
    image: floci/floci:latest
    environment:
      FLOCI_SERVICES_RDS_MOCK: "true"

Switching modes over persisted state

With a persistent storage mode, changing FLOCI_SERVICES_RDS_MOCK between restarts is best-effort, as with the other mock-capable services: resources created in real mode and deleted under mock leave their containers and volumes behind, and resources created in mock mode are restored with fresh, empty containers when loaded in real mode.

DB proxy endpoint routing

DB proxy control-plane resources and target registration are modeled, but Floci's current single-host TCP relay cannot expose multiple same-engine DB proxies as distinct AWS-style bare hostnames on the same engine-default port. The standard Docker Compose mapping also exposes only the 7001-7099 instance/cluster proxy range, not 1433, 3306, or 5432. Use mock mode for DB proxy provisioning workflows until a dedicated endpoint-routing design is implemented.

DB proxy control-plane settings

Proxy and target-group settings are persisted and round-trip through the RDS Query API and CloudFormation. Pool sizing, borrow timeout, idle timeout, TLS, init-query, and session-pinning settings are currently control-plane metadata; the TCP relay does not yet implement those data-plane behaviors. DefaultAuthScheme=IAM_AUTH is supported for control-plane workflows, but a real-mode proxy using that scheme cannot register a target until backend IAM authentication is implemented. Requests to RegisterDBProxyTargets, DeregisterDBProxyTargets, and DescribeDBProxyTargets use the default target group when TargetGroupName is omitted, matching the RDS API contract. CreateDBProxy/AWS::RDS::DBProxy accept EndpointNetworkType (IPV4, IPV6, or DUAL) and TargetConnectionNetworkType (IPV4 or IPV6) and round-trip them like the other proxy settings above; the TCP relay itself still only listens on IPv4, so a non-IPV4 value is accepted as control-plane metadata rather than making the relay dual-stack. A non-IPV4 value is rejected with InvalidParameterValue unless the proxy's VPC and every subnet in VpcSubnetIds already carry an associated IPv6 CIDR block, matching AWS's own network prerequisites for RDS Proxy.

Aurora Serverless v2 scaling

CreateDBCluster, ModifyDBCluster, and DescribeDBClusters support the AWS ServerlessV2ScalingConfiguration Query shape for aurora-mysql and aurora-postgresql clusters. Requests that apply this configuration to another engine fail with InvalidParameterCombination.

The minimum and maximum capacities use half-ACU increments; the maximum must be at least 1 ACU and no greater than 256 ACUs. AWS's actual maximum depends on the Aurora engine and platform version, while Floci currently applies the 256-ACU ceiling uniformly.

When MinCapacity is zero, SecondsUntilAutoPause accepts 300–86,400 seconds and defaults to 300. Changing the minimum to a nonzero value removes the auto-pause interval, matching the AWS response shape. AWS limits zero-capacity auto-pause to compatible Aurora versions; Floci does not currently enforce that version matrix. ModifyDBCluster accepts partial scaling updates and preserves omitted values. AWS::RDS::DBCluster creation also maps the equivalent CloudFormation property.

If a persisted cluster record does not contain its original AWS engine identifier, Floci rejects a new scaling configuration instead of assuming that the cluster is Aurora.

This is control-plane compatibility: Floci persists and returns the scaling configuration, but it does not resize or automatically pause the backing Docker container.

Examples

export AWS_ENDPOINT_URL=http://localhost:4566

# Create a PostgreSQL instance
aws rds create-db-instance \
  --db-instance-identifier mypostgres \
  --db-instance-class db.t3.micro \
  --engine postgres \
  --master-username admin \
  --master-user-password secret123 \
  --allocated-storage 20 \
  --endpoint-url $AWS_ENDPOINT_URL

# Get connection details
aws rds describe-db-instances \
  --db-instance-identifier mypostgres \
  --query 'DBInstances[0].Endpoint' \
  --endpoint-url $AWS_ENDPOINT_URL

# Connect with psql (use the port returned above)
psql -h localhost -p 7001 -U admin

# Create a MySQL instance
aws rds create-db-instance \
  --db-instance-identifier mymysql \
  --db-instance-class db.t3.micro \
  --engine mysql \
  --master-username root \
  --master-user-password secret123 \
  --allocated-storage 20 \
  --endpoint-url $AWS_ENDPOINT_URL

# Connect with mysql client
mysql -h 127.0.0.1 -P 7002 -u root -psecret123

Supported Engines

Engine Default image
postgres postgres:16-alpine
mysql mysql:8.0
mariadb mariadb:11

Override the image per-instance with the --engine-version flag or globally via environment variables. Aurora MySQL versions such as 8.0.mysql_aurora.3.08.0 use the MySQL version in front, so that example runs mysql:8.0.

Option Groups

Option groups are metadata: Floci stores the options you add, returns them on the wire, and attaches a group to a DB instance, but it does not install the underlying engine feature in the container.

As on AWS, every engine has an implicit default:<engine>-<major version> group that DescribeOptionGroups returns even when you have created none. Floci ships the defaults for the engines it can run (postgres 1318, mysql 8.0/8.4, mariadb 10.11/11.2/11.4), so an instance created without --option-group-name reports the matching default. Default groups can't be modified, deleted, or tagged.

CreateOptionGroup accepts any EngineName AWS accepts — including oracle-*, sqlserver-*, and db2-* — so a Terraform aws_db_option_group for an engine Floci cannot start still applies. Attaching one to a DB instance requires the group's engine and major engine version to match the instance, as on AWS: a mysql 8.0 group can't be attached to a mysql 8.4 instance. A mismatch fails with InvalidParameterCombination.

aws rds create-option-group \
  --option-group-name my-og \
  --engine-name mysql \
  --major-engine-version 8.0 \
  --option-group-description "MySQL options" \
  --endpoint-url $AWS_ENDPOINT_URL

aws rds modify-option-group \
  --option-group-name my-og \
  --options OptionName=MEMCACHED,Port=11211 \
  --apply-immediately \
  --endpoint-url $AWS_ENDPOINT_URL

aws rds describe-option-groups \
  --engine-name mysql \
  --endpoint-url $AWS_ENDPOINT_URL

Deleting a group that is still attached to a DB instance fails with InvalidOptionGroupStateFault, matching AWS.

Known gaps, all deliberate:

Behavior Status
CopyOptionGroup, DescribeOptionGroupOptions Not implemented — separate actions, not part of option group CRUD
OptionGroupQuotaExceededFault (AWS caps an account at 20 groups) Not enforced — capping a local emulator would only get in a test's way
OptionSetting metadata (DataType, ApplyType, AllowedValues, DefaultValue, Description) Omitted — it would require the per-engine option catalog DescribeOptionGroupOptions serves
MaxRecords / Marker pagination Every group is returned in one page, as with every other RDS list action

Persistence

Each DB instance and cluster gets its own named Docker volume (floci-rds-{volumeId}) created automatically. No configuration is required.

Scenario Volume behavior
memory mode (default) Volume is removed automatically when the instance is deleted
persistent / hybrid / wal Volume is retained after delete — data survives for manual recovery
# CI — ephemeral, volumes cleaned up on each delete
FLOCI_STORAGE_MODE=memory

# Local dev — retain DB data across Floci restarts
FLOCI_STORAGE_MODE=hybrid

# Local dev — also remove volumes immediately on delete
FLOCI_STORAGE_MODE=hybrid
FLOCI_STORAGE_PRUNE_VOLUMES_ON_DELETE=true

To use a host bind mount instead of a named volume (advanced), set an absolute path:

FLOCI_STORAGE_HOST_PERSISTENT_PATH=/absolute/host/path/data

Docker Desktop on macOS

Named volumes work correctly on Docker Desktop for macOS. Bind mounts to paths inside the Floci container are not supported — use named volumes (the default).

Authentication

The RDS auth proxy validates the master username and password at the proxy layer. All other database users are passed through directly to the backend engine — create them with standard SQL (CREATE USER) and connect as normal.

IAM database authentication is also supported. Set --enable-iam-database-authentication at instance creation time and use aws rds generate-db-auth-token to obtain a token.

On PostgreSQL, the token names a database role (DBUser) and the session runs as that role: current_user and session_user both report it, objects it creates are owned by it, and a token naming a role the database does not have is refused with FATAL: role "..." does not exist. Create the role first with CREATE ROLE <name> WITH LOGIN as the master user, and grant it whatever the application needs.

Underneath, the proxy reaches the container as the master user and hands the session over to the token's role, so an IAM session that talks its way back to the master role, via RESET SESSION AUTHORIZATION and its variants, is terminated with FATAL: permission denied to set session authorization rather than being allowed to regain superuser. SET ROLE is untouched: PostgreSQL still permission-checks it against the token's role, exactly as on RDS. One difference from RDS: the proxy learns of the switch from PostgreSQL's own report, so when several statements are batched into a single query after the switch, their results are returned before the session is closed.

On MySQL, AWSAuthenticationPlugin is proprietary to RDS and ships in no public MySQL build, so CREATE USER ... IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS' would fail against the container with ERROR 1524 (HY000): Plugin 'AWSAuthenticationPlugin' is not loaded. The proxy rewrites that clause to IDENTIFIED WITH mysql_native_password AS '*000...0', an authentication string no password hashes to. The account is therefore created, can be granted to and can be dropped, but holds no password of its own, which is what an IAM DB user is: an account whose credentials come from IAM rather than from MySQL. This is the MySQL counterpart of the empty rds_iam role Floci pre-creates for PostgreSQL.

Two limits follow from that. SHOW CREATE USER reports the substituted plugin rather than AWSAuthenticationPlugin, so a Terraform or Pulumi refresh sees drift on auth_plugin. And connecting with a token is not yet emulated for MySQL: unlike PostgreSQL, which receives the password in cleartext, MySQL sends a scramble, so the proxy would have to drive the mysql_clear_password auth switch that real RDS triggers before it could see a token to validate.

TLS / SSL

The RDS auth proxy terminates TLS itself (the backend container stays plaintext) using a self-signed CA whose Subject Alternative Names cover every advertised host Floci has handed out for a DB instance, cluster, or RDS Proxy — the Docker bridge IP, host.docker.internal, localhost, or whatever rds.endpointHost resolves to. The CA is persisted at {storage.persistent-path}/tls/rds-ca.crt and grows its SAN list as new hosts appear, so the same root survives restarts and works for every local database, not just the one that generated it.

Floci logs the certificate path (and the PGSSLROOTCERT hint) the first time it generates or loads it:

RDS proxy TLS: CA cert at ./data/tls/rds-ca.crt
RDS proxy TLS: for sslmode=verify-full set PGSSLROOTCERT=./data/tls/rds-ca.crt

Because the SAN matches the address you actually connect to, verify-full (the same level Aurora enforces in AWS) works locally too — no need to fall back to sslmode=disable just to exercise the same connection-string settings you use in production:

# PostgreSQL
PGSSLROOTCERT=./data/tls/rds-ca.crt psql "host=localhost port=7001 user=admin sslmode=verify-full"

# MySQL / MariaDB
mysql -h 127.0.0.1 -P 7002 -u root -psecret123 \
  --ssl-mode=VERIFY_IDENTITY --ssl-ca=./data/tls/rds-ca.crt