Relational (SQL) Databases
Stacktape allows you to deploy a fully managed relational (SQL) database, with support for PostgreSQL, MySQL, MariaDB, Oracle, and SQL Server. The service handles capacity scaling, hardware provisioning, database setup, patching, logging, and backups, so you can focus on your application.
Under the hood, Stacktape uses Amazon RDS. It supports three engine categories with different topologies and scaling behaviors:
- Basic RDS engines: A single-node database that is the most cost-effective option. It supports optional read replicas for higher performance and a Multi-AZ standby instance for resilience.
- Aurora engine: A multi-node, highly available cluster with increased durability and fault tolerance. It automatically balances read requests across nodes for better performance.
- Aurora Serverless engine: Similar to the Aurora engine, but with automatic, usage-based scaling. It can scale down to zero when inactive, saving costs.
Databases are always deployed in a private network within your stack's VPC. You can configure accessibility to control whether the database is accessible from the internet or only from other resources in the VPC.
Basic usage
To connect to a relational database, you typically use a connection string. Stacktape can automatically inject the necessary credentials and connection details into your application's environment when you use the connectTo
property.
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: $Secret('database.password')engine:type: postgresproperties:version: '16.2'primaryInstance:instanceSize: db.t3.microapiServer:type: multi-container-workloadproperties:resources:cpu: 1memory: 1024containers:- name: api-containerpackaging:type: stacktape-image-buildpackproperties:entryfilePath: src/main.tsconnectTo:- myDatabase
A single-node PostgreSQL database.
import express from 'express';import { Pool } from 'pg';const pgPool = new Pool({connectionString: process.env.STP_MY_DATABASE_CONNECTION_STRING // env variable was automatically injected by Stacktape});const app = express();app.get('/time', async (req, res) => {const result = await pgPool.query('SELECT NOW()');const time = result.rows[0];res.send(time);});app.listen(3000, () => {console.info('Server running on port 3000');});
A container workload connecting to the database.
Database credentials
You can configure the credentials for the database's master user. It's recommended to use a secret to manage these credentials securely.
resources:myRelationalDatabase:type: relational-databaseproperties:credentials:masterUserName: $File('.env').DB_USER_NAME # OPTIONALmasterUserPassword: $Secret('dbCredentials.password')engine:type: postgresproperties:version: '16.2'port: 5432primaryInstance:instanceSize: db.t2.micro
Engine
The database engine determines the database type, performance, availability, and pricing.
Depending on the properties they provide, we group engine types into following groups:
- RDS Engine group - single node fully managed databases.
- Supported engines:
- postgres
- mysql
- mariadb
- oracle-ee
- oracle-se2
- sqlserver-ee
- sqlserver-ex
- sqlserver-se
- sqlserver-web
- Supported engines:
- Aurora Engine group - multi node highly available cluster with replicated storage.
- Supported engines:
- aurora-postgresql
- aurora-mysql
- Supported engines:
- Aurora Serverless V2 Engine - serverless engine with replicated storage. Compute
resources scale based on actual demand.
- Supported engines:
- aurora-postgresql-serverless-v2
- aurora-mysql-serverless-v2
- Supported engines:
- Aurora Serverless V1 Engine - old generation serverless engine. We recommend using V2
instead.
- Supported engines:
- aurora-postgresql-serverless
- aurora-mysql-serverless
- Supported engines:
Engine version
You must choose a version that is compatible with your chosen engine.
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: $Secret('dbPassword')engine:type: postgresproperties:version: '16.2'port: 5432primaryInstance:instanceSize: db.t3.micro
Available versions
aurora-mysql
aurora-mysql-serverless-v2
aurora-postgresql
aurora-postgresql-serverless-v2
mariadb
mysql
oracle-ee
oracle-se2
postgres
sqlserver-ee
sqlserver-ex
sqlserver-se
sqlserver-web
RDS engine
The RDS engine is the default, single-node, and most cost-effective option. While it's not highly available by default, you can configure a standby instance in a different Availability Zone or add read replicas to improve performance and resilience.
Instance size
You can configure the instance size for both the primary instance and its read replicas.
- Allows you to choose the database instance size. Each instance size offers different combination of CPU, memory, storage, and networking capacity.
- To see a list of available instances, refer to AWS docs
- Not every instance size is supported for every database engine, version or region.
- Be aware that some instance families (such as t3 or t4) are mostly meant for testing and not production workloads
- Refer to AWS Docs for detailed breakdown of supported combinations.
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: $Secret('dbPassword')engine:type: postgresproperties:version: '16.2'port: 5432primaryInstance:instanceSize: db.t3.micro
Multi-AZ mode
You can enable Multi-AZ mode for both primary instances and read replicas.
- When enabled, the data is replicated to a standby instance in a different AZ (Availability Zone).
- If the default (primary) instance fails, the failover to the standby instance in another AZ is performed.
- The failover to the standby instance is synchronous (highly durable).
- The standby instance can not be directly accessed (doesn't have its own database endpoint).
- In contrast to using replicas, standby instance can fully take-over the responsibilities of the primary instance, while replicas can only be used for read operations.
- Reduces the impact of maintenance. The maintenance is performed on the standby instance first, promotes the standby instance to a primary instance, and then performs maintenance on the old primary instance which is now a standby replica.
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: $Secret('dbPassword')engine:type: postgresproperties:version: '16.2'port: 5432primaryInstance:instanceSize: db.t2.micromultiAz: true
Read replicas
- Read replicas help to decrease the load on the primary instance by serving a read-only database requests.
- Replication uses database's native asynchronous replication to update the read replicas whenever there is a change to the primary instance.
- Each read replica has its own database endpoint.
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: $Secret('dbPassword')engine:type: postgresproperties:version: '16.2'port: 5432primaryInstance:instanceSize: db.t3.microreadReplicas:- instanceSize: db.t3.micro- instanceSize: db.t3.micro
Storage
- When you run out of free database space, your storage will automatically scale up.
- By default, scales between 20GB and 200GB.
- The scaling process happens when the following conditions are met:
- Free available space` is less than 10 percent of the allocated storage
- The low-storage condition lasts at least five minutes.
- At least six hours have passed since the last storage modification.
- To learn more about storage autoscaling, refer to AWS Docs
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: $Secret('dbPassword')engine:type: postgresproperties:version: '16.2'port: 5432primaryInstance:instanceSize: db.t3.microstorage:initialSize: 40maxSize: 400
Aurora engine
The Aurora engine is a fully managed, AWS-developed database engine that offers clustering, high availability, and increased performance. It replicates storage six ways across three Availability Zones and automatically load-balances read operations between nodes. If a primary instance fails, a read replica is automatically promoted to take its place.
resources:auroraSlsPostgres:type: relational-databaseproperties:credentials:masterUserPassword: $Secret('dbSecret.password')engine:type: aurora-postgresqlproperties:version: '16.2'instances:- instanceSize: db.t3.mediumport: 5432
Aurora Serverless V2 engine
The Aurora Serverless V2 engine is similar to the standard Aurora engine but adds automatic, usage-based scaling. It's more responsive and less disruptive when scaling than the V1 engine. The database can scale down to zero, so you don't pay for compute capacity when it's not in use.
Scaling is measured in Aurora Capacity Units (ACUs), where each ACU provides approximately 2GB of RAM and one virtual CPU.
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: $Secret('dbSecret.password')engine:type: aurora-postgresql-serverless-v2properties:version: '16.4'maxCapacity: 8
Aurora Serverless V1 engine
We recommend using the newer Aurora Serverless V2 engine instead of this one.
The Aurora Serverless V1 engine is the previous generation of the serverless Aurora engine. It also provides automatic, usage-based scaling and can scale to zero, but it is less responsive than the V2 engine.
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: $Secret('dbSecret.password')engine:type: aurora-postgresql-serverlessproperties:version: '13.12'minCapacity: 4maxCapacity: 8pauseAfterSeconds: 600
Backups
- Databases are automatically backed up once a day.
- Maximum retention period is
35
days. - You can disable automated backups by setting the value to 0 (works only for RDS engines).
- You can also take manual backup snapshots (in the console or using the API). The retention is not applied to manual backups.
- By default, backups are retained for 1 day.
- To learn more about RDS engine backups, refer to RDS engine backups AWS Docs.
- To learn more about Aurora engine backups, refer to Aurora engine backups AWS Docs.
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: my_secret_passwordengine:type: postgresproperties:version: '16.2'primaryInstance:instanceSize: db.t3.microautomatedBackupRetentionDays: 5
Logging
- By default, logging is enabled and logs are preserved for
90
days. - Logged data depend on the used
engine
. You can log information about connections, disconnections, executed queries & much more.
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: my_secret_passwordengine:type: postgresproperties:version: '16.2'primaryInstance:instanceSize: db.t3.micrologging:retentionDays: 30engineSpecificOptions:log_connections: true
Forwarding logs
You can forward logs to third-party services. See Forwarding Logs for more information.
Closing zombie connections
Connections from resources that are no longer running (like stopped containers) can become "zombie" connections. In modern, ephemeral architectures, it's important to have a strategy for handling them.
From container workloads
When connecting from a container, you should close the connection before the container exits. You can do this by listening for the SIGTERM
signal.
const connectionPool = createConnectionPool();// remember to close the connection even on errorsprocess.on('uncaughtException', () => {connectionPool.close();process.exit(1);}).on('unhandledRejection', () => {connectionPool.close();process.exit(1);});process.on('SIGTERM', () => {connectionPool.close();process.exit(0);});
From batch jobs
Similarly, for batch jobs, you should close the connection before the job finishes.
const connectionPool = createConnectionPool();connectionPool.connect();// remember to close the connection even on errorsprocess.on('uncaughtException', () => {connectionPool.close();process.exit(1);}).on('unhandledRejection', () => {connectionPool.close();process.exit(1);});doSomethingWithYourConnection();connectionPool.close();
From Lambda functions
For Lambda functions, you have two options:
1. Initialize and close the connection inside the handler.
This prevents zombie connections but can be slow, as creating a new connection for each invocation can add significant latency.
import { Client } from 'pg';const handler = async (event, context) => {const pgClient = new Client({user: process.env.DB_USER,host: process.env.DB_HOST,database: process.env.DB_NAME,password: process.env.DB_PASSWORD,port: process.env.DB_PORT});await pgClient.connect();const result = await pgClient.query('SELECT NOW()');const time = result.rows[0];await pgClient.end();return { result: time };};export default handler;
2. Initialize the connection outside the handler.
This reuses the connection across invocations, which is more performant. However, it can lead to zombie connections because you can't hook into the Lambda container's shutdown process. To mitigate this, you should:
- Lower your database's connection timeout settings.
- Add logic to your application to re-establish the connection if it's been closed by the database.
import { Client } from 'pg';const pgClient = new Client({user: process.env.DB_USER,host: process.env.DB_HOST,database: process.env.DB_NAME,password: process.env.DB_PASSWORD,port: process.env.DB_PORT});(async () => {await pgClient.connect();})();const handler = async (event, context) => {const result = await pgClient.query('SELECT NOW()');const time = result.rows[0];return { result: time };};export default handler;
Accessibility
You can control which resources can access your database. In addition to requiring user credentials, you can restrict access based on the network.
Internet mode
This is the default and least restrictive mode. The database can be accessed from anywhere on the internet.
VPC mode
The database can only be accessed from resources within the default VPC, such as functions, batch jobs, and container workloads. You can also whitelist specific IP addresses to allow access from the internet.
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: $Secret('dbPassword')engine:type: aurora-postgresqlproperties:version: '16.2'instances:- instanceSize: db.t3.mediumport: 5432accessibility:accessibilityMode: vpcmyFunction:type: functionproperties:packaging:type: stacktape-lambda-buildpackproperties:entryfilePath: path/to/my/function.tsjoinDefaultVpc: true
Scoping workloads in VPC mode
This mode is similar to VPC mode but more restrictive. In addition to being in the same VPC, a resource must explicitly list the database in its connectTo
property to gain access.
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: $Secret('dbPassword')engine:type: aurora-postgresqlproperties:version: '16.2'instances:- instanceSize: db.t3.mediumport: 5432accessibility:accessibilityMode: scoping-workloads-in-vpcmyFunction:type: functionproperties:packaging:type: stacktape-lambda-buildpackproperties:entryfilePath: path/to/my/function.tsjoinDefaultVpc: trueconnectTo:- myDatabase
Whitelisted IPs only mode
The database can only be accessed from the IP addresses and CIDR blocks in the whitelistedIps
list.
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: $Secret('dbPassword')engine:type: aurora-postgresqlproperties:version: '16.2'instances:- instanceSize: db.t3.mediumport: 5432accessibility:accessibilityMode: whitelisted-ips-onlywhitelistedIps:- '147.25.33.12'
Maintenance window
- The maintenance window is the time period during which the database is available for maintenance.
- During this time, your database instance can be updated with modifications like OS patching or database engine version upgrades.
- The database might be unavailable for a short period of time during maintenance. To avoid service interruptions, you should use
multiAz
deployment for RDS engines or use an Aurora engine. - The maintenance window is specified in UTC.
- The maintenance window is specified in the format of
day:time-day:time
. Example:Sun:02:00-Sun:04:00
- By default, the maintenance window is set to Sunday 02:00 - Sunday 04:00 adjusted to the region's timezone (but does not account for daylight savings time). Examples:
- In us-east-1 the maintenance window is set to Sunday 07:00 - Sunday 09:00 UTC which translates to Sunday 02:00 - Sunday 04:00 in the region's timezone
- In eu-west-1 the maintenance window is set to Sunday 01:00 - Sunday 03:00 UTC which translates to Sunday 02:00 - Sunday 04:00 in the region's timezone
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: my_secret_passwordengine:type: postgresproperties:version: '16.2'primaryInstance:instanceSize: db.t3.micropreferredMaintenanceWindow: Sun:04:00-Sun:05:00
Referenceable parameters
The following parameters can be easily referenced using $ResourceParam directive directive.
To learn more about referencing parameters, refer to referencing parameters.
Fully-formed connection string that can be used to access the primary instance. For aurora databases, this is connectionString to cluster endpoint, which can be used for both reads and writes. Includes host, port, username, password and dbName.
- Usage:
$ResourceParam('<<resource-name>>', 'connectionString')
Fully-formed connection string in JDBC form that can be used to access the primary instance. Includes host, port, username, password and dbName.
- Usage:
$ResourceParam('<<resource-name>>', 'jdbcConnectionString')
Hostname (address) of the primary instance that can be used for both reads and writes. For aurora databases, this is hostname of a cluster endpoint, which can be used for both reads and writes.
- Usage:
$ResourceParam('<<resource-name>>', 'host')
Port of the database.
- Usage:
$ResourceParam('<<resource-name>>', 'port')
Name of the automatically created database (can be configured using the
dbName
property).- Usage:
$ResourceParam('<<resource-name>>', 'dbName')
Hostname (address) used for reads only. (only available for
aurora-postgresql
andaurora-mysql
engines). If you have multiple instances, it is advised to use readerHost for reads to offload the primary (read/write) host. ReaderHost automatically balances requests between available instances. Connections are auto-balanced among available reader hosts.- Usage:
$ResourceParam('<<resource-name>>', 'readerHost')
Same as connectionString but targets readerHosts (only available for
aurora-postgresql
andaurora-mysql
engines). Connections are auto-balanced among available reader hosts.- Usage:
$ResourceParam('<<resource-name>>', 'readerConnectionString')
Same as readerConnectionString but in JDBC format (only available for
aurora-postgresql
andaurora-mysql
engines).- Usage:
$ResourceParam('<<resource-name>>', 'readerJdbcConnectionString')
Comma-separated list of read replica hostnames (only available if read replicas are configured). Read replicas can only be used for read operations.
- Usage:
$ResourceParam('<<resource-name>>', 'readReplicaHosts')
Comma-separated list of connection strings (URLs) used to connect to read replicas (only available when read replicas are configured). Read replicas can only be used for read operations.
- Usage:
$ResourceParam('<<resource-name>>', 'readReplicaConnectionStrings')
Same as readReplicaConnectionStrings but in JDBC format (only available when read replicas are configured).
- Usage:
$ResourceParam('<<resource-name>>', 'readReplicaJdbcConnectionStrings')
Pricing
Pricing depends heavily on the engine you choose.
RDS engines:
- Instances: Prices vary by instance size and region. See the AWS pricing pages for PostgreSQL, MySQL, MariaDB, Oracle, and SQL Server.
- Storage: $0.115 - $0.238 per GB per month.
- Backups: Free for automated backups that don't exceed your total database storage. Additional backup storage is $0.095 per GB per month.
Aurora engines:
- Instances: Prices start at $0.073 per hour. See the Aurora pricing page for details.
- Storage: $0.10 - $0.19 per GB per month.
- I/O Rate: $0.20 - $0.28 per million read/write operations.
- Backups: Free for automated backups that don't exceed your total database storage. Additional backup storage is $0.021 - $0.037 per GB per month.
Aurora Serverless:
- ACUs (Aurora Capacity Units): Each ACU costs $0.06 - $0.10 per hour.
- Storage, I/O, and Backups: Same as the standard Aurora engine.
Data transfer charges (all engines):
- Inbound: Free.
- Outbound (same AZ): Free.
- Outbound (different AZ): $0.01 per GB.
- Outbound (internet): First 1GB is free, then $0.09 - $0.15 per GB.
Free Tier (first 12 months):
- 750 hours of
db.t2.micro
usage per month. - 20GB of SSD storage.
- 20GB of backup storage.