Skip to content

deployment patterns

Aleksej Komnenovic edited this page Jun 30, 2026 · 3 revisions

Deployment Patterns

Five deployment topologies. Example code in aws/examples/.

Pattern 1: Full Stack

Directory: aws/examples/full-stack/

Deploys everything: VPC + Database + Storage + BRMS + Agent

flowchart TD
    subgraph VPC["VPC (created)"]
        subgraph Public[Public Subnets]
            ALBs[ALBs]
        end
        subgraph Private[Private Subnets]
            BRMS[BRMS ECS Service]
            Agent[Agent ECS Service]
            Aurora[Aurora PostgreSQL]
        end
        ALBs --> BRMS
        ALBs --> Agent
    end
    S3[S3 Bucket\nrules storage]
Loading

When to use

  • Single-account deployments
  • Complete self-contained environments
  • Development and testing

Key configuration

  • vpc.create = true
  • storage != null
  • database != null (provide min/max capacity)
  • brms != null (provide cpu, memory, domain)
  • agent != null (provide cpu, memory)

Pattern 2: Agent Only

Directory: aws/examples/agent-only/

Stateless rule execution: no database, no BRMS.

flowchart TD
    subgraph VPC["VPC (created)"]
        subgraph Public[Public Subnets]
            ALB[Agent ALB]
        end
        subgraph Private[Private Subnets]
            Agent[Agent ECS Service]
        end
        ALB --> Agent
    end
    S3[S3 Bucket\nrules storage, read-only]
Loading

When to use

  • Dedicated rule execution environments
  • High-throughput stateless processing
  • Part of multi-environment setup

Key configuration

  • database = null
  • brms = null
  • agent != null
  • Agent gets read-only S3 access via IAM Architecture

Pattern 3: Existing VPC

Directory: aws/examples/existing-vpc/

Integrate with an already-provisioned VPC.

flowchart TD
    subgraph VPC["Existing VPC (not managed)"]
        subgraph Public[Existing Public Subnets]
            ALBs[ALBs]
        end
        subgraph Private[Existing Private Subnets]
            BRMS[BRMS ECS Service]
            Agent[Agent ECS Service]
            Aurora[Aurora PostgreSQL]
        end
        ALBs --> BRMS
        ALBs --> Agent
    end
    S3[S3 Bucket\nrules storage]
Loading

When to use

  • Enterprise environments with centralized networking
  • Shared VPCs managed by a platform team
  • Compliance requirements for network topology

Key configuration

vpc = {
  create             = false
  id                 = "vpc-0abc123..."
  private_subnet_ids = ["subnet-0aaa...", "subnet-0bbb..."]
  public_subnet_ids  = ["subnet-0ccc...", "subnet-0ddd..."]
}

Validation in the Root Module requires id and at least one private subnet ID when create = false. Public subnets are optional: provide public_subnet_ids only when an ALB is internet-facing (alb_internal = false). For internal-only deployments you can pass public_subnet_ids = [] (see Pattern 5).

Pattern 4: Multi-Environment

Directory: aws/examples/multi-environment/

Cross-account deployment with centralized BRMS and distributed Agents.

flowchart LR
    subgraph Mgmt[Management Account]
        BRMS[BRMS Editor]
        DB[Database]
        AgentDev[DEV Agent]
        S3Mgmt[S3\nDEV bucket]
        S3Mgmt --> AgentDev
    end
    subgraph Staging[Staging Account]
        AgentStg[Agent]
        S3Stg[S3\nstaging bucket\ngrants write to mgmt]
        S3Stg --> AgentStg
    end
    subgraph Prod[Production Account]
        AgentProd[Agent]
        S3Prod[S3\nprod bucket\ngrants write to mgmt]
        S3Prod --> AgentProd
    end
    BRMS -- "Cross-Account Write" --> S3Stg
    BRMS -- "Cross-Account Write" --> S3Prod
Loading

Deployment Order (Critical)

  1. Staging first: creates Agent infrastructure and its own S3 bucket
  2. Production second: creates Agent infrastructure and its own S3 bucket
  3. Management last: creates BRMS + DB + DEV Agent, referencing the staging/prod bucket ARNs

This order matters because the management account must reference the actual S3 bucket ARNs of the staging and prod accounts (via brms.external_buckets), and those ARNs only exist after staging and prod are deployed. The reverse is not a constraint: the staging/prod buckets grant cross-account write access using only the management account's 12-digit account ID (cross_account_write_principals), a static value known in advance.

Cross-Account S3

The grant lives on the staging and prod buckets, not on the management bucket. Each downstream account creates its own bucket and grants the management account write access to it:

# In staging-account and prod-account:
storage = {
  cross_account_write_principals = [var.management_account_id]
}

