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

# Customer Placements

> Move agent VMs out of Murmur's infrastructure and into your own GCP project or AWS account — keep data and network traffic inside your perimeter.

By default [agent](/concepts/agents) VMs run on Murmur's infrastructure. To move them into your own GCP or AWS account, set up a customer [Placement](/concepts/placement). The result: each VM runs in your project, on your subnet, billed to you, reachable by your internal services. See [placement](/catalog/placement) for the full resource spec.

This is advanced. Most teams don't need it. Common reasons to do it anyway:

* **Compliance**: security team requires compute in your own account
* **Data residency**: regulations pin you to a specific region
* **VPC-internal access**: agents need databases, services, or APIs not on the public internet
* **Custom networking**: specific firewall rules, proxy, or VPN

Murmur authenticates into your cloud via **OIDC token exchange** (Workload Identity Federation). No service-account JSON, no long-lived keys, no rotation burden. The trust policy is scoped to exactly your tenant ID, so no other Murmur tenant can ever reach your resources.

## GCP

### 1. Apply the Terraform module

The customer modules are public at [`github.com/prassoai/terraform-modules`](https://github.com/prassoai/terraform-modules). Consume them via a git source pinned to a release tag with `?ref=` — never a branch:

```hcl theme={null}
module "murmur_placement" {
  source = "git::https://github.com/prassoai/terraform-modules.git//modules/gcp-wif?ref=v0.2.0"

  project_id          = "your-project-id"
  tenant_id           = "github_app/your-org" # from `murmur tenant whoami` or the dashboard
  vm_service_accounts = ["murmur-vm@your-project-id.iam.gserviceaccount.com"]

  # Placement-shape inputs — set these and the module emits a sync-ready
  # `placement` output you can pipe straight into the CLI (step 2).
  placement_name   = "my-gcp" # must not start with "murmur-"
  placement_zones  = ["us-central1-a", "us-central1-b"]
  placement_subnet = "projects/your-project-id/regions/us-central1/subnetworks/agents"
}
```

`vm_service_accounts` lists the runtime service accounts your agents run as; the module grants the WIF service account permission to attach them at VM-create time. The module creates a Workload Identity Federation pool and provider, the VM-creator and read-only service accounts, a tight custom IAM role, and the bindings — all scoped to your tenant. Murmur exchanges its OIDC token for short-lived GCP credentials; nothing long-lived lives on the agent VMs.

### 2. Sync the Placement

As of `v0.2.0` the module assembles the full [Placement](/catalog/placement) from its own WIF outputs plus the `placement_*` inputs and exposes it as the `placement` output. Pipe it straight into [`murmur set`](/cli/set) — no hand-copying WIF resource names:

```bash theme={null}
terraform output -json placement | murmur set placement my-gcp
```

The output is `null` until `placement_name` is set, so the module also works for WIF-only setups. By default every tenant member may spawn agents under the placement; set `default_spawn_grant` (or `spawn_grants_by_sa`, keyed by SA email) to scope it to named groups or users.

### 3. Rebake your Images into your project

GCE images are project-bound. Murmur's platform [Images](/concepts/images) aren't visible from your project, so you have to bake fresh ones against the new Placement with [`murmur bake`](/cli/bake):

```bash theme={null}
murmur bake my-recipe default my-gcp
```

This boots a scratch VM in **your** project, runs your [Recipe](/concepts/recipe)'s provisioning script there, snapshots the disk, and registers the resulting Image. Parallel bakes are independent workflows and don't contend.

### 4. Point a Workspace at the Placement

```bash theme={null}
cat <<EOF | murmur set workspace my-workspace
name: my-workspace
placement: my-gcp
environment_ref: default
image_ref: my-recipe
repos:
  - clone_url: https://github.com/your-org/your-repo
    base_branch: main
EOF
```

Spawn an agent and verify with [`murmur status`](/cli/status) that the `project` field matches yours. If it doesn't, double-check that the Image you're using was baked against `my-gcp` (different Placements can't share Images). See [Workspaces](/concepts/workspaces) for the full Workspace model.

## AWS

### 1. Apply the Terraform module

```hcl theme={null}
module "murmur_placement" {
  source = "git::https://github.com/prassoai/terraform-modules.git//modules/aws-wif?ref=v0.2.0"

  tenant_id = "github_app/your-org" # from `murmur tenant whoami` or the dashboard

  # Placement-shape inputs — set these and the module emits a sync-ready
  # `placement` output you can pipe straight into the CLI (step 2).
  placement_name              = "my-aws" # must not start with "murmur-"
  placement_region            = "us-east-1"
  placement_vpc_id            = "vpc-0abc123"
  placement_subnet_ids        = ["subnet-0aaa", "subnet-0bbb"]
  placement_security_group_id = "sg-0def456"
}
```

Creates an OIDC provider with a `StringEquals` trust policy scoped to your tenant ID, the VM-creator and read-only IAM roles, and the VM runtime instance profile. The `sub` claim match is what gives you cryptographic isolation: another tenant's OIDC token cannot assume your role, period. The VPC, subnets, and security group are **yours** — the module takes them as inputs and never creates them, so point `placement_vpc_id` / `placement_subnet_ids` / `placement_security_group_id` at networking that already exists in your account.

### 2. Sync the Placement

As of `v0.2.0` the module assembles the full [Placement](/catalog/placement) — deriving `account_id` from the VM-creator role ARN and reading the role, instance-profile, and OIDC-provider ARNs from its own resources — and exposes it as the `placement` output. Pipe it straight into [`murmur set`](/cli/set):

```bash theme={null}
terraform output -json placement | murmur set placement my-aws
```

The output is `null` until `placement_name` is set, so the module also works for federation-only setups. By default every tenant member may spawn agents under the placement; set `default_spawn_grant` to scope it to named groups or users.

### 3. Rebake your Images into your account

AMIs are account-bound, same as GCE images are project-bound:

```bash theme={null}
murmur bake my-recipe default my-aws
```

### 4. Point a Workspace at the Placement

```bash theme={null}
cat <<EOF | murmur set workspace my-workspace
name: my-workspace
placement: my-aws
environment_ref: default
image_ref: my-recipe
repos:
  - clone_url: https://github.com/your-org/your-repo
    base_branch: main
EOF
```

Verify with `murmur status <slug>`; `account_id` and `region` should match yours.

## What changes operationally

* **Billing splits**: cloud spend (VMs, network, storage) bills to your account. Murmur's per-agent-hour charge is separate.
* **Region pinning**: VMs only run where your Placement allows. A Workspace using `my-gcp` with `zones: ["us-central1-a"]` can never spawn outside that zone.
* **Networking is yours**: your firewall rules, your routes, your egress. Agents reach your internal services as if they lived in your VPC.
* **You own incident response**: if a VM misbehaves, it's in your project; you can grab logs, snapshot disks, or shut it down directly.
