Documentation Index

Fetch the complete documentation index at: https://docs.plainid.io/llms.txt

Use this file to discover all available pages before exploring further.

ECS Deployment

Prev Next
Preproduction Feature

Note that this is a preproduction feature that is available only to preproduction Tenants.

This guide provides complete instructions for deploying the PlainID Policy Authorization Agent (PAA) to the AWS Elastic Container Service (ECS) on Fargate using Terraform. It walks you through prerequisites, configuration, installation, and advanced customization.

Deploying the PAA through this Terraform pack gives you a full set of PAA services (agent, runtime, pip-operator, idp-webhook, secrets-mgmt) running on ECS-native primitives.

This deployment guide covers:

Prerequisites

Before you begin, ensure you have the following:

  • Terraform version 1.9 or later
  • AWS provider version 5.40 or later
  • An AWS account with sufficient permissions to create ECS services, IAM roles, Cloud Map resources, CloudWatch Logs groups, and Application Auto Scaling resources
  • An existing VPC and subnets
  • Task subnets with outbound network reachability to ECR, the SSM API, the ECS API, and the CloudWatch Logs API (a NAT gateway or VPC endpoints), plus reachability to the PlainID tunnel endpoint (remote_api)
  • Your PlainID Tenant ID, PAA ID, and Agent Secret Key, available in Platform Tenant Settings

Networking model

