How to Connect RDS PostgreSQL to Your ECS Fargate App: A Startup Guide
Connecting RDS PostgreSQL to your ECS Fargate app is the step that catches most startup engineers off guard. You got your container running in Fargate. You set up an RDS instance. Then you stared at a connection timeout and spent two hours figuring out why they can't talk to each other.
This guide walks through the exact setup โ VPC configuration, security groups, credentials, and environment injection โ in plain English. No AWS architecture diagrams with 15 boxes. Just what you need to get your app reading and writing to a managed Postgres database.
Why RDS Instead of Running Postgres in a Container
Before the setup: running Postgres in a container feels simpler, but it's not a production pattern you want.
When your Fargate task restarts (and it will), your containerized Postgres loses its data unless you've mounted persistent storage. EBS volumes and Fargate don't play nicely together. EFS works but adds complexity. And you're now responsible for backups, software updates, failover, and connection pooling.
RDS manages all of that for you. You get automated backups, minor version updates, Multi-AZ failover, and a stable endpoint your app connects to. The tradeoff is a small monthly cost. The benefit is that your database doesn't disappear when a container restarts.
For any startup with real production traffic, RDS is the right call.
The Mental Model: VPC Isolation
The most important thing to understand before you touch the AWS console: your Fargate tasks and your RDS instance need to be in the same VPC to communicate over a private network connection.
By default, AWS creates a default VPC in every region. You can use it to get started. In production, you'll want dedicated subnets โ but for your first RDS + Fargate setup, the default VPC works fine.
Here's the path a database connection takes:
- Your Fargate task makes a connection to a hostname (the RDS endpoint)
- That hostname resolves to a private IP address inside your VPC
- The connection hits a security group attached to your RDS instance
- That security group either allows or denies the connection based on rules
- If allowed, the TCP connection reaches Postgres on port 5432
- Your app authenticates with username + password
Steps 3 and 4 โ security groups โ are where most connection issues happen.
Step 1 โ Create Your RDS PostgreSQL Instance
In the RDS console, click Create database. Choose:
- Standard create
- Engine: PostgreSQL (latest minor version in your preferred major version)
- Template: Production (or Free tier for development)
- DB instance identifier: Give it a name, e.g.
myapp-postgres - Master username:
postgres(or whatever you prefer) - Master password: Use a strong, random password โ you'll store this in Secrets Manager in a moment
Under Connectivity:
- VPC: Select the same VPC your Fargate tasks are in (usually the default VPC)
- Public access: No โ your database should never be publicly accessible
- VPC security group: Choose Create new and name it
myapp-rds-sg
Under Additional configuration:
- Set your initial database name (e.g.
myapp_production)
Create the database. It takes 5โ10 minutes to provision.
Step 2 โ Store Your Credentials in AWS Secrets Manager
Never pass database credentials to your Fargate task as plaintext environment variables. Use Secrets Manager.
Go to Secrets Manager โ Store a new secret:
- Secret type: Credentials for Amazon RDS database
- Select your RDS instance
- Enter the username and password you just created
Name the secret something like myapp/production/rds-credentials.
Secrets Manager will give you a secret ARN. Copy it โ you'll need it shortly.
Step 3 โ Configure Security Groups
This is where most people get stuck. Two security groups matter:
RDS security group (myapp-rds-sg): This controls who can reach your database. You need to add an inbound rule:
- Type: PostgreSQL
- Port: 5432
- Source: The security group attached to your Fargate tasks (not a CIDR block โ use the security group ID)
If you reference the Fargate security group as the source, only traffic originating from your Fargate tasks is allowed. Nothing else โ not even your own laptop โ can reach the database. This is correct.
Fargate task security group: This controls outbound traffic from your tasks. By default, most security groups allow all outbound traffic. If yours does, you're fine. If outbound rules are restricted, add an outbound rule allowing TCP on port 5432 to the RDS security group.
To get the security group ID of your Fargate tasks: go to your ECS service โ Tasks โ click a running task โ look at the Network section. You'll see a security group ID.
Step 4 โ Grant Your Task IAM Role Access to Secrets Manager
Your Fargate task needs permission to read the secret. IAM roles handle this.
Find the task role attached to your ECS task definition (it's in the task definition under Task role). If you don't have one, create an IAM role with the AmazonECSTaskExecutionRolePolicy managed policy, then attach it.
Add this inline policy to the task role (replace the ARN with your secret's ARN):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789:secret:myapp/production/rds-credentials-*"
}
]
}
Step 5 โ Inject Database Credentials Into Your Container
In your ECS task definition, under Container definitions โ Environment variables, use the valueFrom pattern to pull from Secrets Manager at startup:
{
"name": "DB_HOST",
"value": "myapp-postgres.xxxx.us-east-1.rds.amazonaws.com"
},
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789:secret:myapp/production/rds-credentials:password::"
},
{
"name": "DB_USER",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789:secret:myapp/production/rds-credentials:username::"
},
{
"name": "DB_NAME",
"value": "myapp_production"
},
{
"name": "DB_PORT",
"value": "5432"
}
The valueFrom syntax tells ECS to fetch the value from Secrets Manager when the task starts, and inject it as an environment variable. Your app code reads process.env.DB_PASSWORD (or whatever your framework expects) and never sees the raw secret stored anywhere in your codebase.
The RDS endpoint (DB_HOST) doesn't need to be a secret โ it's not sensitive. The credentials are what you're protecting.
Step 6 โ Test the Connection
Deploy a new task with your updated task definition. In your app logs (CloudWatch โ Log groups โ your ECS log group), look for either a successful database connection or an error.
Common errors and what they mean:
connect ETIMEDOUT โ The security group is blocking the connection. Double-check that the RDS security group inbound rule references the correct Fargate security group ID, not a CIDR block.
password authentication failed โ The credentials are wrong, or the Secrets Manager injection didn't work. Check that the task role has the correct IAM permissions and that the secret ARN in the task definition is correct.
database "myapp_production" does not exist โ You set a different initial database name when creating the RDS instance. Connect with a database client to confirm the database name, or create it via psql.
could not connect to server โ The hostname isn't resolving. Confirm the RDS endpoint in your task definition matches the endpoint shown in the RDS console.
Connection Pooling: What You Need at Scale
Once you have the connection working, think about connection pooling before you hit production traffic.
Each Fargate task opens a connection to RDS. If you have 10 tasks and each task's Node.js process opens 10 connections, you're at 100 database connections. PostgreSQL has a default connection limit of 100 (for db.t3.micro) to a few hundred (for larger instances). At scale, you'll hit the limit.
The fix is PgBouncer running as a sidecar container in your Fargate task, or RDS Proxy (AWS's managed pooler). RDS Proxy is simpler to set up and handles failover cleanly, but adds cost. PgBouncer is free but requires configuration.
For a team of 2โ5 engineers with moderate traffic, RDS Proxy is worth the cost for the operational simplicity.
How NoahOps Handles This
When you deploy a service on NoahOps and add a managed database, this entire setup โ VPC placement, security group rules, Secrets Manager storage, IAM policies, and credential injection into the task definition โ happens automatically.
You pick your database type, size, and environment. NoahOps provisions it inside your VPC, wires up the credentials, and injects them into your ECS task. You get the RDS endpoint in your service config. Your app code connects like it's a local database.
No IAM policy JSON. No security group rules. No Secrets Manager ARN arithmetic.
Request a free demo at noahops.com to see it in action.
FAQ
Can I use the same RDS instance for staging and production?
Technically yes, but you shouldn't. Use separate databases (at minimum) on the same instance, or separate RDS instances entirely. If a production query locks a table, you don't want your staging environment timing out โ and vice versa. NoahOps provisions separate database resources per environment by default.
Do I need Multi-AZ for a startup?
For production: yes. Multi-AZ keeps a standby replica in a different availability zone. If the primary instance fails, AWS automatically fails over to the standby in 60โ120 seconds. Without it, an AZ failure takes your database down until AWS recovers the instance. The cost is roughly 2x the single-AZ price โ worth it for production.
What's the difference between RDS and Aurora?
Aurora is AWS's high-performance, distributed SQL engine. It's faster than standard RDS for read-heavy workloads and has a serverless option (Aurora Serverless v2) that scales to zero. The tradeoff: Aurora is more expensive and the serverless variant has cold start latency. For most startups, standard RDS PostgreSQL is the right starting point. Migrate to Aurora when you have data to prove you need it.
How do I run database migrations?
Run migrations from a one-off Fargate task (same VPC, same security group, same credentials), not from CI/CD pipelines that run outside your VPC. Your CI runner doesn't have network access to your RDS instance โ and it shouldn't. ECS has a run-task API call that's well-suited for migration tasks.
Can I connect to RDS from my local machine for debugging?
Not directly if public access is off (which is correct). Options: use AWS Session Manager with a port-forwarding session to a bastion EC2 instance in your VPC, use RDS Data API if you're on Aurora Serverless, or temporarily add your IP to the RDS security group (clean it up immediately after). NoahOps includes SSH access to your instances, which you can use to set up a port forward.