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) ghCLI installed locally (or GitHub web UI access)- A GitHub repo with admin permissions
Step 1 — Create a PaaS API token¶
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:
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:
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:
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.