> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runlayer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# External Dependencies

> Postgres, Redis, buckets, streams, and sample Terraform for EKS (AWS) and GKE (GCP)

The operator does **not** create cloud infrastructure. Provision these out-of-band, then wire hosts, ARNs, and bucket names into Secrets / `RunlayerInstance`.

Samples below are **illustrative** — adapt to your modules, naming, and security baselines. They are not a required Runlayer Terraform product.

Cluster contract: [Kubernetes prerequisites](/deployment/kubernetes-prerequisites). Identity: [Workload identity](/deployment/workload-identity).

## Required for every tenant

| Dependency     | Purpose           | AWS                                         | GCP                                     |
| -------------- | ----------------- | ------------------------------------------- | --------------------------------------- |
| **PostgreSQL** | Primary datastore | Aurora / RDS PostgreSQL                     | Cloud SQL for PostgreSQL                |
| **Redis**      | Cache / queues    | ElastiCache (TLS recommended)               | Memorystore for Redis                   |
| **DNS + TLS**  | `spec.domain`     | Route 53 + ACM (typical)                    | Cloud DNS + managed / cert-manager cert |
| **Images**     | App containers    | ECR or Customer Distribution `088332244652` | GAR mirror / pullable registry          |

Map into CR: `spec.database.*`, `spec.redis.*`, Secrets `runlayer-db` / `runlayer-redis`.

### Postgres — AWS sample

```hcl theme={null}
module "tenant_rds" {
  source  = "terraform-aws-modules/rds-aurora/aws"
  version = "~> 9.0"

  name           = "${local.name_prefix}-db"
  engine         = "aurora-postgresql"
  engine_version = "16.4"
  engine_mode    = "provisioned"

  vpc_id               = var.vpc_id
  db_subnet_group_name = aws_db_subnet_group.tenant.name
  security_group_rules = {
    vpc_ingress = { cidr_blocks = var.vpc_cidrs }
  }

  master_username             = "runlayer"
  manage_master_user_password = true
  database_name               = "runlayer"
  storage_encrypted           = true

  serverlessv2_scaling_configuration = {
    min_capacity = 0.5
    max_capacity = 16
  }

  instances = {
    1 = { instance_class = "db.serverless", publicly_accessible = false }
  }
}
```

Requirements: private subnets, SSL (`rds.force_ssl`), backups/PITR per your policy, allow node/pod CIDRs to 5432.

### Postgres — GCP sample

```hcl theme={null}
resource "google_sql_database_instance" "primary" {
  name             = "${local.name_prefix}-pg"
  region           = var.region
  database_version = "POSTGRES_16"
  deletion_protection = true

  settings {
    tier              = "db-custom-2-7680"
    availability_type = "REGIONAL"
    disk_autoresize   = true
    ip_configuration {
      ipv4_enabled    = false
      private_network = google_compute_network.main.id
    }
    backup_configuration {
      enabled                        = true
      point_in_time_recovery_enabled = true
    }
  }

  depends_on = [google_service_networking_connection.private_vpc_connection]
}
```

### Redis — AWS sample

```hcl theme={null}
resource "aws_elasticache_replication_group" "tenant" {
  replication_group_id = "${local.name_prefix}-redis"
  description          = "Redis for Runlayer tenant"
  engine               = "redis"
  engine_version       = "7.0"
  node_type            = "cache.t4g.medium"
  port                 = 6379
  num_cache_clusters   = 1

  subnet_group_name  = aws_elasticache_subnet_group.tenant.name
  security_group_ids = [aws_security_group.tenant_redis.id]

  at_rest_encryption_enabled = true
  transit_encryption_enabled = true
  transit_encryption_mode    = "required"
}
```

Wire `spec.redis.tls: true` and password Secret when auth is enabled.

### Redis — GCP sample

```hcl theme={null}
resource "google_redis_instance" "main" {
  name               = "${local.name_prefix}-redis"
  tier               = "STANDARD_HA"
  memory_size_gb     = 5
  region             = var.region
  redis_version      = "REDIS_7_0"
  authorized_network = google_compute_network.main.id
  connect_mode       = "PRIVATE_SERVICE_ACCESS"
  auth_enabled       = true
  transit_encryption_mode = "SERVER_AUTHENTICATION"
}
```