The management account does not set cross_account_write_principals; its storage block is just storage = {}. Instead it lists the staging and prod bucket details in brms.external_buckets, which grants the BRMS task role IAM permission to publish rules into those buckets:

# In management-account:
storage = {}

brms = {
  # ...
  external_buckets = [
    { arn = "arn:aws:s3:::gorules-staging-rules-abc12345", name = "gorules-staging-rules-abc12345" },
    { arn = "arn:aws:s3:::gorules-prod-rules-def67890", name = "gorules-prod-rules-def67890" },
  ]
}

The centralized BRMS in the management account writes (publishes rules) into the staging and prod buckets. Each environment's Agent reads rules from its own local bucket. See Storage Module for how the bucket policy is constructed.

Each environment owns its bucket

Each environment (staging, prod) creates its own S3 bucket and grants the management account cross-account write access. The local Agent reads from that same-account bucket while BRMS in the management account writes rules into it. There is no create_bucket = false or existing_bucket_arn in the multi-environment example; the data flows the other way, with management publishing into each environment's bucket.

# In staging/prod accounts: create a local bucket, let management write to it
storage = {
  cross_account_write_principals = [var.management_account_id]
}

Pattern 5: Internal / Private (no public subnets)

Directory: aws/examples/internal-alb/

Both load balancers use the internal scheme in private subnets. Nothing is created in a public subnet. Use this when policy forbids public-subnet resources, or when the services are reached only from inside the network.

flowchart TD
    Clients((Internal network))
    License[[portal.gorules.io]]
    subgraph VPC["VPC (module creates nothing public)"]
        subgraph Private[Private Subnets]
            ALBs[Internal ALBs]
            BRMS[BRMS ECS Service]
            Agent[Agent ECS Service]
            Aurora[Aurora PostgreSQL]
        end
    end
    Egress[Your egress: NAT / TGW / proxy]
    Clients --> ALBs
    ALBs --> BRMS
    ALBs --> Agent
    BRMS --> Egress --> License
    BRMS --> S3[S3 Bucket]
    Agent --> S3
Loading

When to use

  • Company policy forbids resources in public subnets
  • Services reached only from inside the network
  • Public ingress is layered on separately, for example CloudFront with a VPC origin

BRMS needs egress

BRMS validates its license at https://portal.gorules.io, so a VPC with no egress cannot run BRMS. The Agent is self-contained. This pattern brings a VPC whose egress comes from your network, and the module creates nothing in a public subnet.

Key configuration

vpc = {
  create             = false # bring a VPC that already has egress
  id                 = "vpc-0123456789abcdef0"
  private_subnet_ids = ["subnet-aaa", "subnet-bbb"]
  public_subnet_ids  = []
}

brms = {
  alb_internal        = true
  domain              = "brms.internal.example.com"
  certificate_arn     = "arn:aws:acm:..." # internal ALB still terminates TLS
  allowed_cidr_blocks = ["10.0.0.0/8"]
  # ...
}
agent = { alb_internal = true, /* ... */ }

An internal ALB still terminates TLS, so BRMS still needs HTTPS. Provide certificate_arn (the route53_zone_id auto-issue path needs a publicly resolvable zone), or set alb_http_only = true to run behind a TLS-terminating edge.

A fully air-gapped, module-built VPC (create = true, nat_gateway_mode = "none") has no internet egress, so it suits an agent-only deployment. In a no-NAT VPC the tasks reach AWS through auto-provisioned VPC endpoints, but those never reach Docker Hub. Mirror the default images to a private ECR repository and set agent.image (and brms.image) to the ECR URI, otherwise the tasks fail with CannotPullContainerError. See the internal-alb example and the Internal Load Balancers section of the module README.

HTTP-only behind an edge

If a trusted edge such as CloudFront (VPC origin) terminates HTTPS in front of the ALB, run the ALB over HTTP only and skip its certificate with alb_http_only = true. This is the shape GoRules runs in production.

brms = {
  domain              = "brms.example.com" # public hostname served by the edge
  allowed_cidr_blocks = ["10.0.0.0/8"]
  alb_internal        = true
  alb_http_only       = true # requires alb_internal = true
}

BRMS still requires the browser to use HTTPS, so the edge must provide it. domain is still required because BRMS uses it for APP_URL. See the HTTP-only ALB behind an edge section of the module README.

Auth Mode Combinations

Different security postures are possible:

Component Mode 1 (Simple) Mode 2 (IAM-Native)
Database auth = "secrets" auth = "iam"
Storage auth = "iam" auth = "iam"
BRMS Secrets type = "env" type = "aws-kms"

See Secrets Management for details on each mode.

Clone this wiki locally