DEV Community

John  Ajera
John Ajera

Posted on

Terraforming Scalr Configuration - Bootstrap Infrastructure as Code

Terraforming Scalr Configuration - Bootstrap Infrastructure as Code

This guide walks through configuring Terraform to bootstrap and manage Scalr infrastructure using Infrastructure as Code. You'll learn how to create a hierarchical organizational structure (Organization → Environments → Workspaces), set up OIDC authentication for passwordless CI/CD, and automate everything via GitHub Actions.


1. Overview

This is a bootstrap configuration that manages Scalr resources using Terraform. The Terraform state is stored externally (S3) because Scalr doesn't exist yet when you're creating it.

Organizational Structure:

Organization (platformfuzz)
├── Projects (Environments: terraform-fastly, terraform-aws)
│   └── Workspaces (dev, prod)
Enter fullscreen mode Exit fullscreen mode

Key Features:

  • OIDC Authentication: Passwordless authentication via GitHub Actions OIDC (no manual token rotation)
  • Custom Roles: Least-privilege permissions for service accounts
  • Hierarchical Structure: Environments (projects) and workspaces managed as code
  • CI/CD Automation: GitHub Actions workflows for plan/apply
  • Bootstrap Configuration: State stored in S3 (external backend)

2. Prerequisites

Before starting, ensure you have:

  • Terraform >= 1.0
  • A Scalr account (free tier available at scalr.com)
  • AWS Account (for S3 backend state storage)
  • GitHub Account (for CI/CD and optional VCS integration)
  • Basic familiarity with Terraform and GitHub Actions

3. Scalr Configuration

3.1. Get Scalr Account ID

  1. Log in to your Scalr account
  2. Look at the top-left corner for the Green Menu Context (account selector)
  3. Click on the account name to view account details
  4. Copy the Account ID (format: acc-xxxxxxxxxxxxx)
    • Also visible in URL: https://app.scalr.io/#/account/<account-id>/...

3.2. Get Scalr Hostname

  • Default: app.scalr.io (standard Scalr cloud)
  • Custom Domain: your-account.scalr.io (if using custom domain)
  • Self-Hosted: Your organization's Scalr hostname

You can find this in your browser URL when logged into Scalr.

3.3. Create Service Account with Custom Role (Recommended)

Why Custom Roles? Following least-privilege principles, we'll create a custom role with only the permissions needed for Terraform to manage Scalr resources.

Step 1: Create Custom Role
  1. Navigate to SecurityIAMRoles
  2. Click + New role (or Create role)
  3. Enter:
    • Name: terraform-manager (or your preferred name)
    • Description: "Custom role for Terraform to manage Scalr infrastructure"
  4. Click + Add permissions in the "Role permissions" section
  5. Search for and add the following permissions:

Required Permissions:

  • environments:create - To create Scalr environments (projects)
  • environments:read - To read environment details
  • environments:update - To update environments
  • workspaces:create - To create workspaces
  • workspaces:read - To read workspace details
  • workspaces:update - To update workspaces

Optional Permissions (for cleanup):

  • environments:delete - To delete environments
  • workspaces:delete - To delete workspaces

VCS Integration Permissions (only if using VCS):

  • vcs-providers:create - To create VCS providers
  • vcs-providers:read - To read VCS provider details
  • vcs-providers:update - To update VCS providers
  • vcs-providers:delete - To delete VCS providers (optional)
  1. Click Apply and then Create to save the role
Step 2: Create Service Account
  1. Navigate to SecurityIAMService accounts
  2. Click + New service account
  3. Enter:
    • Name: terraform (or your preferred name)
    • Description: "Service account for Terraform infrastructure management"
  4. Click + Grant access to assign roles:
    • Roles: Select the custom role you created (e.g., terraform-manager)
    • Grant on: Select Account for account-level access
    • Click Assign
  5. Click Create (or Assign if the button shows that)
  6. Note the service account email: After creation, Scalr will display the service account email (format: name@account-id.scalr.io, e.g., terraform@acc-xxxxxxxxxxxx.scalr.io). You'll need this for GitHub Actions configuration.

