PgDog ECS Terraform Module

February 16, 2026 ยท View on GitHub

Deploys PgDog on AWS ECS. Both Fargate and EC2 clusters are supported, with Fargate used by default.

Features

:heavy_check_mark: ECS service with load balancer and CPU autoscaling

:heavy_check_mark: Secure configuration storage in Secrets Manager

:heavy_check_mark: PgDog logs and metrics export to CloudWatch

:heavy_check_mark: Automatic RDS instance detection, including Aurora readers & writer

:heavy_check_mark: Guaranteed QoS with preconfigured CPU and memory requirements

Quick start

Add this module to your Terraform workspace. If you have an existing RDS Postgres or Aurora database, the module can import it automatically and add it to the [[databases]] section in pgdog.toml, for example:

module "pgdog" {
  source = "github.com/pgdogdev/pgdog-ecs-terraform?ref=v0.1.0"

  # The module will automatically detect all instances
  # and add them to pgdog.toml.
  aurora_clusters = [
    {
      cluster_identifier = "aurora-cluster-name"
      database_name      = "postgres"
    }
  ]

  # Users need to be configured manually.
  users = [
    {
      name                = "postgres"
      database            = "postgres" # Must match database_name above.
      # Password is securely stored in AWS Secrets Manager.
      password_secret_arn = aws_secretsmanager_secret.postgres_user_password.arn
    }
  ]

  # You have a ECS cluster already?
  ecs_cluster_arn = "arn:aws:ecs:us-west-2:1234567890:cluster/your-fargate-ecs-cluster"

  # Networking configuration.
  vpc_id     = "vpc-xxxxxxxxxx"
  subnet_ids = ["subnet-xxxxxxxxxx", "subnet-yyyyyyyyyyy"]

  pgdog = {
    # PgDog version.
    image_tag = "v0.1.29"
    
    # Configure any pgdog.toml settings directly in TF.
    general = {
      workers = 2
    }
  }
}

The password for each user needs to be stored in Secrets Manager. You can do so manually or by using Terraform:

resource "aws_secretsmanager_secret" "postgres_user_password" {
  name = "your-pgdog-deployment/postgres-password"
}

resource "aws_secretsmanager_secret_version" "postgres_user_password" {
  secret_id     = aws_secretsmanager_secret.postgres_user_password.id

  # Store the password in Vault or another secrets manager, e.g. 1Password.
  secret_string = "${data.vault_kv_secret_v2.db.data["password"]}"
}

Dependencies

Tasks created by the ECS service require access to the Internet to download container images and push metrics and logs to CloudWatch. To make this work out of the box, you have two options:

  1. Use private subnet_ids which have an attached NAT gateway
  2. Use public subnet_ids and assign ECS tasks public IPs by configuring assign_public_ip = true

Failure to do either will produce confusing errors at task creation, since it won't be able to log errors to CloudWatch or pull container images.

Managed resources

ResourceDescription
ECS ClusterOptional, only if ecs_cluster_arn not provided.
ECS task definitionPgDog container, init container to setup configuration, and optional ADOT sidecar to export Prometheus metrics to CloudWatch.
ECS serviceSupports deployment circuit breaker and rolling deployments.
Network load balancerYour application will connect to the load balancer.
Security groupFor controlling ingress to ECS tasks.
IAM RolesAutomatically configured task execution and task runtime roles.
Secrets Manager SecretsStoring pgdog.toml and users.toml configuration files.
CloudWatch Log GroupPgDog container logs.
AutoscalingTarget and policies (CPU/memory-based).

Variables

General

NameDescriptionTypeDefaultRequired
nameName prefix for all resourcesstring-yes
create_resourcesWhether to create AWS resources (set to false for config-only testing)booltrueno
tagsTags to apply to all resourcesmap(string){}no
module "pgdog" {
  source = "github.com/pgdogdev/pgdog-ecs-terraform?ref=v0.1.0"
  name   = "myapp"
  tags   = { Environment = "prod" }
}

Networking

NameDescriptionTypeDefaultRequired
vpc_idVPC ID where ECS tasks will runstring-yes
subnet_idsSubnet IDs for ECS taskslist(string)-yes
assign_public_ipAssign public IP to ECS tasks (required if using public subnets without NAT Gateway)boolfalseno
security_group_idsAdditional security group IDs to attach to ECS taskslist(string)[]no
nlb_subnet_idsSubnet IDs for NLB (defaults to var.subnet_ids if not specified)list(string)nullno
nlb_internalWhether the NLB should be internalbooltrueno
vpc_id           = "vpc-123456"
subnet_ids       = ["subnet-a", "subnet-b"]
assign_public_ip = true

RDS configuration

NameDescriptionTypeDefaultRequired
databasesDirect database configuration (alternative to rds_instances/aurora_clusters)list(object)[]no
rds_instancesList of RDS instance identifiers to configure as databaseslist(object)[]no
aurora_clustersList of Aurora cluster identifiers to configure as databaseslist(object)[]no
# Option 1: Direct database configuration
databases = [
  { name = "mydb", host = "primary.example.com", role = "primary" },
  { name = "mydb", host = "replica.example.com", role = "replica" }
]