***

## Optional: AI Watch binary packages

<a id="ai-watch-binary-packages" />

Needed so the platform can cache and serve AI Watch installers (`RUNLAYER_DOWNLOAD_TOKEN` from [Runlayer-provided inputs](/deployment/runlayer-provided-inputs)).

|                | AWS                                                                              | GCP                                                                                                  |
| -------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| **Store**      | Private S3 bucket                                                                | Private GCS bucket                                                                                   |
| **App config** | `BINARY_PACKAGES_S3_BUCKET` on `runlayer-app`                                    | Equivalent GCS bucket name / env — ask Runlayer (no first-party GCS sample in our GKE reference yet) |
| **IAM**        | `{instance}-backend` (+ worker): `s3:ListBucket`, `s3:GetObject`, `s3:PutObject` | Same principals: `storage.objects.get/create/list` on the bucket                                     |
| **Settings**   | SSE-S3/KMS, block public access, versioning optional                             | Uniform bucket-level access; no public ACLs                                                          |

### AWS sample

```hcl theme={null}
resource "aws_s3_bucket" "binary_packages" {
  bucket = "${var.account}-${var.project}-binary-packages"
}

resource "aws_s3_bucket_public_access_block" "binary_packages" {
  bucket                  = aws_s3_bucket.binary_packages.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_server_side_encryption_configuration" "binary_packages" {
  bucket = aws_s3_bucket.binary_packages.id
  rule {
    apply_server_side_encryption_by_default { sse_algorithm = "AES256" }
  }
}

# Attach to the IRSA role for {instance}-backend / -worker
data "aws_iam_policy_document" "binary_packages" {
  statement {
    actions   = ["s3:ListBucket"]
    resources = [aws_s3_bucket.binary_packages.arn]
  }
  statement {
    actions   = ["s3:GetObject", "s3:PutObject"]
    resources = ["${aws_s3_bucket.binary_packages.arn}/*"]
  }
}
```

After config: restart backend/worker → **Check now** (or wait for scheduled discovery).

***

## Optional: audit stream + consumers

Backend can publish audit events to a stream. **Audit consumer** and **SIEM export** are independent consumers of that stream (each needs its own EFO/subscription, checkpoint store, DLQ, and IAM).

| Piece       | AWS                                       | GCP                                            |
| ----------- | ----------------------------------------- | ---------------------------------------------- |
| Stream      | Kinesis Data Stream                       | Pub/Sub topic                                  |
| Consumer    | Enhanced fan-out consumer ARN             | Pub/Sub subscription                           |
| Checkpoints | DynamoDB table                            | (subscription ack / your pattern)              |
| DLQ         | S3 bucket                                 | GCS bucket or DLQ topic                        |
| CR          | `components.auditConsumer` / `siemExport` | `streamBackend: pubsub` + `spec.gcp.projectId` |

Kinesis SIEM export and audit consumer: **replicas ≤ 1**.

### GCP Pub/Sub sample (audit)

```hcl theme={null}
resource "google_pubsub_topic" "audit_events" {
  name = "${local.name_prefix}-audit-events"
}

resource "google_pubsub_subscription" "audit_events_persistence" {
  name  = "${local.name_prefix}-audit-persistence"
  topic = google_pubsub_topic.audit_events.id
  ack_deadline_seconds = 60

  dead_letter_policy {
    dead_letter_topic     = google_pubsub_topic.audit_events_dead_letter.id
    max_delivery_attempts = 50
  }
}

# Bind subscriber to audit-consumer KSA principal (Workload Identity)
resource "google_pubsub_subscription_iam_member" "audit_consumer" {
  subscription = google_pubsub_subscription.audit_events_persistence.name
  role         = "roles/pubsub.subscriber"
  member       = "principal://iam.googleapis.com/projects/${data.google_project.current.number}/locations/global/workloadIdentityPools/${var.project_id}.svc.id.goog/subject/ns/${var.platform_namespace}/sa/${var.instance_name}-audit-consumer"
}
```

