This guide explains how to manage Wingman's Slack app infrastructure using Terraform.
Wingman uses Infrastructure as Code (IaC) via Terraform to automatically create and manage the Slack app. This eliminates manual configuration and enables reproducible deployments.
Key Benefits:
- Automated Slack app creation with correct scopes and permissions
- Token rotation management
- Version-controlled infrastructure configuration
- Reproducible deployments across environments
Terraform Cloud (Optional): Terraform Cloud provides remote state storage and collaboration features, but it's not required. You can use local state or any other Terraform backend. Terraform Cloud is convenient for teams because it provides:
- Encrypted remote state storage
- Token rotation management via workspace variables
- Access control and audit logs
- State locking to prevent concurrent modifications
For local development or personal use, you can skip Terraform Cloud and use local state.
- Terraform 1.12+ (managed by mise)
uvfor Python dependency management- Slack workspace admin access
- (Optional) Terraform Cloud account for remote state - free tier available
Sign up for Terraform Cloud (free tier available).
Create an organization and workspace, or use the defaults during setup:
TF_CLOUD_ORGANIZATION=your-org
TF_WORKSPACE=wingmanGenerate a Terraform Cloud API token from https://app.terraform.io/app/settings/tokens.
Add to .env:
# Terraform Cloud
TF_CLOUD_ORGANIZATION=your-org
TF_WORKSPACE=wingman
TF_TOKEN_app_terraform_io=your-terraform-cloud-token
# Slack (obtained from initial app setup or token rotation)
SLACK_CONFIG_ACCESS_TOKEN=xoxe.xoxp-1-...
SLACK_CONFIG_REFRESH_TOKEN=xoxe-1-...Mise automatically loads .env for all tasks via [env] configuration in mise.toml.
# Initialize Terraform (downloads provider and sets up state)
mise run tf-init
# Review what will be created
mise run tf-plan
# Create the Slack app
mise run tf-applyIf you don't want to use Terraform Cloud, you can use local state:
Comment out or remove the cloud backend in terraform/backend.tf:
# terraform {
# cloud {
# organization = var.tf_cloud_organization
# workspaces {
# name = var.tf_workspace
# }
# }
# }Add to .env:
# Slack tokens (obtained from initial app setup or token rotation)
SLACK_CONFIG_ACCESS_TOKEN=xoxe.xoxp-1-...
SLACK_CONFIG_REFRESH_TOKEN=xoxe-1-...mise run tf-init
mise run tf-plan
mise run tf-applyNote: With local state, the state file (terraform.tfstate) will be stored locally. Add it to .gitignore to avoid committing sensitive data.
After applying Terraform, load the generated credentials into .env:
mise run tf-load-varsThis loads:
SLACK_CLIENT_IDSLACK_CLIENT_SECRETSLACK_SIGNING_SECRETSLACK_VERIFICATION_TOKEN
# Get the OAuth installation URL
mise run tf-output | grep oauth_authorize_urlVisit the URL and click "Install to Workspace". This will prompt you to:
- Select the workspace to install the app
- Review requested permissions (scopes)
- Click "Allow"
After installation, Slack will show your app's tokens:
Copy these tokens and add to .env:
# Navigate to your Slack app at https://api.slack.com/apps
# Select your app (Wingman)
# Go to "OAuth & Permissions" section
SLACK_BOT_TOKEN=xoxb-... # Bot User OAuth Token
SLACK_APP_TOKEN=xapp-... # App-Level Token (for Socket Mode)To find these tokens:
- Go to https://api.slack.com/apps
- Select your Wingman app
- OAuth & Permissions → Copy "Bot User OAuth Token" (xoxb-...)
- Socket Mode → Copy "App-Level Token" (xapp-...)
- Paste both into
.env
To load all credentials (except bot/app tokens which require manual installation):
mise run tf-load-varsNote: SLACK_BOT_TOKEN and SLACK_APP_TOKEN are NOT available from Terraform outputs because they are only generated after you install the app to your workspace. These must be manually copied from Slack after installation.
# Get the OAuth URL
mise run tf-output
# Or just the authorize URL
mise run tf-output | grep oauth_authorize_urlVisit the URL and click "Install to Workspace". This generates:
- Bot token (xoxb-...) - for the bot to act
- App token (xapp-...) - for Socket Mode
Copy these to .env:
SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...mise run tf-load-credentialsThis extracts the signing secret from Terraform and adds it to .env.
# Show all outputs
mise run tf-output
# Show specific output
mise run tf-output -json app_id
# View app credentials (sensitive data)
mise run tf-credentialsSlack tokens expire after 12 hours. Rotate them before expiry:
# Sync (rotate) tokens TO Terraform Cloud from .env
mise run tf-sync-vars
# Load credentials FROM Terraform outputs to .env
mise run tf-load-varsToken Lifecycle:
-
tf-sync-vars- Updates Terraform Cloud with fresh tokens from.env- Validates tokens from
.env - Falls back to Terraform Cloud if
.envtokens invalid - Rotates tokens via Slack API
- Updates
.envand Terraform Cloud workspace variables
- Validates tokens from
-
tf-load-vars- Loads Slack credentials from Terraform outputs to.env- Extracts
signing_secret,client_id,client_secret,verification_token - Updates existing values in
.env - Note:
SLACK_BOT_TOKENandSLACK_APP_TOKENmust be manually added after installing the app to your workspace
- Extracts
Slack tokens expire after 12 hours. Rotate them before expiry:
mise run tf-sync-varsThis script:
- Validates tokens from
.envby attempting rotation - Falls back to Terraform Cloud if
.envtokens invalid - If both invalid, prompts for manual token entry with link to Slack API
- Rotates tokens via Slack's
tooling.tokens.rotateAPI - Updates local
.envfile - Syncs new tokens to Terraform Cloud workspace variables
Before applying, always review what Terraform will change:
mise run tf-planEdit terraform/variables.tf to customize:
variable "app_name" {
default = "Wingman"
}
variable "app_description" {
default = "AI-powered Slack support assistant"
}
variable "bot_display_name" {
default = "Wingman"
}
variable "socket_mode_enabled" {
default = true
}Then apply:
mise run tf-plan
mise run tf-applymise run tf-destroyThis removes the Slack app from your workspace. Requires confirmation.
-
terraform/main.tf- Slack app provider and resource- Configures Slack provider with tokens
- Creates app from manifest
-
terraform/variables.tf- Input variables- App name, description, colors
- Display settings
-
terraform/outputs.tf- Output values- App ID for reference
- OAuth URL for installation
- App credentials (signing secret, etc.)
-
terraform/backend.tf- Remote state configuration- Uses Terraform Cloud backend
- Reads from environment variables (not hardcoded)
scripts/sync_tf_vars.py- Token management- Validates and rotates Slack tokens
- Updates
.envand Terraform Cloud - Handles invalid tokens with user prompts
| Variable | Purpose | Example |
|---|---|---|
TF_CLOUD_ORGANIZATION |
Terraform Cloud org | your-org |
TF_WORKSPACE |
Terraform Cloud workspace | wingman |
TF_TOKEN_app_terraform_io |
Terraform Cloud API token | tfp_xxxx... |
SLACK_CONFIG_ACCESS_TOKEN |
Slack API token | xoxe.xoxp-1-... |
SLACK_CONFIG_REFRESH_TOKEN |
Slack refresh token | xoxe-1-... |
Ensure .env is properly configured:
cp .env.example .env
# Edit .env with your values
mise run tf-initTokens may have expired. Rotate them:
mise run tf-sync-varsIf tf-sync-vars prompts for manual entry:
- Go to https://api.slack.com/apps
- Select your app
- Navigate to "Tokens and installation"
- Copy "Refresh token for rotation" (xoxe-1-...)
- Paste when prompted
Ensure environment variables match what's in Terraform Cloud:
# Check local state
mise run tf-plan
# If differences appear, verify tokens are synced
mise run tf-sync-vars
# Then re-plan
mise run tf-planVerify the workspace exists:
- Log in to https://app.terraform.io
- Navigate to your organization
- Check the workspace name matches
TF_WORKSPACEin.env
Re-initialize Terraform:
cd terraform
rm -rf .terraform .terraform.lock.hcl
cd ..
mise run tf-init- Never commit tokens to git - they're in
.envwhich is.gitignored - Rotate tokens regularly - use
mise run tf-sync-varsbefore 12h expiry - Use Terraform Cloud variables - sensitive data is encrypted at rest
- Limit permissions - Slack tokens should have minimal required scopes
Terraform Cloud provides:
- Encrypted state storage
- Access control (team/org level)
- Audit logs
- State locking to prevent concurrent modifications
Create separate Terraform Cloud workspaces for dev/staging/prod:
# For dev environment
TF_WORKSPACE=wingman-dev mise run tf-init
# For prod environment
TF_WORKSPACE=wingman-prod mise run tf-initEdit terraform/variables.tf:
variable "app_background_color" {
default = "#36c5f0" # Slack blue
}
variable "slash_commands" {
type = list(string)
default = ["/wingman", "/help"]
}Update terraform/main.tf to use these variables in the manifest.
Check if your Slack app configuration has drifted from Terraform:
mise run tf-planAny changes will be shown. To sync to Terraform state:
mise run tf-apply- Slack API Documentation
- Terraform Cloud Documentation
- terraform-provider-slackapp
- Setup Guide - General project setup
- Slack Authentication - Token types and permissions