Two ways to deploy, and when each makes sense
PandaStack's default model is push-to-deploy: connect a Git repo, and every push to your production branch triggers a build (rootless BuildKit in an ephemeral Kubernetes Job) and a Helm rollout. For a lot of projects that's all you want — the tagline is literally "Push code. It runs."
But as a team grows, you usually want a gate *before* code becomes a deploy: run the test suite, lint, type-check, maybe a security scan, and only deploy if everything is green. That's exactly the job GitHub Actions does well. The pattern is: GitHub Actions runs CI, and a successful CI run is what allows the deploy to happen.
There are two clean approaches.
Approach 1: CI gate + auto-deploy on a protected branch
The simplest robust setup keeps PandaStack's native push-to-deploy but puts a branch protection rule in front of it. You only allow merges to main when CI passes; PandaStack deploys main.
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test -- --coverage
- run: npm run buildThen in GitHub → Settings → Branches, require the test check to pass before merging. Because PandaStack deploys on push to main, and nothing reaches main without passing CI, you get a tested-only production branch with zero extra deploy wiring.
This is the approach I recommend for most teams: it keeps the deploy mechanism native (so you keep live logs, deploy history, and one-click rollback) and uses Actions purely as the quality gate.
Approach 2: Build once in CI, deploy the exact artifact
Some teams want CI to build the container image and have the platform deploy *that exact image*, so the thing you tested is byte-for-byte the thing that ships. With PandaStack you'd typically still let the platform build (its BuildKit pipeline is reproducible and pushes to Artifact Registry), but you can drive deploys via the API from Actions when you want CI to be the orchestrator.
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # enables required reviewers / approvals
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci && npm test
- name: Trigger PandaStack deploy
env:
PANDA_TOKEN: ${{ secrets.PANDASTACK_API_TOKEN }}
run: |
curl -fsS -X POST \
-H "Authorization: Bearer $PANDA_TOKEN" \
-H "Content-Type: application/json" \
https://dashboard.pandastack.io/api/v1/projects/$PROJECT_ID/deployStore the token in GitHub → Settings → Secrets and variables → Actions, never in the repo. Use a GitHub Environment with required reviewers if you want a human approval step before production deploys.
Adding approvals and manual gates
GitHub Environments are the cleanest way to add a release gate. Define a production environment, add required reviewers, and any job referencing environment: production will pause until someone approves:
jobs:
deploy:
environment:
name: production
url: https://app.example.comThis pairs nicely with a staging deploy that runs automatically and a production deploy that waits for a click.
Recommended pipeline shape
| Stage | Trigger | What runs | Deploys to |
|---|---|---|---|
| CI | PR to main | lint, type-check, tests, build | nothing |
| Staging | merge to main | smoke tests | staging app |
| Production | manual approval | — | production app |
This gives you fast feedback on PRs, an always-current staging environment, and a deliberate production release.
Practical tips
- Cache dependencies.
actions/setup-nodewithcache: npm(orpip/go) cuts minutes off every run. - Fail fast. Put lint and type-check before the test suite so cheap failures surface first.
- Pin action versions to a major (
@v4) at minimum; pin to a SHA for supply-chain-sensitive repos. - Keep secrets in GitHub or PandaStack env vars, never committed. PandaStack injects env vars (including the auto-wired
DATABASE_URLfor managed databases) at runtime. - Watch your build minutes. PandaStack's Free tier includes 300 build minutes/month, Pro 1000, Premium 2500. If CI also builds, you're spending GitHub minutes separately — let the platform own the production image build to avoid double-paying.
The honest trade-off
If you go full Approach 2 and orchestrate everything from Actions, you take on more YAML and more moving parts. For most teams, Approach 1 — native push-to-deploy gated by a required CI check — gives you 90% of the safety with a fraction of the maintenance. Start there, and only reach for API-driven deploys when you have a concrete reason (multi-environment promotion, build-once semantics, or a release tool you already standardize on).
References
- [GitHub Actions documentation](https://docs.github.com/en/actions)
- [Using environments for deployment (approvals)](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment)
- [Managing branch protection rules](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches)
- [Encrypted secrets in GitHub Actions](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions)
Want CI-gated deploys with live build logs and one-click rollback? Spin up a project on PandaStack's free tier: [dashboard.pandastack.io](https://dashboard.pandastack.io)