PAA traffic is outbound and in-VPC only. This pack doesn't provision a load balancer, target groups, listener rules, or a TLS certificate.

  • The agent service opens an outbound tunnel to PlainID over remote_api (wss://... or https://...).
  • Callers inside your VPC, both other PAA services and your own applications, reach runtime, pip-operator, and secrets-mgmt over AWS Cloud Map private DNS (<service>.<service_discovery_namespace>, for example runtime.plainid-paa.internal:8010).
  • The pack disables the idp-webhook by default. If you need inbound webhooks from external identity providers, add your own load balancer or API Gateway in front of it and point it at its Cloud Map address.

If you need inbound access to runtime from outside your VPC, for example fronting it with a load balancer, add that yourself. The pack doesn't create it.

What the pack creates

For each service, the pack creates:

  • An ECS task definition (Fargate, awsvpc networking)
  • An ECS service with rolling deployment and automatic rollback if a deployment fails
  • A Cloud Map DNS entry for service-to-service communication (for example, agent.plainid-paa.internal)
  • An Application Auto Scaling target with a CPU-based scaling policy
  • A security group allowing traffic from your VPC on the container port

It also creates, once for the whole deployment:

  • An AWS Cloud Map private DNS namespace (plainid-paa.internal by default)
  • An IAM task-execution role with SSM Parameter Store read access
  • An IAM task role (empty by default; you can extend it if your services need additional AWS permissions)
  • A CloudWatch Logs group per service

Optionally, depending on your configuration, the pack can also create an ECS Fargate cluster, SSM parameters for your secrets, and a Redis instance. Each of these is described in the sections below.

Service ports

Service Ports Notes
agent 8080 (HTTP), 8081 (management/health) Opens the outbound tunnel to PlainID
runtime 8010 (HTTP and health) Connects to pip-operator and the agent; serves authorization decisions to your applications
pip-operator 8080 (HTTP), 8089 (management), 31350 (JDBC), 31351 (Postgres) Connects to the agent over WebSocket
idp-webhook 8080 (HTTP), 9090 (management) Disabled by default
secrets-mgmt 8080 (HTTP), 8081 (management) Connects to Redis only if secrets_mgmt.redis_integration = true

Docker Images

Docker images are listed in the images.txt file included with the Helm Chart package.
Refer to the Working with PlainID DockerHub article for details on authenticating with the PlainID private Docker Hub repository.

Installing the pack

Basic installation

  1. Get the pack and navigate to its directory.

    cd deploy/terraform/plainid-paa-ecs
    
  2. Create your variables file from the provided example.

    cp terraform.tfvars.example terraform.tfvars
    

    terraform.tfvars.example lists every available setting with comments. Copying it gives you a starting point you can trim down to just what you need. Any setting you leave out of terraform.tfvars falls back to its default value, so you only need to include what you want to change.

  3. Edit terraform.tfvars with your environment details. At minimum, set:

    • vpc_id and your subnet IDs
    • ecs_cluster_arn (an existing cluster) or create_ecs_cluster = true (have the pack create one)
    • redis_password or redis_password_ssm_parameter_arn
    • agent_secret_key or agent_secret_key_ssm_parameter_arn
    • Your PAA identity: tenant_id, paa_id, and remote_api (from Platform Tenant Settings)

    See Configuring services, Secrets, and Redis modes for the full set of options and how each one behaves.

  4. Initialize Terraform. This downloads the AWS provider and sets up the working directory.

    terraform init
    
  5. Review the plan. This shows every resource Terraform will create, change, or destroy, without making any changes yet. Read through it to confirm it matches what you expect, especially the VPC, subnets, and cluster.

    terraform plan
    
  6. Apply. This creates the resources in AWS. Terraform shows the plan again and asks you to confirm before proceeding.

    terraform apply
    

    A first-time apply typically takes a few minutes while ECS provisions tasks, registers them in Cloud Map, and runs initial health checks.

  7. Confirm the services are healthy. In the AWS Console, check that each ECS service under your cluster shows its tasks as RUNNING and passing health checks. You can also check the CloudWatch Logs group for each service if a task fails to start.

Once applied, clients inside your VPC can reach each service at:

agent.plainid-paa.internal:8080
runtime.plainid-paa.internal:8010
pip-operator.plainid-paa.internal:8080
secrets-mgmt.plainid-paa.internal:8080

Adjust the namespace suffix to match your service_discovery_namespace value if you changed it from the default.

Uninstalling

To remove the deployment and all its AWS resources:

terraform destroy

Terraform shows every resource it will remove and asks for confirmation before proceeding. Keep in mind:

  • If running redis_mode = "ecs" (the default), destroying the deployment also removes that Redis task and any data it holds, since it has no persistent storage. If you need to keep that data, back it up first or move to external mode before destroying.
  • SSM parameters that the pack created for your secrets (in plaintext mode) are removed as well. Parameters you created yourself and referenced by ARN (bring-your-own-ARN mode) aren't managed by Terraform and stay in place.
  • This action isn't reversible. Confirm you're pointed at the right AWS account and state file before running it, especially if you manage multiple environments from the same working directory.

Upgrading

  1. Update the version. In terraform.tfvars, change paa_version to upgrade every service at once, or set <service>.image_tag on an individual service to upgrade it independently of the others.

  2. Review the plan. Run terraform plan to confirm only the intended service(s) will change.

  3. Apply the change.

    terraform apply
    

    Each affected ECS service performs a rolling deployment: it starts new tasks on the updated version alongside the old ones, waits for them to pass health checks, then shifts traffic over and stops the old tasks. If the new version fails its health checks, the deployment circuit breaker automatically rolls the service back to the previous version, so you don't need to intervene manually.

  4. Verify. Check the ECS service events (in the console or via aws ecs describe-services) to confirm the deployment completed and the new task definition revision is the one running.

Configuring services

Use terraform.tfvars to configure each service. Each top-level key (agent, runtime, pip_operator, idp_webhook, secrets_mgmt) accepts the settings for that service.

ECS cluster

You have two options:

  • Use an existing cluster (default). Set ecs_cluster_arn to the ARN of a cluster you already manage. The pack deploys the PAA services into it alongside anything else running there.
  • Have the pack create a cluster for you. Set create_ecs_cluster = true. In this mode, the pack ignores ecs_cluster_arn and instead provisions a new cluster registered with both the FARGATE and FARGATE_SPOT capacity providers (FARGATE is the default strategy). Optionally set container_insights = true to enable CloudWatch Container Insights on the new cluster for additional monitoring.

Scaling

Autoscaling is CPU target-tracking, configured per service. For each service you want to scale automatically:

  1. Set <service>.autoscaling.enabled = true.
  2. Set the minimum and maximum task count bounds, for example min_capacity and max_capacity.
  3. Set the target CPU utilization percentage the scaling policy should track.

Once autoscaling is running, Application Auto Scaling owns the task count for that service, so don't expect desired_count to stay fixed after deployment. If you check the ECS console and see a different task count than what's in terraform.tfvars, that's Auto Scaling responding to load, not configuration drift.

Logging

Each service writes to its own CloudWatch Logs group, named /<name_prefix>/<service> (for example, /plainid-paa/runtime), with the log stream prefixed by the service name. The top-level log_retention_days setting controls retention uniformly across all services; increase it if you need logs to persist longer for auditing or troubleshooting, or decrease it to reduce storage costs. You can view logs live by tailing the group in the CloudWatch console, or with:

aws logs tail /<name_prefix>/<service> --follow

Secrets

You have two options for handling the Redis password and the agent's CLIENT_SECRET_KEY:

Mode What you set Where the value lives
Plaintext (default) redis_password / agent_secret_key Terraform writes the value to an SSM SecureString parameter for you. The value is marked sensitive so Terraform won't print it in plan or apply output, but it does remain in Terraform state.
Bring your own ARN redis_password_ssm_parameter_arn / agent_secret_key_ssm_parameter_arn You create the SSM parameter yourself, outside Terraform. The plaintext value never enters Terraform state.

Set exactly one option per secret. For high-sensitivity environments, use the bring-your-own-ARN mode.

Each service also has an extra_secret_env setting for additional secret environment variables, mapping an environment variable name to a secret ARN. Use this when a service needs credentials beyond the Redis password and agent secret key covered above, for example a database password or a third-party API token that pip-operator reads at startup.

extra_secret_env accepts either an SSM Parameter Store ARN or a Secrets Manager ARN per entry, so you can mix sources or standardize on whichever your organization already uses.

Example:

pip_operator = {
  extra_secret_env = {
    PIP_DB_PASSWORD = "arn:aws:ssm:...:parameter/plainid-paa/pip/db-password"
    SOME_API_TOKEN  = "arn:aws:ssm:...:parameter/plainid-paa/pip/api-token"
  }
}

If you keep your secrets in Secrets Manager instead, point the same keys at Secrets Manager ARNs:

pip_operator = {
  extra_secret_env = {
    PIP_DB_PASSWORD = "arn:aws:secretsmanager:us-east-2:123456789012:plainid-paa/pip/db-password-AbCdEf"
    SOME_API_TOKEN  = "arn:aws:secretsmanager:us-east-2:123456789012:plainid-paa/pip/api-token-GhIjKl"
  }
}

Redis modes

Set redis_mode to choose where Redis runs:

redis_mode Behavior
ecs (default) The pack runs Redis as a Fargate task in your ECS cluster. It's registered in Cloud Map as redis.<service_discovery_namespace>. You provide redis_password_ssm_parameter_arn; the Redis container reads its password from SSM.
external You manage Redis yourself, typically with Amazon ElastiCache. Set redis_host to your Redis endpoint and redis_password_ssm_parameter_arn to your SSM parameter.

The ecs mode runs as a single task with no persistence, which is convenient for getting started since it requires no external dependency. For staging and production environments where you need high availability and durability, use external mode with ElastiCache, which supports multi-AZ failover and encryption in transit.

Container images

Each service's image resolves as:

<image_repository, or "<image_registry>/<default-name>" if not set>:<image_tag, or paa_version if not set>
Setting Scope Default When to use it
image_registry Whole deployment plainid You want every service to share one registry and use the default image names.
<service>.image_repository Per service Not set (uses the default) A service needs a fully-qualified repository URL, for example your own ECR repository.
<service>.image_tag Per service Not set (uses paa_version) You want to pin one service to a different version than the rest.
paa_version Whole deployment 5.2622.0 Sets the default version for every service.

Example: per-service ECR repositories

agent = {
  image_repository = "123456789012.dkr.ecr.us-east-2.amazonaws.com/plainid-agent"
}
runtime = {
  image_repository = "123456789012.dkr.ecr.us-east-2.amazonaws.com/plainid-runtime"
}
pip_operator = {
  image_repository = "123456789012.dkr.ecr.us-east-2.amazonaws.com/plainid-pip-operator"
}

Example: one shared ECR registry with default image names

image_registry = "123456789012.dkr.ecr.us-east-2.amazonaws.com"

If you're pulling from a private ECR repository in another AWS account, also set repository_credentials_secret_arn. You don't need this if the ECR repository is in the same account as your task execution role, since that role already has ECR pull permissions.


If you require additional help or support, contact PlainID Support.

© 2026 PlainID LTD. All rights reserved.