> ## 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.

# Terraform Provider

> Set up the Runlayer Terraform provider and manage Runlayer resources from Terraform.

## Beta access

The Runlayer Terraform provider is currently in beta.

To get access:

* Reach out to us in Slack
* Or email [support@runlayer.com](mailto:support@runlayer.com)

We will help with:

* Private network access if your Runlayer API is not public
* Access to the provider release
* The right base URL for your environment

## What you need

* Terraform installed
* Network access to your Runlayer API
* A Runlayer user API key
* A provider binary from the GitHub releases page for `runlayer/terraform-provider-runlayer`

## Setup

<Steps>
  <Step title="Get private access first">
    If your Runlayer API is only reachable over VPN or another private network, make sure that is working before you start Terraform.
  </Step>

  <Step title="Download the provider release">
    Download the correct release artifact for your OS from the GitHub releases page for <code>runlayer/terraform-provider-runlayer</code>.

    Example:

    ```bash theme={null}
    gh release download \
      --repo runlayer/terraform-provider-runlayer \
      --pattern "*darwin_arm64.zip" \
      --output provider.zip

    unzip provider.zip -d /tmp/tf-provider
    mv /tmp/tf-provider/terraform-provider-runlayer_v* /tmp/tf-provider/terraform-provider-runlayer
    ```

    The binary should end up named <code>terraform-provider-runlayer</code>.
  </Step>

  <Step title="Create a local override directory">
    Put the provider binary in its own directory. Example:

    ```bash theme={null}
    mkdir -p ~/.runlayer/terraform-provider
    mv ./terraform-provider-runlayer ~/.runlayer/terraform-provider/
    chmod +x ~/.runlayer/terraform-provider/terraform-provider-runlayer
    ```
  </Step>

  <Step title="Tell Terraform to use the local provider">
    Create a Terraform CLI config file:

    ```hcl theme={null}
    provider_installation {
      dev_overrides {
        "stainless-sdks/runlayer" = "/Users/you/.runlayer/terraform-provider"
      }
      direct {}
    }
    ```

    Then point Terraform at it:

    ```bash theme={null}
    export TF_CLI_CONFIG_FILE="$HOME/.terraformrc"
    ```
  </Step>

  <Step title="Export your Runlayer credentials">
    ```bash theme={null}
    export RUNLAYER_API_KEY="rl_..."
    export RUNLAYER_BASE_URL="https://your-runlayer-base-url"
    ```

    <Note>
      Use a user API key here.

      Organization API keys are not sufficient for Terraform resource management endpoints such as policy creation.
    </Note>

    <Note>
      <code>RUNLAYER\_BASE\_URL</code> should be the API base URL for your environment. If you are not sure which URL to use, ask us.
    </Note>
  </Step>

  <Step title="Create your Terraform config">
    Minimal example:

    ```hcl theme={null}
    terraform {
      required_providers {
        runlayer = {
          source = "stainless-sdks/runlayer"
        }
      }
    }

    provider "runlayer" {}
    ```
  </Step>

  <Step title="Optional: set the base URL in the provider block">
    If you prefer explicit provider configuration instead of environment variables, use the provider's <code>base\_url</code> and <code>api\_key</code> arguments:

    ```hcl theme={null}
    variable "runlayer_base_url" {
      type = string
    }

    variable "runlayer_api_key" {
      type      = string
      sensitive = true
    }

    provider "runlayer" {
      base_url = var.runlayer_base_url
      api_key  = var.runlayer_api_key
    }
    ```

    If you use this form, the variable name can be <code>runlayer\_base\_url</code>, but the provider argument itself is <code>base\_url</code>.
  </Step>

  <Step title="Run Terraform">
    When you use <code>dev\_overrides</code>, go straight to <code>terraform plan</code> or <code>terraform apply</code>. Terraform may warn or fail if you run <code>terraform init</code> first.

    ```bash theme={null}
    terraform providers schema -json > /dev/null
    terraform plan
    ```
  </Step>

  <Step title="Verify the setup">
    A good first check is:

    * <code>terraform providers schema -json</code> works with your local override
    * once you add a real resource, <code>terraform plan</code> reaches the Runlayer API instead of failing on auth, host, provider install, or network setup
  </Step>
</Steps>

## Using variables for users, groups, and roles

If you want stable references in Terraform, declare variables like this:

```hcl theme={null}
variable "users" {
  type = map(string)
}

variable "groups" {
  type = map(string)
}

variable "roles" {
  type = map(string)
}
```

Then use them in resources:

```hcl theme={null}
principal = {
  type = "group"
  ids  = [var.groups.engineers]
}
```

## How the CLI can help

The CLI includes `uvx runlayer terraform export` to generate a tfvars file with stable names for users, groups, and roles.

Example:

```bash theme={null}
uvx runlayer terraform export --output runlayer.auto.tfvars
```

