CI/CD Workshop
In this tutorial, you’ll build a complete CI/CD pipeline that creates ephemeral environments for every pull request, runs tests, and provides preview URLs for reviewers.
Time to complete: 45-60 minutes
Prerequisites:
- Teabar account with organization access
- Keycloak service account (contact your Keycloak admin)
- GitHub repository (or GitLab/Bitbucket)
- Basic familiarity with CI/CD concepts
What You’ll Build
By the end of this workshop, your pipeline will:
- Create an ephemeral environment for each PR
- Deploy your application to the environment
- Run integration tests against the live environment
- Post a preview URL as a PR comment
- Automatically clean up when the PR is closed
Step 1: Set Up Your Project
First, let’s create a Teabar project for your repository:
# Login to Teabar
teabar auth login
# Create a new project
teabar project create my-app-ci
# Set as current project
teabar project use my-app-ci Step 2: Create a Blueprint
Create a blueprint that defines your application’s environment:
# teabar.yaml
name: my-app
version: "1.0"
components:
app:
image: ${APP_IMAGE:-myapp:latest}
ports:
- 3000:3000
environment:
NODE_ENV: preview
DATABASE_URL: postgres://postgres:postgres@database:5432/myapp
REDIS_URL: redis://cache:6379
health_check:
type: http
path: /health
port: 3000
database:
image: postgres:15
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: myapp
volumes:
- db-data:/var/lib/postgresql/data
cache:
image: redis:7-alpine
volumes:
db-data:
expose:
- component: app
port: 3000
public: true Commit this file to your repository root.
Step 3: Set Up Service Account
For CI/CD, you’ll use a Keycloak service account instead of interactive login.
Request a Service Account
Contact your Keycloak administrator to create a service account with:
- Client ID: e.g.,
github-actions-ci - Client credentials grant enabled
- Teabar roles:
teabar-env-manager
You’ll receive:
- Client ID: Store in CI variables
- Client Secret: Store securely in CI secrets
Token Generation Script
Add this to your CI pipeline to get an access token:
# Get Keycloak access token
TEABAR_TOKEN=$(curl -s -X POST
"${KEYCLOAK_URL}/protocol/openid-connect/token"
-d "client_id=${KEYCLOAK_CLIENT_ID}"
-d "client_secret=${KEYCLOAK_CLIENT_SECRET}"
-d "grant_type=client_credentials" | jq -r '.access_token')
export TEABAR_TOKEN Step 4: Configure Your CI Pipeline
Create .github/workflows/preview.yml:
name: Preview Environment
on:
pull_request:
types: [opened, synchronize, reopened, closed]
env:
KEYCLOAK_URL: ${{ vars.KEYCLOAK_URL }}
KEYCLOAK_CLIENT_ID: ${{ vars.KEYCLOAK_CLIENT_ID }}
TEABAR_PROJECT: my-app-ci
jobs:
preview:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Teabar CLI
run: curl -fsSL https://get.teabar.dev | sh
- name: Get Keycloak Token
id: auth
run: |
TOKEN=$(curl -s -X POST
"${KEYCLOAK_URL}/protocol/openid-connect/token"
-d "client_id=${KEYCLOAK_CLIENT_ID}"
-d "client_secret=${{ secrets.KEYCLOAK_CLIENT_SECRET }}"
-d "grant_type=client_credentials" | jq -r '.access_token')
echo "token=$TOKEN" >> $GITHUB_OUTPUT
- name: Build Application
run: |
docker build -t myapp:${{ github.sha }} .
docker push myregistry.com/myapp:${{ github.sha }}
- name: Create Preview Environment
id: preview
env:
TEABAR_TOKEN: ${{ steps.auth.outputs.token }}
run: |
ENV_NAME="pr-${{ github.event.pull_request.number }}"
# Create or update environment
teabar env upsert $ENV_NAME
--var APP_IMAGE=myregistry.com/myapp:${{ github.sha }}
--wait
# Get environment URL
URL=$(teabar env info $ENV_NAME --output json | jq -r '.url')
echo "url=$URL" >> $GITHUB_OUTPUT
echo "env_name=$ENV_NAME" >> $GITHUB_OUTPUT
- name: Run Integration Tests
run: |
npm ci
PREVIEW_URL="${{ steps.preview.outputs.url }}" npm run test:integration
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
const url = '${{ steps.preview.outputs.url }}';
const envName = '${{ steps.preview.outputs.env_name }}';
const sha = '${{ github.sha }}';
const body = [
'## Preview Environment Ready',
'',
'| Resource | Link |',
'|----------|------|',
'| Preview URL | ' + url + ' |',
'| Environment | ' + envName + ' |',
'| Commit | ' + sha + ' |',
].join('
');
// Find existing comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('Preview Environment Ready')
);
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
}
cleanup:
if: github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Install Teabar CLI
run: curl -fsSL https://get.teabar.dev | sh
- name: Delete Preview Environment
run: |
ENV_NAME="pr-${{ github.event.pull_request.number }}"
teabar env delete $ENV_NAME --yes || trueStep 5: Add CI Secrets
Add your Keycloak service account credentials to your CI system:
GitHub:
- Go to Settings → Secrets and variables → Actions
- Add these secrets:
KEYCLOAK_CLIENT_ID: Your service account client IDKEYCLOAK_CLIENT_SECRET: Your service account secret
- Add these variables:
KEYCLOAK_URL:https://auth.bcp.technology/realms/teabar
GitLab:
- Go to Settings → CI/CD → Variables
- Add variables:
KEYCLOAK_CLIENT_ID: Your client IDKEYCLOAK_CLIENT_SECRET: Your secret (masked)KEYCLOAK_URL: Keycloak URL
CircleCI:
- Go to Project Settings → Environment Variables
- Add:
KEYCLOAK_CLIENT_IDKEYCLOAK_CLIENT_SECRETKEYCLOAK_URL
Step 6: Test Your Pipeline
Create a pull request to trigger the pipeline:
git checkout -b test-preview
echo "# Test" >> README.md
git add .
git commit -m "Test preview environments"
git push -u origin test-preview
# Create PR via GitHub/GitLab UI Watch the CI pipeline create your preview environment!
Step 7: Advanced Configuration
Database Seeding
Add seed data to preview environments:
# teabar.yaml
hooks:
post_create:
- component: database
command: ["psql", "-f", "/seeds/data.sql"] Branch-Based Configuration
Use different settings per branch:
# In your CI workflow
- name: Create Preview Environment
run: |
if [[ "${{ github.base_ref }}" == "main" ]]; then
RESOURCES="--cpu 2 --memory 4G"
else
RESOURCES="--cpu 1 --memory 2G"
fi
teabar env upsert pr-${{ github.event.pull_request.number }} $RESOURCES Parallel Test Environments
Create multiple environments for parallel testing:
strategy:
matrix:
test_suite: [unit, integration, e2e]
steps:
- name: Create Test Environment
run: |
teabar env create "pr-${{ github.event.pull_request.number }}-${{ matrix.test_suite }}" Slack Notifications
Add Slack notifications for preview URLs:
- name: Notify Slack
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "Preview ready for PR #${{ github.event.pull_request.number }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*<${{ steps.preview.outputs.url }}|Preview Environment Ready>*
PR: <${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }}>"
}
}
]
} Troubleshooting
Environment Creation Timeout
# Increase timeout
teabar env upsert pr-123 --wait --timeout 10m Tests Failing to Connect
# Add health check wait
- name: Wait for Environment
run: |
teabar env wait pr-123 --healthy --timeout 5m Cleanup Not Running
Ensure cleanup runs even on failure:
cleanup:
if: always() && github.event.action == 'closed' Summary
You’ve built a complete CI/CD pipeline that:
- Creates isolated preview environments for every PR
- Runs tests against real infrastructure
- Provides reviewers with live preview URLs
- Automatically cleans up resources
Note
Next Steps
- Add cost alerts to monitor CI spend
- Configure checkpoints for test data
- Set up custom domains for preview URLs