Skip to main content

Beta access

The Runlayer Terraform provider is currently in beta. To get access: 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

1

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

Download the provider release

Download the correct release artifact for your OS from the GitHub releases page for runlayer/terraform-provider-runlayer.Example:
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 terraform-provider-runlayer.
3

Create a local override directory

Put the provider binary in its own directory. Example:
mkdir -p ~/.runlayer/terraform-provider
mv ./terraform-provider-runlayer ~/.runlayer/terraform-provider/
chmod +x ~/.runlayer/terraform-provider/terraform-provider-runlayer
4

Tell Terraform to use the local provider

Create a Terraform CLI config file:
provider_installation {
  dev_overrides {
    "stainless-sdks/runlayer" = "/Users/you/.runlayer/terraform-provider"
  }
  direct {}
}
Then point Terraform at it:
export TF_CLI_CONFIG_FILE="$HOME/.terraformrc"
5

Export your Runlayer credentials

export RUNLAYER_API_KEY="rl_..."
export RUNLAYER_BASE_URL="https://your-runlayer-base-url"
Use a user API key here.Organization API keys are not sufficient for Terraform resource management endpoints such as policy creation.
RUNLAYER_BASE_URL should be the API base URL for your environment. If you are not sure which URL to use, ask us.
6

Create your Terraform config

Minimal example:
terraform {
  required_providers {
    runlayer = {
      source = "stainless-sdks/runlayer"
    }
  }
}

provider "runlayer" {}
7

Optional: set the base URL in the provider block

If you prefer explicit provider configuration instead of environment variables, use the provider’s base_url and api_key arguments:
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 runlayer_base_url, but the provider argument itself is base_url.
8

Run Terraform

When you use dev_overrides, go straight to terraform plan or terraform apply. Terraform may warn or fail if you run terraform init first.
terraform providers schema -json > /dev/null
terraform plan
9

Verify the setup

A good first check is:
  • terraform providers schema -json works with your local override
  • once you add a real resource, terraform plan reaches the Runlayer API instead of failing on auth, host, provider install, or network setup

Using variables for users, groups, and roles

If you want stable references in Terraform, declare variables like this:
variable "users" {
  type = map(string)
}

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

variable "roles" {
  type = map(string)
}
Then use them in resources:
principal = {
  type = "group"
  ids  = [var.groups.engineers]
}

How the CLI can help

If your CLI version includes runlayer terraform export, you can generate a tfvars file with stable names for users, groups, and roles. Example:
runlayer terraform export --output runlayer.auto.tfvars
Example output:
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:
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

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

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

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

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

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

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

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