Example output:

```hcl theme={null}
users = {
  marcin_at_runlayer_com = "11111111-1111-1111-1111-111111111111"
}

groups = {
  engineers = "22222222-2222-2222-2222-222222222222"
}

roles = {
  admin = "33333333-3333-3333-3333-333333333333"
}
```

That file can be loaded by Terraform and referenced like:

```hcl theme={null}
var.users.marcin_at_runlayer_com
var.groups.engineers
var.roles.admin
```

This is useful when one person refreshes IDs occasionally and the rest of the Terraform code stays readable.

## Policy examples

These examples are based on the provider e2e coverage and show common policy shapes.

### Allow one group full access to one server

```hcl theme={null}
resource "runlayer_policy" "allow_engineers_linear" {
  action = "allow"

  principal = {
    type = "group"
    ids  = [var.groups.engineers]
  }

  scope = {
    servers   = [runlayer_server.linear.id]
    tools     = ["*"]
    resources = ["*"]
  }

  description = "Engineers: full Linear access"
}
```

### Allow one group read-only access to a server

```hcl theme={null}
resource "runlayer_policy" "allow_contractors_deepwiki_readonly" {
  action = "allow"

  principal = {
    type = "group"
    ids  = [var.groups.contractors]
  }

  scope = {
    servers   = [runlayer_server.deepwiki.id]
    tools     = ["search_documentation", "get_library_docs", "resolve_library_id"]
    resources = ["*"]
  }

  description = "Contractors: read-only DeepWiki"
}
```

### Deny one group access to one server

```hcl theme={null}
resource "runlayer_policy" "deny_contractors_linear" {
  action = "deny"

  principal = {
    type = "group"
    ids  = [var.groups.contractors]
  }

  scope = {
    servers   = [runlayer_server.linear.id]
    tools     = ["*"]
    resources = ["*"]
  }

  description = "Contractors: no Linear access"
}
```

### Deny tools by name prefix

```hcl theme={null}
resource "runlayer_policy" "deny_destructive_tools" {
  action = "deny"

  principal = {
    type = "any"
  }

  scope = {
    servers   = [runlayer_server.linear.id]
    tools     = ["*"]
    resources = ["*"]
  }

  conditions = [{
    rules = [{
      field      = "tool_name"
      operator   = "begins_with"
      value      = "delete"
      report_raw = false
    }]
  }]

  description = "Deny any tool starting with delete"
}
```

### Deny access outside a private IP range

```hcl theme={null}
resource "runlayer_policy" "deny_internal_outside_vpn" {
  action = "deny"

  principal = {
    type = "any"
  }

  scope = {
    servers   = [runlayer_server.internal_tools.id]
    tools     = ["*"]
    resources = ["*"]
  }

  conditions = [{
    rules = [{
      field      = "meta.request.ip"
      operator   = "not_ip_range"
      value      = "10.0.0.0/8"
      report_raw = true
    }]
  }]

  description = "Deny internal tools access from outside VPN"
}
```

### Deny inactive users globally

```hcl theme={null}
resource "runlayer_policy" "deny_inactive_users" {
  action = "deny"

  principal = {
    type = "any"
  }

  scope = {
    servers   = ["*"]
    tools     = ["*"]
    resources = ["*"]
  }

  conditions = [{
    rules = [{
      field      = "meta.subject.is_active"
      operator   = "equals"
      value      = "false"
      report_raw = false
    }]
  }]

  description = "Global: deny all access for inactive users"
}
```

### Deny everything except a small read-only tool set

```hcl theme={null}
resource "runlayer_policy" "deny_contractors_internal_writes" {
  action = "deny"

  principal = {
    type = "group"
    ids  = [var.groups.contractors]
  }

  scope = {
    servers   = [runlayer_server.internal_tools.id]
    tools     = ["*"]
    resources = ["*"]
  }

  conditions = [{
    rules = [
      {
        field      = "tool_name"
        operator   = "not_equals"
        value      = "read_data"
        report_raw = false
      },
      {
        field      = "tool_name"
        operator   = "not_equals"
        value      = "list_items"
        report_raw = false
      },
    ]
  }]

  description = "Contractors: deny internal tools except read_data and list_items"
}
```

## Troubleshooting

* `terraform init` fails under `dev_overrides`: skip `init` and run `terraform plan` directly
* Terraform cannot find the provider: check `TF_CLI_CONFIG_FILE` and the `dev_overrides` path
* `terraform apply` returns `403 Forbidden`: make sure you are using a user API key, not an organization API key
* The provider binary does not run: make sure it is executable
* API calls fail: verify `RUNLAYER_API_KEY`
* API calls hit the wrong environment: verify `RUNLAYER_BASE_URL`
* Requests time out or refuse to connect: confirm private network access first