Publish permission for `{instance}-backend` and `{instance}-worker` on the topic.

### AWS Kinesis sample (audit + SIEM on one stream)

Each consumer needs its **own** EFO consumer, DynamoDB checkpoint table, and DLQ bucket. SIEM also needs a destination bucket (or cross-account write role).

```hcl theme={null}
resource "aws_kinesis_stream" "audit_log" {
  name             = "${local.name_prefix}-audit-log"
  retention_period = 168
  encryption_type  = "KMS"
  kms_key_id       = "alias/aws/kinesis"
  stream_mode_details { stream_mode = "ON_DEMAND" }
}

locals {
  kinesis_consumers = {
    audit = "${local.name_prefix}-audit-log-consumer"
    siem  = "${local.name_prefix}-siem-export"
  }
}

resource "aws_kinesis_stream_consumer" "this" {
  for_each   = local.kinesis_consumers
  name       = each.value
  stream_arn = aws_kinesis_stream.audit_log.arn
}

resource "aws_dynamodb_table" "checkpoints" {
  for_each     = local.kinesis_consumers
  name         = "${each.value}-checkpoints"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "stream_arn"
  range_key    = "shard_id"
  attribute {
    name = "stream_arn"
    type = "S"
  }
  attribute {
    name = "shard_id"
    type = "S"
  }
  server_side_encryption { enabled = true }
}

resource "aws_s3_bucket" "dlq" {
  for_each = local.kinesis_consumers
  bucket   = "${var.account}-${each.value}-dlq"
}

resource "aws_s3_bucket_public_access_block" "dlq" {
  for_each                = aws_s3_bucket.dlq
  bucket                  = each.value.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket" "siem_destination" {
  bucket = "${var.account}-${local.name_prefix}-siem-export"
}

# Publishers (backend / worker IRSA)
data "aws_iam_policy_document" "audit_publish" {
  statement {
    actions   = ["kinesis:PutRecord", "kinesis:PutRecords", "kinesis:DescribeStream"]
    resources = [aws_kinesis_stream.audit_log.arn]
  }
}

# Per-consumer IRSA ({instance}-audit-consumer / {instance}-siem-export)
data "aws_iam_policy_document" "kinesis_consumer" {
  for_each = local.kinesis_consumers
  statement {
    actions = [
      "kinesis:DescribeStream",
      "kinesis:DescribeStreamConsumer",
      "kinesis:SubscribeToShard",
      "kinesis:GetShardIterator",
      "kinesis:GetRecords",
    ]
    resources = [
      aws_kinesis_stream.audit_log.arn,
      aws_kinesis_stream_consumer.this[each.key].arn,
    ]
  }
  statement {
    actions   = ["dynamodb:DescribeTable", "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:Query"]
    resources = [aws_dynamodb_table.checkpoints[each.key].arn]
  }
  statement {
    actions   = ["s3:PutObject", "s3:AbortMultipartUpload"]
    resources = ["${aws_s3_bucket.dlq[each.key].arn}/*"]
  }
}

data "aws_iam_policy_document" "siem_destination_write" {
  statement {
    actions   = ["s3:PutObject", "s3:AbortMultipartUpload"]
    resources = ["${aws_s3_bucket.siem_destination.arn}/*"]
  }
  statement {
    actions   = ["s3:ListBucket"]
    resources = [aws_s3_bucket.siem_destination.arn]
  }
}
```

Attach `audit_publish` to `{instance}-backend` / `-worker`. Attach `kinesis_consumer["audit"]` to `{instance}-audit-consumer`. Attach `kinesis_consumer["siem"]` + `siem_destination_write` to `{instance}-siem-export` (or use a customer-owned assume-role for the destination bucket).

Wire into:

