GitHub Actions Pipeline
Automate Terraform validation, security scanning, and plan comments on pull requests
Prerequisites
- Terry-Form MCP Docker image built
- A GitHub repository with Terraform configurations
- GitHub Actions enabled on the repository
GitHub Actions Pipeline
This tutorial shows you how to use Terry-Form MCP in GitHub Actions to automatically validate Terraform configurations, run security scans, and post plan output as PR comments.
What You’ll Learn
- How to run Terry-Form MCP in a CI pipeline
- How to validate Terraform on every pull request
- How to scan for security issues on push to main
- How to post plan output as a PR comment
Pipeline Architecture
graph LR
A[Pull Request] --> B[Validate Job]
A --> C[Security Job]
B --> D{Pass?}
C --> E{Clean?}
D -->|Yes| F[Post Plan Comment]
D -->|No| G[Block Merge]
E -->|Yes| H[Approve]
E -->|No| I[Post Findings]
Step 1: Repository Structure
Set up your repository:
my-infrastructure/
├── .github/
│ └── workflows/
│ └── terraform-ci.yaml
├── Dockerfile # or reference to terry-form-mcp image
├── terraform/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── README.md
Step 2: Basic Validation Workflow
Create .github/workflows/terraform-ci.yaml:
name: Terraform CI
on:
pull_request:
paths:
- 'terraform/**'
jobs:
validate:
name: Validate Terraform
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build Terry-Form MCP
run: |
docker build -t terry-form-mcp \
https://github.com/aj-geddes/terry-form-mcp.git
- name: Initialize and Validate
id: validate
run: |
RESULT=$(echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"terry","arguments":{"path":".","actions":["init","validate"]}}}' | \
docker run -i --rm \
-v $/terraform:/mnt/workspace/project \
terry-form-mcp:latest)
echo "result<<EOF" >> $GITHUB_OUTPUT
echo "$RESULT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Check validation result
run: |
echo '$' | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
# Check for validation success in the response
print(json.dumps(data, indent=2))
"
Step 3: Add Plan Output as PR Comment
Extend the workflow to generate a plan and post it as a comment:
plan:
name: Terraform Plan
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build Terry-Form MCP
run: |
docker build -t terry-form-mcp \
https://github.com/aj-geddes/terry-form-mcp.git
- name: Run Terraform Plan
id: plan
run: |
RESULT=$(echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"terry","arguments":{"path":".","actions":["init","plan"]}}}' | \
docker run -i --rm \
-v $/terraform:/mnt/workspace/project \
-e AWS_ACCESS_KEY_ID=$ \
-e AWS_SECRET_ACCESS_KEY=$ \
-e AWS_DEFAULT_REGION=$ \
terry-form-mcp:latest)
echo "result<<EOF" >> $GITHUB_OUTPUT
echo "$RESULT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Comment Plan on PR
uses: actions/github-script@v7
if: github.event_name == 'pull_request'
with:
script: |
const plan = `$`;
const body = `## Terraform Plan
\`\`\`json
${plan.substring(0, 60000)}
\`\`\`
*Generated by Terry-Form MCP*`;
// Find existing comment
const { data: comments } = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
const existing = comments.find(c =>
c.body.includes('## Terraform Plan') &&
c.body.includes('Terry-Form MCP')
);
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: body
});
} else {
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
}
Step 4: Add Security Scanning
Add a security scan job that runs on push to main:
security:
name: Security Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build Terry-Form MCP
run: |
docker build -t terry-form-mcp \
https://github.com/aj-geddes/terry-form-mcp.git
- name: Run Security Scan
id: security
run: |
RESULT=$(echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"terry_security_scan","arguments":{"path":".","severity":"medium"}}}' | \
docker run -i --rm \
-v $/terraform:/mnt/workspace/project \
terry-form-mcp:latest)
echo "result<<EOF" >> $GITHUB_OUTPUT
echo "$RESULT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Check for findings
run: |
echo '$' | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
print(json.dumps(data, indent=2))
# Fail if high severity findings exist
"
- name: Run Best Practice Analysis
id: analyze
run: |
RESULT=$(echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"terry_analyze","arguments":{"path":"."}}}' | \
docker run -i --rm \
-v $/terraform:/mnt/workspace/project \
terry-form-mcp:latest)
echo "result<<EOF" >> $GITHUB_OUTPUT
echo "$RESULT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
Step 5: Complete Workflow
Here’s the complete workflow combining all three jobs:
name: Terraform CI
on:
pull_request:
paths:
- 'terraform/**'
push:
branches: [main]
paths:
- 'terraform/**'
jobs:
validate:
name: Validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Terry-Form MCP
run: docker build -t terry-form-mcp https://github.com/aj-geddes/terry-form-mcp.git
- name: Validate
run: |
echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"terry","arguments":{"path":".","actions":["init","fmt","validate"]}}}' | \
docker run -i --rm \
-v $/terraform:/mnt/workspace/project \
terry-form-mcp:latest
security:
name: Security Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Terry-Form MCP
run: docker build -t terry-form-mcp https://github.com/aj-geddes/terry-form-mcp.git
- name: Scan
run: |
echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"terry_security_scan","arguments":{"path":".","severity":"medium"}}}' | \
docker run -i --rm \
-v $/terraform:/mnt/workspace/project \
terry-form-mcp:latest
plan:
name: Plan
runs-on: ubuntu-latest
needs: [validate, security]
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- name: Build Terry-Form MCP
run: docker build -t terry-form-mcp https://github.com/aj-geddes/terry-form-mcp.git
- name: Plan
id: plan
run: |
RESULT=$(echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"terry","arguments":{"path":".","actions":["init","plan"]}}}' | \
docker run -i --rm \
-v $/terraform:/mnt/workspace/project \
-e AWS_ACCESS_KEY_ID=$ \
-e AWS_SECRET_ACCESS_KEY=$ \
-e AWS_DEFAULT_REGION=$ \
terry-form-mcp:latest)
echo "result<<EOF" >> $GITHUB_OUTPUT
echo "$RESULT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const plan = `$`;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Terraform Plan\n\`\`\`json\n${plan.substring(0, 60000)}\n\`\`\`\n\n*Generated by Terry-Form MCP*`
});
Step 6: Credential Management
Using OIDC (Recommended)
For AWS, use OIDC instead of static credentials:
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/terraform-ci
aws-region: us-east-1
- name: Plan with OIDC
run: |
echo '...' | \
docker run -i --rm \
-v $/terraform:/mnt/workspace/project \
-e AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY \
-e AWS_SESSION_TOKEN \
-e AWS_DEFAULT_REGION \
terry-form-mcp:latest
Using GitHub Secrets
Store credentials as repository secrets:
- Go to Settings > Secrets and variables > Actions
- Add:
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_DEFAULT_REGION - Reference as
$
Security
Terry-Form MCP forces
Terry-Form MCP forces
TF_INPUT=false to prevent accidental credential prompts in CI. Credentials are never logged.
Step 7: Parse Results
Check results programmatically in your workflow:
RESULT=$(echo '...' | docker run -i --rm ... terry-form-mcp:latest)
# Extract success status
SUCCESS=$(echo "$RESULT" | jq -r '.[\"terry-results\"][-1].success')
if [ "$SUCCESS" != "true" ]; then
echo "::error::Terraform validation failed"
exit 1
fi
Pipeline Patterns
Gate Pattern
Validation and security must pass before plan runs:
plan:
needs: [validate, security]
Matrix Pattern
Validate multiple environments:
strategy:
matrix:
environment: [dev, staging, prod]
steps:
- run: |
echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"terry","arguments":{"path":"environments/$","actions":["init","validate"]}}}' | ...
Important
Terry-Form MCP blocks
Terry-Form MCP blocks
apply and destroy. Use it only for validation and planning in CI. Actual infrastructure changes should go through your standard deployment process.
Summary
In this tutorial, you learned how to:
- Run Terry-Form MCP in GitHub Actions
- Validate Terraform configurations automatically on PRs
- Run security scans on push to main
- Post plan output as PR comments
- Manage credentials securely with OIDC or GitHub secrets
- Parse JSON results to gate pipeline stages
Next Steps
- Multi-Environment Setup — Manage dev/staging/prod with shared modules
- CI/CD Integration Guide — More CI patterns including GitLab CI
- Security Guide — Comprehensive security reference