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:

  1. Create an ephemeral environment for each PR
  2. Deploy your application to the environment
  3. Run integration tests against the live environment
  4. Post a preview URL as a PR comment
  5. 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 || true

Step 5: Add CI Secrets

Add your Keycloak service account credentials to your CI system:

GitHub:

  1. Go to SettingsSecrets and variablesActions
  2. Add these secrets:
    • KEYCLOAK_CLIENT_ID: Your service account client ID
    • KEYCLOAK_CLIENT_SECRET: Your service account secret
  3. Add these variables:
    • KEYCLOAK_URL: https://auth.bcp.technology/realms/teabar

GitLab:

  1. Go to SettingsCI/CDVariables
  2. Add variables:
    • KEYCLOAK_CLIENT_ID: Your client ID
    • KEYCLOAK_CLIENT_SECRET: Your secret (masked)
    • KEYCLOAK_URL: Keycloak URL

CircleCI:

  1. Go to Project SettingsEnvironment Variables
  2. Add:
    • KEYCLOAK_CLIENT_ID
    • KEYCLOAK_CLIENT_SECRET
    • KEYCLOAK_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

Next Steps

ende