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:
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
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.
This determines the CPU, memory, and networking capacity of the database instance. For a list of available instance sizes, see the AWS RDS instance types documentation.
Note: Not all instance sizes are available for all engines, versions, and regions.
Some instance families (like t3 or t4) are intended for development and testing, not production workloads.
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, a standby replica is created in a different AZ. If the primary instance fails, traffic is automatically failed over to the standby. This also minimizes downtime during maintenance.
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 can handle read-only traffic to reduce the load on the primary instance. They are kept in sync with the primary through asynchronous replication. Each read replica has its own 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
Storage will automatically scale up when free space is low. For more details on storage autoscaling, see the AWS documentation.
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
Backups
Automated backups are taken daily. You can retain them for up to 35 days. To disable automated backups for RDS engines, set this to 0. This setting does not affect manual snapshots.
resources:myDatabase:type: relational-databaseproperties:credentials:masterUserPassword: my_secret_passwordengine:type: postgresproperties:version: '16.2'primaryInstance:instanceSize: db.t3.microautomatedBackupRetentionDays: 5
Logging
By default, logs are enabled and retained for 90 days. The available log types depend on the database engine. You can log connections, queries, errors, and 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
Maintenance activities, such as OS patching or engine upgrades, will be performed during this window. The database may be briefly unavailable during maintenance. To avoid downtime, use a multi-AZ deployment or an Aurora engine.
The format is day:start_time-day:end_time in UTC (e.g., Sun:02:00-Sun:04:00).
By default, the maintenance window is set to a region-specific time on Sundays.
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
dbNameproperty).- Usage:
$ResourceParam('<<resource-name>>', 'dbName')
Hostname (address) used for reads only. (only available for
aurora-postgresqlandaurora-mysqlengines). 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-postgresqlandaurora-mysqlengines). Connections are auto-balanced among available reader hosts.- Usage:
$ResourceParam('<<resource-name>>', 'readerConnectionString')
Same as readerConnectionString but in JDBC format (only available for
aurora-postgresqlandaurora-mysqlengines).- 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.microusage per month. - 20GB of SSD storage.
- 20GB of backup storage.