This guide gives the current production-style deployment path for this repository:
- Deploy the API as a container on Amazon ECS with AWS Fargate.
- Use Neon PostgreSQL for the database.
- Store sensitive runtime values in AWS Secrets Manager.
- Route traffic through an Application Load Balancer.
This repository is the PostgreSQL track of the Users & Posts API. If you continue running the MongoDB version separately, keep SQL-specific runtime resource names distinct with a suffix such as -sql.
Use this architecture for the current project:
- Neon PostgreSQL 18 for the database
- Amazon ECR for the container image
- Amazon ECS with AWS Fargate for compute
- Application Load Balancer for inbound traffic
- AWS Secrets Manager for
DATABASE_URLandJWT_SECRET - CloudWatch Logs for container logs
Application traffic flow:
- A client calls the load balancer DNS name.
- The load balancer forwards traffic to the ECS target group.
- A Fargate task runs the API container on port
3111. - The API connects to Neon PostgreSQL over TLS using
DATABASE_URL. - Neon stores the application data in PostgreSQL.
This project requires PostgreSQL 18 because the schema uses native uuidv7() defaults.
Important notes:
- Create the Neon project on PostgreSQL
18. - If the database is created on PostgreSQL
17or lower, schema initialization fails. - Neon supports PostgreSQL
18as of March 2026, but it is currently preview.
If you still maintain the MongoDB version in AWS, these parts can remain shared if that already matches your setup:
- VPC
- Internet Gateway
- Public subnets for the load balancer
- Private subnets for ECS tasks
- NAT gateway and route tables
- ECS cluster
- Task execution IAM role, if it already has the permissions you need
- Application Load Balancer, if you use separate target groups and listener rules
- General Secrets Manager structure
These items should be separate for the SQL deployment and should use a distinct -sql name:
- Neon project and database credentials
- ECR repository
- ECS task definition family
- ECS service
- Container name
- Target group
- CloudWatch log group
Recommended SQL resource names:
- ECR repository:
users-posts-api-sql - ECS task definition family:
users-posts-api-sql - ECS service:
users-posts-api-sql - ECS container:
users-posts-api-sql - Target group:
users-posts-api-sql-tg - CloudWatch log group:
/ecs/users-posts-api-sql - Neon project:
users-posts-sql - Secret for database URL:
users-posts/sql/prod/database-url
Prepare these values before you start:
AWS_REGION=<your-region>
AWS_ACCOUNT_ID=<your-account-id>
ECR_REPOSITORY_NAME=users-posts-api-sql
ECS_CLUSTER_NAME=<existing-or-new-ecs-cluster>
ECS_SERVICE_NAME=users-posts-api-sql
ECS_TASK_FAMILY=users-posts-api-sql
ECS_CONTAINER_NAME=users-posts-api-sql
TARGET_GROUP_NAME=users-posts-api-sql-tg
APP_PORT=3111
ALLOWED_ORIGINS=<comma-separated-frontend-origins>
JWT_SECRET=<long-random-secret>
NEON_PROJECT_NAME=users-posts-sql
NEON_POSTGRES_VERSION=18
NEON_DATABASE_NAME=users
NEON_DATABASE_ROLE=app_user
NEON_DATABASE_URL=<your-neon-connection-string>
High-level flow:
- Create the Neon PostgreSQL 18 project.
- Create the AWS secrets.
- Build and push the container image to ECR.
- Prepare the ECS networking and security groups.
- Create the task definition and ECS service.
- Run the database migrations as a one-off ECS task.
- Validate
/healthand/api-docs.
This application expects these environment variables:
DATABASE_URLis required.JWT_SECRETis required.ALLOWED_ORIGINSis required.PORTis optional and defaults to3111.DATABASE_SSLis optional and should betruefor Neon.DATABASE_SSL_REJECT_UNAUTHORIZEDis optional and should usually staytrue.
Useful endpoints after deployment:
GET /healthGET /api-docsGET /usersGET /posts
Before you start, make sure you have:
- An AWS account
- A Neon account
- Docker installed on your local machine
- Access to the AWS console
- AWS CLI installed locally if you want to use CLI examples
Optional but helpful:
- A GitHub account or another Git remote for the source
- Existing shared AWS networking if you already run the MongoDB version
Use Neon first because the app cannot boot without a valid PostgreSQL connection string.
- Create or open your Neon account.
- Create a new Neon project.
- Choose PostgreSQL version
18. - Choose an AWS region close to your ECS deployment.
- Set the project name to something distinct such as
users-posts-sql. - Create or confirm the database name is
users. - Create a dedicated application role such as
app_user. - Copy the server-side connection string.
Example format:
postgresql://app_user:<password>@ep-example-123456.us-east-2.aws.neon.tech/users?sslmode=require
Use the application database name users unless you intentionally want a different database.
If you want IP restriction in Neon:
- Decide how ECS tasks will reach the internet.
- If using private ECS subnets, send outbound traffic through a NAT gateway with an Elastic IP.
- Add that Elastic IP to the Neon IP allow rules.
If you do not want to manage IP restrictions immediately, you can start without an allowlist and tighten it later when your network path is stable.
Store runtime secrets before creating the ECS task definition.
Recommended secrets:
- One secret for
DATABASE_URL - One secret for
JWT_SECRET
Recommended names:
users-posts/sql/prod/database-urlusers-posts/shared/prod/jwt-secretor a SQL-specific JWT secret if you prefer
Example AWS CLI commands:
aws secretsmanager create-secret \
--name users-posts/sql/prod/database-url \
--secret-string 'postgresql://app_user:<password>@ep-example-123456.us-east-2.aws.neon.tech/users?sslmode=require' \
--region <your-region>
aws secretsmanager create-secret \
--name users-posts/shared/prod/jwt-secret \
--secret-string '<long-random-secret>' \
--region <your-region>Keep these non-secret values ready for the task definition:
PORT=3111DATABASE_SSL=trueDATABASE_SSL_REJECT_UNAUTHORIZED=trueALLOWED_ORIGINS=<comma-separated-origins>
Create the image repository for the SQL deployment.
Example repository name:
users-posts-api-sql
Create it:
aws ecr create-repository --repository-name users-posts-api-sql --region <your-region>From the repository root:
docker build -t users-posts-api-sql:latest .
aws ecr get-login-password --region <your-region> | docker login --username AWS --password-stdin <aws-account-id>.dkr.ecr.<your-region>.amazonaws.com
docker tag users-posts-api-sql:latest <aws-account-id>.dkr.ecr.<your-region>.amazonaws.com/users-posts-api-sql:latest
docker push <aws-account-id>.dkr.ecr.<your-region>.amazonaws.com/users-posts-api-sql:latestThis repository's container listens on port 3111 and exposes /health for health checks.
Use the normal ECS with ALB layout:
- Two public subnets for the Application Load Balancer
- Two private subnets for the ECS tasks
- NAT gateway for outbound internet access from the private subnets
Recommended security groups:
Inbound:
80from the internet443from the internet if you add TLS
Outbound:
- Allow traffic to the ECS task security group on port
3111
Inbound:
3111from the load balancer security group only
Outbound:
- Allow outbound internet access so the tasks can reach Neon and other required endpoints
If you already have a shared VPC and ALB for the MongoDB deployment, you can reuse them and add a new SQL target group plus a new listener rule.
You need a task execution role at minimum.
Recommended permissions for the task execution role:
- Pull images from Amazon ECR
- Write logs to CloudWatch Logs
- Read the referenced secrets from Secrets Manager
If you already use a shared execution role for the MongoDB deployment, confirm that it allows access to:
- the
users-posts-api-sqlECR repository - the SQL CloudWatch log group
- the SQL
DATABASE_URLsecret
You can either:
- Reuse an existing ECS cluster
- Create a new one for the SQL deployment
Example CLI:
aws ecs create-cluster --cluster-name <your-cluster-name> --region <your-region>If you already use a shared cluster with the MongoDB deployment, keep the SQL service name distinct.
Create the log group before you register the task definition.
Example:
aws logs create-log-group --log-group-name /ecs/users-posts-api-sql --region <your-region>Create a target group for the SQL API service.
Recommended settings:
- Target type:
ip - Protocol:
HTTP - Port:
3111 - Health check path:
/health
Recommended name:
users-posts-api-sql-tg
If you already have an ALB, add a SQL-specific rule.
Examples:
- Route a dedicated hostname such as
sql-api.example.com - Route a dedicated path prefix such as
/sql/*
If this is your first deployment on the ALB, you can forward the default listener rule to the SQL target group for now.
Create a Fargate task definition with these baseline settings:
- Family:
users-posts-api-sql - Container name:
users-posts-api-sql - Image:
<aws-account-id>.dkr.ecr.<your-region>.amazonaws.com/users-posts-api-sql:latest - Port mapping:
3111 - Launch type compatibility:
FARGATE - Network mode:
awsvpc - Log driver:
awslogs - Log group:
/ecs/users-posts-api-sql
Environment values:
PORT=3111DATABASE_SSL=trueDATABASE_SSL_REJECT_UNAUTHORIZED=trueALLOWED_ORIGINS=<comma-separated-origins>
Secrets:
DATABASE_URLfrom Secrets ManagerJWT_SECRETfrom Secrets Manager
Health check:
- Container health check should hit
http://127.0.0.1:3111/health
Create the ECS service using the SQL task definition.
Recommended settings:
- Service name:
users-posts-api-sql - Launch type:
FARGATE - Desired count:
1to start - Deployment controller: ECS rolling update
- Attach the target group
users-posts-api-sql-tg - Place tasks in the private subnets
- Assign the task security group created earlier
- Do not assign a public IP if the tasks already have NAT egress
This project expects the PostgreSQL schema to exist before normal traffic starts.
For AWS, use a one-off ECS task that runs the compiled migration entrypoint against the Neon database.
Migration command:
node dist/db/init.jsRecommended flow:
- Build and push the application image.
- Register the task definition.
- Run a one-off task using the same image, same secrets, and same network settings.
- Override the container command so it runs
node dist/db/init.js. - Wait for the task to finish successfully.
- Only then create or scale the long-running service.
The migrations create these tables:
userspostsuser_favorite_foodsuser_hobbies
Use the load balancer DNS name or your own hostname.
Test these URLs:
http://<alb-dns>/healthhttp://<alb-dns>/api-docshttp://<alb-dns>/usershttp://<alb-dns>/posts
If /health fails:
- Check the ECS service events.
- Check the CloudWatch log group
/ecs/users-posts-api-sql. - Confirm the ECS task can reach the Neon endpoint.
- Confirm
DATABASE_URLpoints to the Neon PostgreSQL 18 project. - Confirm the migrations completed successfully.
- Confirm the target group health check uses
/health.
For each new release:
docker build -t users-posts-api-sql:latest .
aws ecr get-login-password --region <your-region> | docker login --username AWS --password-stdin <aws-account-id>.dkr.ecr.<your-region>.amazonaws.com
docker tag users-posts-api-sql:latest <aws-account-id>.dkr.ecr.<your-region>.amazonaws.com/users-posts-api-sql:latest
docker push <aws-account-id>.dkr.ecr.<your-region>.amazonaws.com/users-posts-api-sql:latestThen:
- Register a new task definition revision.
- Update the ECS service to the new revision.
- Wait for the rolling deployment to complete.
- Re-test
/healthand/api-docs.
Add these only if you need them.
Useful if you want a custom domain and HTTPS.
Recommended additions:
- Route 53 hosted zone
- ACM certificate
- HTTPS listener on the ALB
Useful if you want operational access and AWS-native troubleshooting workflows.
Useful if you want alerts for:
- unhealthy target count
- high response latency
- task restarts
Useful if you want basic public-edge protection in front of the ALB.
Keep these controls in place:
- Use a long random
JWT_SECRET - Use a dedicated Neon database role for this app
- Keep
DATABASE_SSL=true - Restrict ECS task inbound traffic to the load balancer security group only
- Keep Secrets Manager values out of the repository
- If you use Neon IP allow rules, only allow the stable NAT egress IP
When you no longer need the deployment:
- Scale down or delete the ECS service.
- Deregister old task definition revisions if you want to tidy up.
- Delete the target group and listener rule if they are dedicated to the SQL service.
- Delete the SQL ECR repository if you no longer need the image.
- Delete the SQL secrets from Secrets Manager if they are dedicated to this deployment.
- Delete the Neon project or database role if you no longer need it.
When this deployment is no longer enough, upgrade in this order:
- Add HTTPS with ACM and Route 53.
- Add alarms and dashboards.
- Increase ECS desired count above
1. - Add stricter Neon network controls when your outbound IP path is stable.
- Split shared AWS resources from the MongoDB deployment if you want full isolation.