Skip to content

GitHub Actions CI/CD

Trigger PaaS deploys from GitHub Actions on every push to main (and create preview environments for pull requests). This recipe covers the full setup: token, secrets, workflow file, and review apps.

Overview

PaaS exposes a CLI deploy command that authenticates via a personal access token. GitHub Actions stores that token as an encrypted secret, then runs paas deploy on every push.

sequenceDiagram
  participant Dev
  participant GitHub
  participant PaaS
  Dev->>GitHub: git push main
  GitHub->>GitHub: Actions runner spawns
  GitHub->>PaaS: paas deploy (with PAT)
  PaaS->>PaaS: build + rollout
  PaaS-->>GitHub: deploy logs streamed
  GitHub-->>Dev: ✅ commit status

Prerequisites

  • A PaaS app already created (paas apps create my-app)
  • gh CLI installed locally (or GitHub web UI access)
  • A GitHub repo with admin permissions

Step 1 — Create a PaaS API token

paas tokens:create --name "github-ci" --ttl 365d

Output:

✓ Created token  (expires 2027-05-04T13:42Z)
  PAAS_TOKEN=paas_pat_AbCdEf1234...
  Save this — it cannot be retrieved later.

The token has the same permissions as your user for the apps in your tenant. Restrict it via:

paas tokens:create --name "github-ci-deploy-only" \
  --scope "apps:my-app:deploy" \
  --ttl 365d

Step 2 — Store the token in GitHub secrets

gh secret set PAAS_TOKEN --body "paas_pat_AbCdEf1234..."
gh secret set PAAS_APP_NAME --body "my-app"

Or via the GitHub UI: Settings → Secrets and variables → Actions → New repository secret.

Step 3 — Add the workflow

Create .github/workflows/deploy.yml:

name: Deploy to PaaS

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # paas needs the full history for accurate SHAs

      - name: Install paas CLI
        run: |
          curl -fsSL https://get.di2amp.com/install.sh | sh
          paas --version

      - name: Deploy
        env:
          PAAS_TOKEN: ${{ secrets.PAAS_TOKEN }}
          PAAS_APP: ${{ secrets.PAAS_APP_NAME }}
        run: |
          paas deploy --wait --timeout 600
          paas releases | head -3

The --wait flag blocks the job until the rollout finishes (or --timeout elapses). Without it, the job exits immediately after the build is queued.

Step 4 — Verify

Push a small commit:

echo "<!-- ci test -->" >> README.md
git add README.md && git commit -m "ci: smoke deploy"
git push origin main

Watch:

gh run watch

You should see the build + rollout output streamed in the runner logs:

Run paas deploy --wait
✓ Build started   (job: build-abc1234)
→ Detected: nodejs (paketo-buildpacks/nodejs)
→ Image:    registry.di2amp.com/octave/my-app:abc1234
→ Rollout:  v1 → v2 (canary 10% → 100%)
✓ Released v2 (live in 47s)

Step 5 — Secrets vs config

Don't hard-code secrets in the workflow:

# ❌ WRONG — secret in repo file
- run: paas secrets:set STRIPE_API_KEY=sk_live_xxx

# ✅ RIGHT — pass through GitHub secret
- env:
    STRIPE_API_KEY: ${{ secrets.STRIPE_API_KEY }}
  run: paas secrets:set STRIPE_API_KEY="$STRIPE_API_KEY"

For non-sensitive vars, use paas config:set directly in the workflow:

- run: |
    paas config:set NODE_ENV=production
    paas config:set GIT_SHA=${{ github.sha }}

Review apps (preview envs on PR)

Add a second job that creates a temporary preview env on every pull request:

on:
  pull_request:
    branches: [main]

jobs:
  preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install paas CLI
        run: curl -fsSL https://get.di2amp.com/install.sh | sh
      - name: Create preview env
        env:
          PAAS_TOKEN: ${{ secrets.PAAS_TOKEN }}
        run: |
          BRANCH=${GITHUB_HEAD_REF//\//-}
          paas previews:create my-app --branch "$BRANCH" --wait
          PREVIEW_URL=$(paas previews:url my-app --branch "$BRANCH")
          echo "preview_url=$PREVIEW_URL" >> $GITHUB_OUTPUT
      - name: Comment on PR
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `🚀 Preview: ${{ steps.preview.outputs.preview_url }}`
            })

PaaS automatically deletes preview envs 7 days after the source branch is removed.

Caveats

  • Concurrency: a single deploy lane per app. If a second push lands while the first is rolling, it queues. Use concurrency: deploy-${{ github.ref }} if you want to cancel in-flight runs.
  • Token rotation: PaaS tokens expire by --ttl. Set a calendar reminder before expiry, or use OIDC federation (roadmap).
  • Build cache: GitHub-hosted runners are ephemeral, but PaaS keeps Paketo layer caches server-side keyed on the app — second pushes are fast even from a fresh runner.

See also