# Option 2: Auto-discover from RDS
rds_instances = [
  { identifier = "mydb-primary", database_name = "mydb" }
]

# Option 3: Auto-discover from Aurora (uses instance endpoints with automatic role detection)
aurora_clusters = [
  { cluster_identifier = "my-cluster", database_name = "mydb" }
]

PgDog users

NameDescriptionTypeDefaultRequired
usersPgDog users configurationlist(object)-yes
users[].nameUsername for client authenticationstring-yes
users[].databaseDatabase name this user can connect tostring-yes
users[].password_secret_arnARN of Secrets Manager secret containing the passwordstring-yes
users[].server_userUsername for connecting to the backend database (if different)stringnullno
users[].server_password_secret_arnARN of secret for backend database password (if different)stringnullno
users[].pool_sizeConnection pool size for this usernumber10no
users[].pooler_modePooler mode (transaction/session/statement)stringtransactionno
users = [
  {
    name                = "app"
    database            = "mydb"
    password_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789:secret:app-password"
    pool_size           = 20
  },
  {
    name                = "migrations"
    database            = "mydb"
    password_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789:secret:migrations-password"
  }
]

PgDog configuration

NameDescriptionTypeDefaultRequired
pgdogPgDog configuration - mirrors pgdog.toml structureobject{}no
pgdog.image_repositoryContainer image repositorystringghcr.io/pgdogdev/pgdogno
pgdog.image_tagContainer image tagstring0.1.29no
pgdog.generalGeneral settingsobject{}no
pgdog.tlsTLS settingsobjectnullno
pgdog.tcpTCP settingsobjectnullno
pgdog.memoryMemory settingsobjectnullno
pgdog.adminAdmin settingsobjectnullno
pgdog.query_statsQuery stats settingsobjectnullno
pgdog.rewriteRewrite settingsobjectnullno
pgdog.controlControl plane settings (endpoint, token, intervals)objectnullno
pgdog.sharded_tablesSharded tableslist(object)[]no
pgdog.sharded_schemasSharded schemaslist(object)[]no
pgdog.sharded_mappingsSharded mappingslist(object)[]no
pgdog.omnisharded_tablesOmnisharded tableslist(object)[]no
pgdog.mirrorsMirroring configlist(object)[]no
pgdog = {
  general = {
    workers       = 4
    query_timeout = 30000
  }
}

ECS configuration

NameDescriptionTypeDefaultRequired
ecs_cluster_arnExisting ECS cluster ARN (if not provided, a new cluster will be created)stringnullno
task_cpuCPU units for the task (256, 512, 1024, 2048, 4096)number1024no
task_memoryMemory in MB for the tasknumber2048no
desired_countDesired number of ECS tasksnumber2no
min_capacityMinimum number of ECS tasks for auto-scalingnumber2no
max_capacityMaximum number of ECS tasks for auto-scalingnumber10no
capacity_provider_strategyCapacity provider strategy (if not set, uses FARGATE launch type)list(object)nullno
# Default: Fargate
task_cpu      = 2048
task_memory   = 4096
desired_count = 3

# EC2 capacity provider (managed instances)
capacity_provider_strategy = [
  { capacity_provider = "my-ec2-capacity-provider", weight = 1 }
]

# Mixed Fargate + Fargate Spot
capacity_provider_strategy = [
  { capacity_provider = "FARGATE", weight = 1, base = 1 },
  { capacity_provider = "FARGATE_SPOT", weight = 4 }
]

Logging

NameDescriptionTypeDefaultRequired
log_retention_daysCloudWatch log retention in daysnumber30no
log_retention_days = 7

ECS health check

NameDescriptionTypeDefaultRequired
health_check_grace_periodSeconds to wait before starting health checks on a new tasknumber60no
deregistration_delaySeconds to wait before deregistering a target from the NLBnumber30no
health_check_grace_period = 120
deregistration_delay      = 60

CloudWatch metrics export

NameDescriptionTypeDefaultRequired
export_metrics_to_cloudwatchExport PgDog Prometheus metrics to CloudWatch using ADOT sidecarbooltrueno
cloudwatch_metrics_namespaceCloudWatch metrics namespace for PgDog metricsstringPgDogno
metrics_collection_intervalHow often to scrape metrics (in seconds)number60no
export_metrics_to_cloudwatch = true
cloudwatch_metrics_namespace = "MyApp/PgDog"
metrics_collection_interval  = 30

TLS configuration

NameDescriptionTypeDefaultRequired
tls_modeTLS mode: disabled, self_signed, or secrets_managerstringdisabledno
tls_certificate_secret_arnARN of secret containing TLS certificate (when tls_mode = secrets_manager)stringnullno
tls_private_key_secret_arnARN of secret containing TLS private key (when tls_mode = secrets_manager)stringnullno
tls_self_signed_common_nameCommon name for self-signed certificatestringpgdogno
tls_self_signed_validity_daysValidity period for self-signed certificatenumber365no
# Self-signed certificate (generated on boot)
tls_mode = "self_signed"

# Certificate from Secrets Manager
tls_mode                   = "secrets_manager"
tls_certificate_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789:secret:pgdog-cert"
tls_private_key_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789:secret:pgdog-key"