“Did you remember to export the updated Login journey before leaving on Friday?”
This Slack message used to haunt our team. Someone would make changes in dev, forget to export, and by Monday we’d be scratching our heads about what changed. Sound familiar?
The fix: wire up Frodo CLI with GitHub Actions and never worry about manual exports again. Here’s exactly how we set it up.
Why Bother with CI/CD for ForgeRock?
| Manual Process | CI/CD with Frodo |
|---|---|
| Export from admin console | git push triggers export |
| Copy JSON files manually | Automated version control |
| Import one-by-one | Batch import with validation |
| No audit trail | Full Git history |
| Human errors | Consistent, repeatable |
CI/CD Pipeline Flow:
graph LR
subgraph "Development"
DEV[Dev Tenant]
CODE[Git Push]
end
subgraph "GitHub Actions"
EXP[Export Job]
TEST[Validation]
IMP[Import Job]
end
subgraph "Production"
PROD[Prod Tenant]
end
DEV -->|Trigger| CODE
CODE --> EXP
EXP --> TEST
TEST -->|Approved| IMP
IMP --> PROD
style EXP fill:#667eea,color:#fff
style IMP fill:#28a745,color:#fff
Prerequisites
Before setting up the pipeline:
- Frodo CLI installed locally for testing
- GitHub repository for your ForgeRock configurations
- Service account credentials for each tenant
- GitHub Secrets configured for credentials
Setting Up GitHub Secrets
Store your ForgeRock credentials securely in GitHub:
- Go to your repository → Settings → Secrets and variables → Actions
- Add the following secrets:
| Secret Name | Value |
|---|---|
FRODO_DEV_HOST |
https://openam-dev.forgeblocks.com/am |
FRODO_DEV_USER |
[email protected] |
FRODO_DEV_PASSWORD |
Your service account password |
FRODO_PROD_HOST |
https://openam-prod.forgeblocks.com/am |
FRODO_PROD_USER |
[email protected] |
FRODO_PROD_PASSWORD |
Your service account password |
Don’t use your personal admin account. Create a dedicated service account with only the permissions it needs. When (not if) credentials leak, you want to limit the blast radius.
Pipeline 1: Export on Schedule
Automatically export configurations daily and commit to Git:
# .github/workflows/export-config.yml
name: Export ForgeRock Configuration
on:
schedule:
# Run daily at 2 AM UTC
- cron: '0 2 * * *'
workflow_dispatch: # Allow manual trigger
jobs:
export:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Frodo CLI
run: npm install -g @rockcarver/frodo-cli
- name: Export Journeys
env:
FRODO_HOST: ${{ secrets.FRODO_DEV_HOST }}
FRODO_USER: ${{ secrets.FRODO_DEV_USER }}
FRODO_PASSWORD: ${{ secrets.FRODO_DEV_PASSWORD }}
run: |
mkdir -p exports/journeys
frodo journey export --all --directory exports/journeys
- name: Export Scripts
env:
FRODO_HOST: ${{ secrets.FRODO_DEV_HOST }}
FRODO_USER: ${{ secrets.FRODO_DEV_USER }}
FRODO_PASSWORD: ${{ secrets.FRODO_DEV_PASSWORD }}
run: |
mkdir -p exports/scripts
frodo script export --all --directory exports/scripts
- name: Export OAuth Clients
env:
FRODO_HOST: ${{ secrets.FRODO_DEV_HOST }}
FRODO_USER: ${{ secrets.FRODO_DEV_USER }}
FRODO_PASSWORD: ${{ secrets.FRODO_DEV_PASSWORD }}
run: |
mkdir -p exports/oauth
frodo oauth client export --all --directory exports/oauth
- name: Commit and Push
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add exports/
git diff --staged --quiet || git commit -m "chore: daily config export $(date +%Y-%m-%d)"
git push
Pipeline 2: Deploy to Production on Release
Deploy configurations when a new release is created:
# .github/workflows/deploy-prod.yml
name: Deploy to Production
on:
release:
types: [published]
workflow_dispatch:
inputs:
confirm:
description: 'Type "deploy" to confirm production deployment'
required: true
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Validate deployment confirmation
if: github.event_name == 'workflow_dispatch'
run: |
if [ "${{ github.event.inputs.confirm }}" != "deploy" ]; then
echo "Deployment not confirmed. Exiting."
exit 1
fi
deploy:
needs: validate
runs-on: ubuntu-latest
environment: production # Requires approval in GitHub
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Frodo CLI
run: npm install -g @rockcarver/frodo-cli
- name: Import Scripts First
env:
FRODO_HOST: ${{ secrets.FRODO_PROD_HOST }}
FRODO_USER: ${{ secrets.FRODO_PROD_USER }}
FRODO_PASSWORD: ${{ secrets.FRODO_PROD_PASSWORD }}
run: |
echo "Importing scripts..."
frodo script import --all --directory exports/scripts
- name: Import Journeys
env:
FRODO_HOST: ${{ secrets.FRODO_PROD_HOST }}
FRODO_USER: ${{ secrets.FRODO_PROD_USER }}
FRODO_PASSWORD: ${{ secrets.FRODO_PROD_PASSWORD }}
run: |
echo "Importing journeys..."
frodo journey import --all --directory exports/journeys
- name: Import OAuth Clients
env:
FRODO_HOST: ${{ secrets.FRODO_PROD_HOST }}
FRODO_USER: ${{ secrets.FRODO_PROD_USER }}
FRODO_PASSWORD: ${{ secrets.FRODO_PROD_PASSWORD }}
run: |
echo "Importing OAuth clients..."
frodo oauth client import --all --directory exports/oauth
- name: Deployment Summary
run: |
echo "## Deployment Complete! :rocket:" >> $GITHUB_STEP_SUMMARY
echo "- Scripts: $(ls exports/scripts/*.json 2>/dev/null | wc -l) files" >> $GITHUB_STEP_SUMMARY
echo "- Journeys: $(ls exports/journeys/*.json 2>/dev/null | wc -l) files" >> $GITHUB_STEP_SUMMARY
echo "- OAuth: $(ls exports/oauth/*.json 2>/dev/null | wc -l) files" >> $GITHUB_STEP_SUMMARY
Pipeline 3: PR-Based Promotion
Deploy to staging when a PR is merged, with manual approval for production:
# .github/workflows/promote-config.yml
name: Promote Configuration
on:
push:
branches:
- main
paths:
- 'exports/**'
pull_request:
branches:
- main
paths:
- 'exports/**'
jobs:
# Validate changes in PR
validate:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Validate JSON files
run: |
echo "Validating JSON syntax..."
find exports -name "*.json" -exec python3 -m json.tool {} \; > /dev/null
echo "All JSON files are valid!"
- name: List changes
run: |
echo "## Changed Files" >> $GITHUB_STEP_SUMMARY
git diff --name-only origin/main...HEAD -- exports/ >> $GITHUB_STEP_SUMMARY
# Deploy to staging on merge
deploy-staging:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: staging
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Frodo CLI
run: npm install -g @rockcarver/frodo-cli
- name: Deploy to Staging
env:
FRODO_HOST: ${{ secrets.FRODO_STAGING_HOST }}
FRODO_USER: ${{ secrets.FRODO_STAGING_USER }}
FRODO_PASSWORD: ${{ secrets.FRODO_STAGING_PASSWORD }}
run: |
frodo script import --all --directory exports/scripts
frodo journey import --all --directory exports/journeys
frodo oauth client import --all --directory exports/oauth
- name: Create production deployment issue
uses: actions/github-script@v7
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Production deployment ready: ${context.sha.substring(0, 7)}`,
body: `Staging deployment successful. Review and approve production deployment.\n\nCommit: ${context.sha}\nWorkflow: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
labels: ['deployment', 'production']
})
Pipeline 4: Multi-Environment Matrix
Deploy to multiple tenants in parallel:
# .github/workflows/multi-env-deploy.yml
name: Multi-Environment Deploy
on:
workflow_dispatch:
inputs:
environments:
description: 'Environments to deploy (comma-separated)'
required: true
default: 'dev,staging'
type: string
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
matrix:
environment: ${{ fromJson(format('["{0}"]', join(fromJson(format('["{0}"]', inputs.environments)), '","'))) }}
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Frodo CLI
run: npm install -g @rockcarver/frodo-cli
- name: Deploy to ${{ matrix.environment }}
env:
FRODO_HOST: ${{ secrets[format('FRODO_{0}_HOST', matrix.environment)] }}
FRODO_USER: ${{ secrets[format('FRODO_{0}_USER', matrix.environment)] }}
FRODO_PASSWORD: ${{ secrets[format('FRODO_{0}_PASSWORD', matrix.environment)] }}
run: |
echo "Deploying to ${{ matrix.environment }}..."
frodo journey import --all --directory exports/journeys
Best Practices
1. Use GitHub Environments
Configure environments with protection rules:
# In your workflow
jobs:
deploy:
environment: production # Requires approval
In GitHub Settings → Environments → production:
- Add required reviewers
- Set deployment branch rules
- Add environment secrets
2. Import Order Matters
Always import dependencies first:
# Correct order
- name: Import in correct order
run: |
# 1. Scripts (dependencies for journeys)
frodo script import --all --directory exports/scripts
# 2. Email templates (used by journeys)
frodo email template import --all --directory exports/email
# 3. Journeys (depend on scripts and templates)
frodo journey import --all --directory exports/journeys
# 4. OAuth clients (may reference journeys)
frodo oauth client import --all --directory exports/oauth
3. Handle Secrets Properly
Never export secrets to Git:
- name: Export ESV Variables Only
run: |
# Export variables (non-sensitive)
frodo esv variable export --all --directory exports/esv
# DON'T export secrets to Git!
# frodo esv secret export --all # NEVER DO THIS
4. Add Rollback Capability
- name: Backup before deploy
run: |
mkdir -p backup
frodo journey export --all --directory backup/journeys
- name: Deploy
run: |
frodo journey import --all --directory exports/journeys
- name: Rollback on failure
if: failure()
run: |
echo "Deployment failed, rolling back..."
frodo journey import --all --directory backup/journeys
5. Use Caching for Speed
- name: Cache npm dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- name: Install Frodo CLI
run: npm install -g @rockcarver/frodo-cli
Troubleshooting
Authentication Failures
- name: Test connection
env:
FRODO_HOST: ${{ secrets.FRODO_DEV_HOST }}
FRODO_USER: ${{ secrets.FRODO_DEV_USER }}
FRODO_PASSWORD: ${{ secrets.FRODO_DEV_PASSWORD }}
run: |
frodo journey list || {
echo "Connection failed!"
echo "Check your secrets are correctly configured"
exit 1
}
Rate Limiting
- name: Import with delay
run: |
for file in exports/journeys/*.json; do
echo "Importing $file..."
frodo journey import --file "$file"
sleep 2 # Add delay between imports
done
Debugging
- name: Debug mode
env:
DEBUG: frodo:* # Enable Frodo debug logging
run: |
frodo journey list --verbose
Complete Repository Structure
forgerock-config/
├── .github/
│ └── workflows/
│ ├── export-config.yml
│ ├── deploy-staging.yml
│ └── deploy-prod.yml
├── exports/
│ ├── journeys/
│ │ ├── Login.journey.json
│ │ └── Registration.journey.json
│ ├── scripts/
│ │ └── CustomValidation.script.json
│ ├── oauth/
│ │ └── my-spa-client.oauth2.json
│ └── esv/
│ └── variables.json
├── README.md
└── .gitignore
.gitignore:
# Never commit secrets
exports/esv/secrets*.json
*.secret.json
# Local Frodo cache
.frodo/
TokenCache.json
Related Resources
Frodo CLI Series
CI/CD Resources
What We Learned
After running these pipelines for several months, a few things became clear:
-
Start with exports only - Get comfortable with automated backups before adding deployment automation. Trust builds gradually.
-
The 2 AM cron job is a lifesaver - When someone inevitably makes an undocumented change, you have yesterday’s export to compare against.
-
Production approval gates are non-negotiable - One accidental push to prod taught us that lesson. Use GitHub Environments with required reviewers.
-
Git history becomes your audit log - “When did we add that MFA step to the Login journey?” Just check the commit history.
The setup takes maybe an hour. The peace of mind? Priceless.