```yaml theme={null}
spec:
  components:
    auditConsumer:
      enabled: true
      streamBackend: kinesis
      kinesis:
        streamArn: arn:aws:kinesis:…
        consumerArn: arn:aws:kinesis:…/consumer/…-audit-log-consumer
        checkpointTableName: …-audit-log-consumer-checkpoints
        deadLetterBucket: …-audit-log-consumer-dlq
    siemExport:
      enabled: true
      streamBackend: kinesis
      kinesis:
        streamArn: arn:aws:kinesis:…
        consumerArn: arn:aws:kinesis:…/consumer/…-siem-export
        checkpointTableName: …-siem-export-checkpoints
        deadLetterBucket: …-siem-export-dlq
      export:
        s3Bucket: …-siem-export
```

***

## Optional: session materializer

<a id="session-materializer" />

| Piece                  | AWS                                             | GCP                                     |
| ---------------------- | ----------------------------------------------- | --------------------------------------- |
| Hook-events stream     | Kinesis                                         | Pub/Sub topic (**message ordering** on) |
| Consumer / checkpoints | EFO or GetRecords + DynamoDB                    | Ordered subscription                    |
| Session payloads       | S3 bucket                                       | GCS bucket                              |
| DLQ                    | S3                                              | GCS                                     |
| Workload               | `{instance}-session-materializer` (singleton)   | Same                                    |
| Publishers             | backend + worker: publish hooks + read payloads | Same                                    |

### AWS sample (hook-events stream + materializer deps)

```hcl theme={null}
resource "aws_kinesis_stream" "hook_events" {
  name             = "${local.name_prefix}-hook-events"
  retention_period = 168
  encryption_type  = "KMS"
  kms_key_id       = "alias/aws/kinesis"
  stream_mode_details { stream_mode = "ON_DEMAND" }
}

resource "aws_dynamodb_table" "session_materializer_checkpoints" {
  name         = "${local.name_prefix}-session-materializer-kinesis-checkpoints"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "stream_arn"
  range_key    = "shard_id"
  attribute {
    name = "stream_arn"
    type = "S"
  }
  attribute {
    name = "shard_id"
    type = "S"
  }
  server_side_encryption { enabled = true }
}

resource "aws_s3_bucket" "session_payloads" {
  bucket = "${var.account}-${local.name_prefix}-session-payloads"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "session_payloads" {
  bucket = aws_s3_bucket.session_payloads.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = "alias/aws/s3"
    }
  }
}

resource "aws_s3_bucket" "session_materializer_dlq" {
  bucket = "${var.account}-${local.name_prefix}-session-materializer-dlq"
}

data "aws_iam_policy_document" "session_materializer" {
  statement {
    actions   = ["kinesis:DescribeStream", "kinesis:GetShardIterator", "kinesis:GetRecords", "kinesis:ListShards"]
    resources = [aws_kinesis_stream.hook_events.arn]
  }
  statement {
    actions   = ["dynamodb:DescribeTable", "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:Query"]
    resources = [aws_dynamodb_table.session_materializer_checkpoints.arn]
  }
  statement {
    actions   = ["s3:GetObject", "s3:PutObject", "s3:ListBucket"]
    resources = [aws_s3_bucket.session_payloads.arn, "${aws_s3_bucket.session_payloads.arn}/*"]
  }
  statement {
    actions   = ["s3:PutObject"]
    resources = ["${aws_s3_bucket.session_materializer_dlq.arn}/*"]
  }
}

data "aws_iam_policy_document" "hook_events_publish" {
  statement {
    actions   = ["kinesis:PutRecord", "kinesis:PutRecords", "kinesis:DescribeStream"]
    resources = [aws_kinesis_stream.hook_events.arn]
  }
  statement {
    actions   = ["s3:GetObject", "s3:ListBucket"]
    resources = [aws_s3_bucket.session_payloads.arn, "${aws_s3_bucket.session_payloads.arn}/*"]
  }
}
```

Attach `session_materializer` to `{instance}-session-materializer`. Attach `hook_events_publish` (+ payload read) to `{instance}-backend` / `-worker`.