3.4. Configure Scalr OIDC Authentication (No Manual Token Rotation)

Why OIDC? This eliminates the need for static API tokens. GitHub Actions automatically exchanges OIDC tokens for Scalr access tokens - no manual rotation needed!

Step 1: Register Workload Identity Provider
  1. Navigate to SecurityIAMWorkload identity providers
  2. Click + New workload identity provider
  3. Configure:
    • Name: github-actions (or your preferred name)
    • Issuer (URL): https://token.actions.githubusercontent.com
    • Allowed audiences: Your GitHub organization name (e.g., https://github.com/platformfuzz)
  4. Click Create provider
Step 2: Configure Assume Policy for Service Account
  1. In the service account you created, navigate to the Assume policies tab
  2. Click + Create assume policy
  3. Configure:
    • Name: github-actions-assume (or your preferred name)
    • Service account: Select the service account you created
    • Workload identity provider: Select the provider created in the previous step
    • Maximum session duration: 3600 (1 hour, default)
    • Claim conditions: Add conditions to restrict access:
      • Claim: repository
      • Operation: equals (or eq)
      • Value: platformfuzz/tf-scalr (your repository)
  4. Click Create assume policy
  5. Click Save changes

Note: The save operation may take over a minute as Scalr validates the configuration. Wait for it to complete.

Benefits: No API tokens to manage, no manual rotation, automatic authentication via GitHub Actions OIDC.


4. Terraform Configuration

4.1. Provider Configuration (providers.tf)

terraform {
  required_version = ">= 1.0"

  required_providers {
    scalr = {
      source  = "Scalr/scalr"
      version = ">= 3.12"
    }
  }
}

provider "scalr" {
  hostname = var.scalr_hostname
  token    = var.scalr_api_token
}
Enter fullscreen mode Exit fullscreen mode

Note: The token will be automatically provided by the Scalr CLI in GitHub Actions (via OIDC exchange), so var.scalr_api_token can be null when using OIDC.

4.2. Variables (variables.tf)

variable "scalr_api_token" {
  description = "Scalr API token for authentication. Optional if using Scalr OIDC (credentials stored in ~/.scalr/credentials.tfrc.json by Scalr CLI) or 'terraform login'. Best practice: Use OIDC with workload identity providers or service account tokens instead of personal tokens."
  type        = string
  sensitive   = true
  default     = null
}

variable "scalr_account_id" {
  description = "Scalr account ID for account-scoped resources. Found on the Scalr Account Dashboard (Green Menu Context)."
  type        = string
}

variable "scalr_hostname" {
  description = "Scalr hostname (defaults to app.scalr.io). Use your personalized URL (your-account.scalr.io) if using a custom domain."
  type        = string
  default     = "app.scalr.io"
}

variable "github_token" {
  description = "GitHub App installation token or personal access token for VCS integration. For GitHub App, generate an installation token using the App ID, Installation ID, and private key. If not provided, VCS provider will not be created."
  type        = string
  sensitive   = true
  default     = null
}
Enter fullscreen mode Exit fullscreen mode

4.3. Local Values (locals.tf)

Define your organizational structure:

locals {
  # Environments (Projects in organizational structure)
  # Matches Scalr terminology: scalr_environment resource
  environments = {
    terraform-fastly = {
      name       = "terraform-fastly"
      workspaces = ["prod"]
    }
    terraform-aws = {
      name       = "terraform-aws"
      workspaces = ["dev", "prod"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

4.4. Environments (projects.tf)

# Create Scalr Environments (Projects)
# Each environment represents a project in your organizational structure
resource "scalr_environment" "projects" {
  for_each   = local.environments
  name       = each.value.name
  account_id = var.scalr_account_id
}
Enter fullscreen mode Exit fullscreen mode

4.5. Workspaces (workspaces.tf)

# Create Workspaces within each Environment
# This creates the hierarchical structure: Organization -> Projects (Environments) -> Workspaces
resource "scalr_workspace" "workspaces" {
  for_each = merge([
    for env_name, env_config in local.environments : {
      for workspace_name in env_config.workspaces :
      "${env_name}-${workspace_name}" => {
        env_name       = env_name
        workspace_name = workspace_name
      }
    }
  ]...)

  name           = each.value.workspace_name
  environment_id = scalr_environment.projects[each.value.env_name].id
}
Enter fullscreen mode Exit fullscreen mode

4.6. Backend Configuration (backend.tf)

# Bootstrap Configuration: State cannot be stored in Scalr
# This Terraform configuration creates Scalr resources (environments, workspaces, etc.)
# Therefore, the state must be stored externally (S3, local, etc.) until Scalr is set up.
terraform {
  backend "s3" {
    bucket       = "tf-scalr-state"
    key          = "terraform.tfstate"
    region       = "ap-southeast-2"
    encrypt      = true
    use_lockfile = true
  }
}
Enter fullscreen mode Exit fullscreen mode

Why S3? This is a bootstrap configuration - we're creating Scalr itself, so state can't be stored in Scalr initially.


5. GitHub Actions Workflow

5.1. Workflow Configuration (.github/workflows/terraform.yml)

name: Terraform

permissions:
  id-token: write
  contents: read

on:
  push:
    branches:
      - main
    paths:
      - "**.tf"
      - ".github/workflows/terraform.yml"
  pull_request:
    branches:
      - main
    paths:
      - "**.tf"
      - ".github/workflows/terraform.yml"
  workflow_dispatch:

env:
  AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
  TERRAFORM_VERSION: ${{ vars.TERRAFORM_VERSION }}
  SCALR_CLI_VERSION: ${{ vars.SCALR_CLI_VERSION }}
  OIDC_ROLE_NAME: ${{ vars.OIDC_ROLE_NAME }}
  AWS_REGION: ${{ vars.AWS_REGION }}
  TF_VAR_scalr_account_id: ${{ vars.SCALR_ACCOUNT_ID }}
  TF_VAR_scalr_hostname: ${{ vars.SCALR_HOSTNAME }}
  SCALR_SERVICE_ACCOUNT_EMAIL: ${{ vars.SCALR_SERVICE_ACCOUNT_EMAIL }}
  TF_VAR_github_token: ${{ secrets.SCALR_GITHUB_TOKEN }}

jobs:
  terraform:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v5
        with:
          role-to-assume: arn:aws:iam::${{ env.AWS_ACCOUNT_ID }}:role/${{ env.OIDC_ROLE_NAME }}

      - name: Generate GitHub Actions OIDC ID Token
        id: generate-oidc-token
        run: |
          RESPONSE=$(curl -s -X POST \
            -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL")
          OIDC_ID_TOKEN=$(echo $RESPONSE | jq -r '.value')
          if [ -z "$OIDC_ID_TOKEN" ] || [ "$OIDC_ID_TOKEN" == "null" ]; then
            echo "Error: Failed to retrieve OIDC token."
            exit 1
          fi
          echo "OIDC_ID_TOKEN=$OIDC_ID_TOKEN" >> $GITHUB_ENV
          echo "OIDC_ID_TOKEN extracted successfully."

      - name: Install Scalr CLI
        run: |
          wget -O scalr.zip https://github.com/Scalr/scalr-cli/releases/download/v${{ env.SCALR_CLI_VERSION }}/scalr-cli_${{ env.SCALR_CLI_VERSION }}_linux_amd64.zip
          unzip scalr.zip
          chmod +x ./scalr && mv ./scalr /usr/local/bin

      - name: Exchange OIDC Token for Scalr Token
        run: |
          echo "Exchanging GitHub OIDC token for Scalr access token..."
          SCALR_HOSTNAME="${{ vars.SCALR_HOSTNAME }}" \
          scalr -quiet assume-service-account \
            -id-token="$OIDC_ID_TOKEN" \
            -service-account-email="${{ env.SCALR_SERVICE_ACCOUNT_EMAIL }}"
          echo "Scalr token saved successfully."

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TERRAFORM_VERSION }}

      - name: Terraform Init
        run: terraform init

      - name: Terraform Format
        run: terraform fmt -check

      - name: Terraform Validate
        run: terraform validate

      - name: Terraform Plan
        run: terraform plan

      - name: Terraform Apply
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        run: terraform apply -auto-approve
Enter fullscreen mode Exit fullscreen mode

Key Features:

  • OIDC Authentication: Automatically exchanges GitHub OIDC token for Scalr token
  • Path-based Triggers: Only runs when .tf files change
  • Auto-apply: Applies changes on push to main branch
  • Plan-only: Shows plan on pull requests

6. GitHub Repository Configuration

6.1. Repository Secrets

Navigate to: SettingsSecrets and variablesActionsSecrets

Secret Name Description Example
SCALR_GITHUB_TOKEN GitHub App installation token for VCS integration ghs_xxxxxxxxxxxxx
AWS_ACCOUNT_ID AWS account ID for S3 backend 123456789012

Note: With Scalr OIDC, you don't need SCALR_API_TOKEN. The workflow automatically exchanges the GitHub OIDC token for a Scalr token.

6.2. Repository Variables

Navigate to: SettingsSecrets and variablesActionsVariables

Variable Name Description Example
SCALR_ACCOUNT_ID Scalr account ID acc-xxxxxxxxxxxx
SCALR_HOSTNAME Scalr hostname platformfuzz.scalr.io or app.scalr.io
SCALR_SERVICE_ACCOUNT_EMAIL Service account email for OIDC authentication terraform@acc-xxxxxxxxxxxx.scalr.io
TERRAFORM_VERSION Terraform version to use 1.6.0
SCALR_CLI_VERSION Scalr CLI version for OIDC token exchange 0.17.6
OIDC_ROLE_NAME AWS IAM role name for OIDC authentication github-actions-terraform
AWS_REGION AWS region for S3 backend ap-southeast-2

7. How It Works

Authentication Flow:

  1. GitHub Actions generates an OIDC token
  2. Scalr CLI exchanges the OIDC token for a Scalr access token
  3. Terraform uses the Scalr token (stored in ~/.scalr/credentials.tfrc.json) to authenticate
  4. No manual token rotation needed - everything is automatic!

Workflow Process:

  1. Push to main branch or create a Pull Request
  2. GitHub Actions workflow automatically:
    • Checks out code
    • Configures AWS credentials via OIDC (for S3 backend)
    • Exchanges GitHub OIDC token for Scalr token
    • Initializes Terraform (connects to S3 backend)
    • Formats and validates code
    • Runs terraform plan
    • On push to main: Runs terraform apply -auto-approve

8. Best Practices Summary

  • OIDC Authentication: Use workload identity providers instead of static API tokens
  • Custom Roles: Follow least-privilege principles with custom roles
  • Hierarchical Structure: Organize resources as Organization → Environments → Workspaces
  • CI/CD Automation: Use GitHub Actions for automated plan/apply
  • Bootstrap Configuration: Store state externally (S3) until Scalr is operational
  • Path-based Triggers: Only run workflows when relevant files change
  • Secure Credentials: Never commit secrets; use GitHub secrets/variables

9. Next Steps

Now that you have Scalr infrastructure managed as code, you can:

  • Configure VCS integration for version-controlled workflows
  • Add provider configurations for cloud resources
  • Set up IAM teams and roles for access control
  • Create workspace variables and run triggers
  • Implement drift detection workflows

10. References

Top comments (0)