Skip to content

GitHub Actions

Goal: every PR gets a fresh BC environment, the extension built from the PR branch is published into it, your tests run against it, and it tears down when the PR closes. Total runtime: ~3–5 minutes on a warm pool.

Prerequisites

  • A BCDOCK_TOKEN in your repo's GitHub secrets (Settings → Secrets and variables → Actions → New repository secret). Mint from app.bcdock.io/profile/api-keys with env:read + env:write scopes.
  • A bcdock.yaml at the repo root (optional but recommended) so flag defaults don't have to repeat across jobs.
  • The CLI installed in your action runner — easiest path is the bcdock/setup-cli@v1 action (when it ships) or the install script.

Workflow template

Save as .github/workflows/bcdock-pr.yml:

name: BC test environment

on:
  pull_request:
    types: [opened, synchronize, closed]
    branches: [main]

# Each PR gets its own env; new pushes to the same PR reuse it.
concurrency:
  group: bcdock-pr-${{ github.event.pull_request.number }}
  cancel-in-progress: false

env:
  ENV_NAME: pr-${{ github.event.pull_request.number }}
  BCDOCK_TOKEN: ${{ secrets.BCDOCK_TOKEN }}

jobs:
  provision-and-test:
    if: github.event.action != 'closed'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install bcdock
        run: curl -fsSL https://cli.bcdock.io/install.sh | sh

      - name: Create or resume environment
        run: |
          if bcdock env get "$ENV_NAME" -o json >/dev/null 2>&1; then
            # PR already has an env — make sure it's running
            bcdock env resume "$ENV_NAME" --wait || true
          else
            bcdock env create --name "$ENV_NAME" --version 27 --country au --wait \
              --wait-timeout 30m
          fi

      - name: Pull AL symbols
        run: bcdock env download-symbols "$ENV_NAME" --out-dir .alpackages

      - name: Compile extension
        run: bcdock al compile --env "$ENV_NAME" --out build/MyExtension.app

      - name: Publish into BC
        run: bcdock env publish "$ENV_NAME" build/MyExtension.app

      - name: Run AL tests
        # TODO: wire your test runner here. BCDock doesn't ship a generic
        # "run all AL tests" verb yet — `bcdock env test` is on the CLI
        # roadmap. Concrete options today:
        #   1. Publish a separate `MyApp.Tests.app` extension that exposes
        #      a webservice-callable codeunit; invoke it via OData using
        #      `webServiceAccessKey` from `bcdock env get -o json`.
        #   2. Use the BC Test Tool page from a smoke script.
        #   3. Skip CI tests until `bcdock env test` ships and run AL tests
        #      locally via VS Code's AL Test Runner.
        # Until then, this step is a placeholder.
        run: echo "(no AL tests wired — see TODO above)"

      - name: Comment env URL on PR
        uses: actions/github-script@v7
        with:
          script: |
            const url = require('child_process')
              .execSync(`bcdock env get ${process.env.ENV_NAME} -o json`)
              .toString();
            const env = JSON.parse(url);
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `BC environment ready: ${env.webClientUrl}\n\nLogin: \`Administrator\` / see \`bcdock env get ${env.name}\``
            });

      - name: Hibernate when done (drops billing to A$25/mo stored)
        if: always()
        run: bcdock env hibernate "$ENV_NAME" --wait || true

  teardown:
    if: github.event.action == 'closed'
    runs-on: ubuntu-latest
    steps:
      - name: Install bcdock
        run: curl -fsSL https://cli.bcdock.io/install.sh | sh

      - name: Delete environment
        run: bcdock env delete "$ENV_NAME" --force --wait || true

Patterns worth knowing

Hibernate, don't delete, between pushes

A PR with 10 pushes to the same branch = 10 workflow runs. If each run creates a fresh env, you pay ~10 active provisions + 10 deletions. Hibernating between runs and resuming on the next push:

  • Same active runtime per run (the env is running while tests are)
  • Stored cost between runs: ~A$0.83/day per PR
  • One-time create cost — subsequent pushes resume in ~1 min vs. ~2 min cold create

The template above does this with the bcdock env get … || bcdock env create … pattern.

Concurrency group per PR

concurrency:
  group: bcdock-pr-${{ github.event.pull_request.number }}
  cancel-in-progress: false

Without this, a fast git push followed by git push --force can run two jobs in parallel that both try to publish the same extension into the same env. The second one wins but the first wastes pool time and may corrupt the package state mid-install.

Don't fail the whole job on hibernate failure

- name: Hibernate when done
  if: always()
  run: bcdock env hibernate "$ENV_NAME" --wait || true

The || true swallows the exit code. A failed hibernate (env already in failed state, etc.) shouldn't mark the test job red — that's a billing concern, not a test result. The if: always() guarantees the step runs even when an earlier step failed.

Per-PR teardown on close

The teardown job triggers on closed (whether merged or abandoned). If a PR sits closed without teardown, you keep paying the stored rate until manually cleaned up — the autoscaler doesn't garbage-collect customer envs.

For very long-lived PRs (multi-week WIP branches), you might want scheduled hibernate:

on:
  schedule:
    - cron: '0 18 * * *'  # 6pm UTC daily

Secret hygiene

  • Never print BCDOCK_TOKEN to logs. GitHub auto-masks anything matching secrets.*, but a set -x or accidental echo defeats that.
  • Scope the token narrowlyenv:write is enough for the standard loop; don't add admin "for safety."
  • Rotate when contributors leave. The portal's API keys page lets you revoke a single key without touching others.
  • One token per repo, not one per CI vendor. Reduces blast radius and makes audit log entries easy to attribute.

Cost estimate

For a 10-PR-per-day team on Professional tier (A$389/mo per active env, A$25/mo stored):

  • Active runtime: ~5 min per push × ~3 pushes per PR × 10 PRs/day = ~2.5 hours/day active
  • Equivalent: A$1.50/day in active rate (well under the monthly cap, so monthly cap doesn't apply unless one env runs >=22h/day)
  • Stored: ~A$25/month per open PR (typical 3–5 open PRs at any time)

Total: roughly A$50–80/month for full PR-by-PR BC test environments on a 10-PR/day team. Compare to one always-on shared "BC test server" at A$389+/month with serialised tests and the inevitable shared-state corruption.

Next steps