```yaml theme={null}
spec:
  components:
    sessionMaterializer:
      enabled: true
      streamBackend: kinesis
      sessionsReadFromPostgres: true
      kinesis:
        streamArn: arn:aws:kinesis:…:stream/hook-events
        checkpointTableName: runlayer-session-materializer-checkpoints
        deadLetterBucket: runlayer-session-materializer-dlq
      payload:
        s3Bucket: runlayer-session-payloads
      hookEvents:
        streamName: hook-events
        region: us-east-1
```

***

## Optional: agents (AgentCore)

<a id="agents-agentcore" />

On AWS, preferred path is **Bedrock AgentCore** (customer-provisioned). The operator does not create the runtime.

| Resource                   | Notes                                                                           |
| -------------------------- | ------------------------------------------------------------------------------- |
| AgentCore runtime          | Runlayer-supported agent-sandbox image/version (ask Runlayer)                   |
| Execution role             | Trust + image pull + logging permissions                                        |
| Files bucket               | Encrypted S3; backend needs `s3:PutObject` + **`s3:PutObjectTagging`**          |
| Optional VPC / PrivateLink | When public runtime networking is not allowed                                   |
| Workload IAM               | backend/worker: `InvokeAgentRuntime`, `StopRuntimeSession`, files-bucket access |

### AWS sample (runtime + invoke policy)

Image URI is typically Customer Distribution ECR, e.g. `088332244652.dkr.ecr.<region>.amazonaws.com/runlayer/agent-sandbox:<version>` — confirm the pin with Runlayer.

```hcl theme={null}
resource "aws_bedrockagentcore_agent_runtime" "agent_sandbox" {
  agent_runtime_name = replace("${local.name_prefix}_agent_sandbox", "-", "_")
  description        = "Runlayer agent sandbox runtime"
  role_arn           = aws_iam_role.agentcore_runtime_execution.arn

  agent_runtime_artifact {
    container_configuration {
      container_uri = var.agent_sandbox_image_uri
    }
  }

  network_configuration {
    network_mode = "PUBLIC" # or VPC + subnet/SG config
  }

  protocol_configuration {
    server_protocol = "HTTP"
  }
}

data "aws_iam_policy_document" "invoke_agentcore" {
  statement {
    actions = [
      "bedrock-agentcore:InvokeAgentRuntime",
      "bedrock-agentcore:StopRuntimeSession",
    ]
    resources = [
      aws_bedrockagentcore_agent_runtime.agent_sandbox.agent_runtime_arn,
      "${aws_bedrockagentcore_agent_runtime.agent_sandbox.agent_runtime_arn}/*",
    ]
  }
}
```

Attach `invoke_agentcore` to the IRSA roles for `{instance}-backend` and `{instance}-worker`.

```yaml theme={null}
spec:
  agents:
    sandboxMode: agentcore
    filesBucket: acme-agent-sandbox-files
    agentcore:
      runtimeArn: arn:aws:bedrock-agentcore:…:runtime/…
      qualifier: DEFAULT
```

`sandboxMode: k8s` is advanced (separate controller + gVisor/Kata) — see [Runlayer Operator](/deployment/runlayer-operator).

***

## Other common buckets (AWS / GCS)

Put names in `runlayer-app` (or CR fields where documented):

| Env / purpose                  | Typical IAM on backend/worker                       |
| ------------------------------ | --------------------------------------------------- |
| `AUDIT_PAYLOAD_S3_BUCKET`      | Get/Put/List (and lifecycle for spool dead letters) |
| `TOOL_OUTPUT_S3_BUCKET`        | Get/Put/List                                        |
| `TOOLGUARD_FEEDBACK_S3_BUCKET` | Get/Put                                             |
| `PROFILER_S3_BUCKET`           | Get/Put                                             |
| Agent `filesBucket`            | Put + PutObjectTagging                              |

Mirror with GCS IAM for GKE.

## Shared GCP project

When any component uses `streamBackend: pubsub`:

```yaml theme={null}
spec:
  gcp:
    projectId: acme-prod
```

## Next

* [Workload identity](/deployment/workload-identity) — map roles to ServiceAccounts
* [Runlayer-provided inputs](/deployment/runlayer-provided-inputs)
* [Runlayer Operator](/deployment/runlayer-operator)
