GitHub Actions CI/CD for Policies
This guide shows how to automate policy deployment using GitHub Actions.
Prerequisites
- Fulcrum CLI installed in your CI environment
- API key stored as GitHub secret (
FULCRUM_API_KEY) - Fulcrum API URL stored as GitHub secret (
FULCRUM_API_URL)
Workflow Examples
Validate on Pull Request
Validates policy files on every PR:
# .github/workflows/policy-validate.yml
name: Validate Policies
on:
pull_request:
paths:
- 'policies/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Fulcrum CLI
run: |
curl -sSL https://get.fulcrumlayer.io/cli | bash
echo "$HOME/.fulcrum/bin" >> $GITHUB_PATH
- name: Validate Policies
run: fulcrum policies validate --strict ./policies/
- name: Show Diff
env:
FULCRUM_API_KEY: ${{ secrets.FULCRUM_API_KEY }}
FULCRUM_API_URL: ${{ secrets.FULCRUM_API_URL }}
run: |
fulcrum policies diff ./policies/ > diff.txt
cat diff.txt
- name: Comment Diff on PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const diff = fs.readFileSync('diff.txt', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Policy Changes\n\n\`\`\`\n${diff}\n\`\`\``
});
Deploy on Merge
Deploys policies when changes are merged to main:
# .github/workflows/policy-deploy.yml
name: Deploy Policies
on:
push:
branches:
- main
paths:
- 'policies/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Fulcrum CLI
run: |
curl -sSL https://get.fulcrumlayer.io/cli | bash
echo "$HOME/.fulcrum/bin" >> $GITHUB_PATH
- name: Configure CLI
env:
FULCRUM_API_KEY: ${{ secrets.FULCRUM_API_KEY }}
FULCRUM_API_URL: ${{ secrets.FULCRUM_API_URL }}
run: |
fulcrum auth login --api-key "$FULCRUM_API_KEY"
- name: Validate Policies
run: fulcrum policies validate --strict ./policies/
- name: Deploy Policies
run: fulcrum policies import --update ./policies/
Multi-Environment Deployment
Deploy to staging first, then production:
# .github/workflows/policy-deploy-multi.yml
name: Deploy Policies (Multi-Environment)
on:
push:
branches:
- main
paths:
- 'policies/**'
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- name: Install Fulcrum CLI
run: |
curl -sSL https://get.fulcrumlayer.io/cli | bash
echo "$HOME/.fulcrum/bin" >> $GITHUB_PATH
- name: Deploy to Staging
env:
FULCRUM_API_KEY: ${{ secrets.FULCRUM_API_KEY_STAGING }}
FULCRUM_API_URL: ${{ secrets.FULCRUM_API_URL_STAGING }}
run: |
fulcrum auth login --api-key "$FULCRUM_API_KEY"
fulcrum policies import --update ./policies/staging/
fulcrum policies import --update ./policies/shared/
deploy-production:
runs-on: ubuntu-latest
needs: deploy-staging
environment: production
steps:
- uses: actions/checkout@v4
- name: Install Fulcrum CLI
run: |
curl -sSL https://get.fulcrumlayer.io/cli | bash
echo "$HOME/.fulcrum/bin" >> $GITHUB_PATH
- name: Deploy to Production
env:
FULCRUM_API_KEY: ${{ secrets.FULCRUM_API_KEY_PRODUCTION }}
FULCRUM_API_URL: ${{ secrets.FULCRUM_API_URL_PRODUCTION }}
run: |
fulcrum auth login --api-key "$FULCRUM_API_KEY"
fulcrum policies import --update ./policies/production/
fulcrum policies import --update ./policies/shared/
Scheduled Sync Check
Detect drift between git and Fulcrum:
# .github/workflows/policy-drift.yml
name: Policy Drift Check
on:
schedule:
- cron: '0 */6 * * *' # Every 6 hours
workflow_dispatch:
jobs:
check-drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Fulcrum CLI
run: |
curl -sSL https://get.fulcrumlayer.io/cli | bash
echo "$HOME/.fulcrum/bin" >> $GITHUB_PATH
- name: Check for Drift
id: drift
env:
FULCRUM_API_KEY: ${{ secrets.FULCRUM_API_KEY }}
FULCRUM_API_URL: ${{ secrets.FULCRUM_API_URL }}
run: |
fulcrum policies diff ./policies/ > drift.txt
if grep -E "^[~+-]" drift.txt; then
echo "drift=true" >> $GITHUB_OUTPUT
else
echo "drift=false" >> $GITHUB_OUTPUT
fi
- name: Create Issue on Drift
if: steps.drift.outputs.drift == 'true'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const drift = fs.readFileSync('drift.txt', 'utf8');
// Check for existing drift issue
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'policy-drift',
state: 'open'
});
if (issues.data.length === 0) {
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Policy Drift Detected',
body: `Policies in Fulcrum have drifted from git.\n\n\`\`\`\n${drift}\n\`\`\``,
labels: ['policy-drift']
});
}
Complete Example
Here's a complete repository structure with all workflows:
.
├── .github/
│ └── workflows/
│ ├── policy-validate.yml # Validate on PR
│ ├── policy-deploy.yml # Deploy on merge
│ └── policy-drift.yml # Scheduled drift check
├── policies/
│ ├── production/
│ │ ├── budget-limits.yaml
│ │ └── rate-limits.yaml
│ ├── staging/
│ │ └── test-policies.yaml
│ └── shared/
│ └── content-filters.yaml
└── README.md
Secrets Configuration
Configure these secrets in your GitHub repository settings:
| Secret | Description |
|---|---|
FULCRUM_API_KEY |
API key for Fulcrum access |
FULCRUM_API_URL |
Fulcrum API URL (e.g., https://api.fulcrumlayer.io) |
For multi-environment setups:
| Secret | Description |
|---|---|
FULCRUM_API_KEY_STAGING |
Staging environment API key |
FULCRUM_API_URL_STAGING |
Staging API URL |
FULCRUM_API_KEY_PRODUCTION |
Production environment API key |
FULCRUM_API_URL_PRODUCTION |
Production API URL |
Best Practices
- Use environments for production deployments with required reviewers
- Validate strictly (
--strict) in CI to catch warnings - Comment diffs on PRs for easy review
- Monitor for drift to detect out-of-band changes
- Keep secrets secure using GitHub encrypted secrets
- Use branch protection to require validation before merge
Troubleshooting
CLI Not Found
Ensure the CLI is in PATH:
- name: Install CLI
run: |
curl -sSL https://get.fulcrumlayer.io/cli | bash
echo "$HOME/.fulcrum/bin" >> $GITHUB_PATH
Authentication Failed
Check that secrets are configured correctly:
- name: Debug Auth
env:
FULCRUM_API_KEY: ${{ secrets.FULCRUM_API_KEY }}
run: |
echo "API Key length: ${#FULCRUM_API_KEY}"
fulcrum auth status
Validation Fails but Works Locally
Ensure you're using --strict mode locally to match CI: