The three ways to connect an ECS Fargate service to an RDS Postgres database: security groups with Secrets Manager, RDS Proxy, and IAM database authentication. Plus the setup where the database and its wiring come from your code.
Connecting an ECS Fargate service to Amazon RDS for PostgreSQL takes five pieces: the VPC and subnets, two security groups and the rule between them, the credentials in Secrets Manager, the task definition that injects them, and the IAM permission that lets ECS read the secret. Miss one and the task either never starts or hangs on a connection timeout.
This guide walks through the standard setup and the two common variations, RDS Proxy and IAM database authentication. It starts with Encore, where the database is declared in code and everything between Fargate and RDS is created for you.
Each option is covered below, starting with Encore.
Encore combines an open source infrastructure SDK for TypeScript and Go with a platform that provisions and deploys into your own AWS or GCP account. On AWS, it creates a VPC across two Availability Zones with separate subnets for the load balancer, compute and storage. Fargate tasks run in the private compute subnets, and RDS sits in private storage subnets that only accept connections from the compute subnets.

The database is part of the service:
// accounts/accounts.go
package accounts
import (
"context"
"encore.dev/storage/sqldb"
)
// RDS for PostgreSQL on AWS, a local Postgres under encore run.
var db = sqldb.NewDatabase("accounts", sqldb.DatabaseConfig{
Migrations: "./migrations",
})
type CreateParams struct {
Email string `json:"email"`
}
type Account struct {
ID int64 `json:"id"`
Email string `json:"email"`
}
//encore:api public method=POST path=/accounts
func Create(ctx context.Context, p *CreateParams) (*Account, error) {
a := &Account{Email: p.Email}
err := db.QueryRow(ctx, `
INSERT INTO accounts (email) VALUES ($1)
ON CONFLICT (email) DO UPDATE SET email = EXCLUDED.email
RETURNING id`, p.Email,
).Scan(&a.ID)
return a, err
}
The migration in accounts/migrations creates the accounts table the queries use:
-- accounts/migrations/1_create_accounts.up.sql
CREATE TABLE accounts (
id BIGSERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE
);
Run in your terminal to get started locally.
encore run starts Postgres locally and applies the migration, so the endpoint can be tested with curl localhost:4000/accounts -d '{"email":"a@example.com"}' before anything is deployed.
On deploy, Encore creates the RDS instance if the environment does not have one, runs migrations through Emissary, a SOCKS proxy inside your VPC, and starts the new Fargate tasks. Encore creates the database users and injects the connection configuration into each service, and you can view the database user credentials in the Encore dashboard. Encore provisions an IAM role for the service. If you already run an RDS instance, Encore can import it instead.
An agent asked to "store accounts in Postgres" writes the migration and the query in one change and tests both locally.
This is the standard setup from the ECS developer guide. Create the database in private subnets with a password that RDS manages in Secrets Manager, then allow the Fargate service's security group to reach it:
aws rds create-db-instance \
--db-instance-identifier accounts-db \
--engine postgres --db-instance-class db.t4g.micro \
--allocated-storage 20 \
--master-username app --manage-master-user-password \
--db-subnet-group-name private-db-subnets \
--vpc-security-group-ids sg-0db0000000000000 \
--no-publicly-accessible
# Allow Postgres from the Fargate service's security group only.
aws ec2 authorize-security-group-ingress \
--group-id sg-0db0000000000000 \
--protocol tcp --port 5432 \
--source-group sg-0app000000000000
In the task definition, the container reads the password from the secret RDS created. The :password:: suffix selects the JSON key inside the secret:
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:eu-west-1:123456789012:secret:rds!db-abc123:password::"
}
]
The task execution role needs secretsmanager:GetSecretValue on that secret, and kms:Decrypt if it uses a customer managed key. Without it, the task fails to start with a ResourceInitializationError. The app connects to the RDS endpoint with the username, password and sslmode=require, and the migrations need their own step in your pipeline, run from somewhere inside the VPC. In production, keep the master user for setup and migrations, and give the task its own PostgreSQL role with only the grants the application needs, stored in a separate secret.
RDS Proxy sits between the tasks and the database, holds a pool of connections, and reads the credentials from Secrets Manager itself. It helps when many tasks scale out at once and would otherwise exhaust max_connections, and it shortens failover because clients keep talking to the proxy while it reconnects.
It adds a resource with its own security group, an IAM role for reading the secret, and an hourly cost per vCPU of the database. Some session-level features, such as prepared statements across transactions, can pin connections and reduce the pooling benefit.
With IAM database authentication, the task role gets rds-db:connect for a database user, and the app generates a short-lived authentication token with the AWS SDK instead of using a password. Tokens are valid for 15 minutes, so the connection pool has to request a new one when opening connections.
It removes the long-lived password from the picture. It also adds token generation to your database client setup and a GRANT rds_iam on the Postgres user, and the network rules from Option 1 still apply.
Each Fargate task holds its own pool, so total connections are the pool size times the task count. With autoscaling up to 20 tasks and a pool of 10, the database needs 200 connections, which a db.t4g.micro cannot provide. Size the pool, cap the task count, or add RDS Proxy.
Pick Encore when you want RDS and Fargate created together in your own AWS account, with private networking, per-service database roles and migrations on every deploy, and the same code runnable locally.
Pick security groups and Secrets Manager for a single service talking to its own database. Add RDS Proxy when many tasks scale out quickly, and use IAM database authentication when your security policy rules out long-lived database passwords.
Put the RDS instance in private subnets of the same VPC, allow inbound port 5432 on the database security group from the Fargate service's security group, and pass the credentials to the task through the secrets section of the task definition, which reads them from Secrets Manager at startup. The app then connects to the RDS endpoint like any Postgres host.
The usual causes are a database security group that does not allow 5432 from the task's security group, the task running in a different VPC or in subnets without a route to the database, or a task execution role that lacks secretsmanager:GetSecretValue, which stops the task before your code runs.
Yes. With Encore, you declare the database in TypeScript or Go code and deploy to an AWS environment. Encore creates the VPC, the RDS instance in private subnets, the Fargate services with security groups allowing traffic to it, and a separate database role and credentials for each service, and runs migrations on every deploy.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.