Deploy on AWS

AWS offers two useful Doso shapes:

  1. App Runner for a smaller operational surface.
  2. ECS Fargate for private service discovery, controlled scaling, durable workers, and one Application Load Balancer.

Both use RDS or Aurora PostgreSQL.

Architecture#

Choose ECS for production SaaS or when the API must stay at one controlled replica. App Runner is the simpler single-service option.

Prerequisites#

  • VPC with private subnets across at least two Availability Zones.
  • RDS/Aurora Postgres 16 with automated backups and encryption.
  • ECR repositories for API and web images.
  • Secrets Manager entries for database URLs and encryption/auth keys.
  • ACM certificate and Route 53 or external DNS for app.example.com.

Database#

Keep RDS private. Allow port 5432 only from the web/API task security groups. Create separate application and auth databases:

1DATABASE_URL=postgresql://doso:PASSWORD@DB_HOST:5432/doso?sslmode=require2BETTER_AUTH_DATABASE_URL=postgresql://doso:PASSWORD@DB_HOST:5432/doso_auth?sslmode=require

Use RDS Proxy when autoscaling many short-lived services; otherwise keep SQLx pool sizes small and bounded.

Build and push#

1AWS_REGION=us-east-12ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)3API_IMAGE=$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/doso-api4WEB_IMAGE=$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/doso-web5 6aws ecr get-login-password --region "$AWS_REGION" |7  docker login --username AWS --password-stdin \8    "$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com"9 10docker build -f crates/doso-api/Dockerfile -t "$API_IMAGE:latest" .11docker build -f web/Dockerfile \12  --build-arg NEXT_PUBLIC_DOSO_AUTH_MODE=session \13  --build-arg NEXT_PUBLIC_API_URL= \14  -t "$WEB_IMAGE:latest" web15 16docker push "$API_IMAGE:latest"17docker push "$WEB_IMAGE:latest"

Use immutable commit-SHA tags in production.

App Runner#

Create an API service from ECR:

  • port 8080;
  • VPC connector to RDS;
  • private service when available, otherwise public HTTPS protected by JWT;
  • DATABASE_URL, DOSO_SETTINGS_KEY_HEX, and auth variables from Secrets Manager;
  • health check /healthz.

Create web from its ECR image:

  • port 3000;
  • custom domain app.example.com;
  • DOSO_API_PROXY_TARGET set to the API service URL;
  • Better Auth database and secret from Secrets Manager.

App Runner storage is ephemeral. Use secret-backed key material and Postgres; do not use local SQLite or Git vault persistence.

ECS Fargate#

Create separate task definitions and services for web and api:

  • Cloud Map service discovery gives web a private API hostname.
  • Only web receives traffic from the public ALB.
  • API desired count starts at one.
  • Deployment circuit breaker and automatic rollback are enabled.
  • Tasks use execution/task roles with least-privilege Secrets Manager access.
  • CloudWatch Logs receive stdout/stderr.

Route the ALB's default host to web. Keep /v1/** inside the Next.js proxy so the API target remains private. Use ECS Exec for controlled diagnostics rather than exposing admin ports.

Migrations and deployment#

  1. Run migrations as a one-off Fargate task using the direct database endpoint.
  2. Deploy API, wait for the health check, then deploy web.
  3. For ECS, use rolling or CodeDeploy blue/green deployment.
  4. Verify JWKS, authenticated API calls, SSE, and settings decryption.
  5. Exercise rollback and restore an RDS snapshot in a non-production account.

Keep the API at one replica until startup recovery, background jobs, live settings, and events are safe across processes.

SaaS#

Skip this. Multi-org is ../../saas.

Operations checklist#

  • RDS is private, encrypted, backed up, and has deletion protection.
  • Secrets never appear in task definitions or image layers.
  • ALB/App Runner HTTPS redirects and security headers are enabled.
  • CloudWatch alarms cover 5xx, task restarts, CPU/memory, and database connections.
  • ECR image scanning and lifecycle rules are enabled.

Official references: App Runner, ECS Fargate, and RDS PostgreSQL.