A documentation site built with Astro should deploy automatically when you push to main. Manually clicking Deploy in a dashboard slows down the release cycle and breaks the Git-centric workflow. GitHub Actions can trigger a PandaStack deploy via the REST API, and the entire process — from commit to live site — takes under two minutes.
The key piece is a psk_ token, which authenticates API requests without needing a user session. PandaStack generates one per organization, and it stays valid until you revoke it. Store it as a GitHub secret, and your workflow can deploy on every push.
Create a PandaStack API token
Log in to the dashboard, go to Settings → API Tokens, and generate a new token. Copy the value — it looks like psk_live_abc123xyz — and never commit it to Git.
Add it as a GitHub repository secret:
- 1Go to your repo → Settings → Secrets and variables → Actions
- 2Click New repository secret
- 3Name:
PANDASTACK_TOKEN - 4Value:
psk_live_abc123xyz
Now GitHub Actions can reference ${{ secrets.PANDASTACK_TOKEN }} in workflows.
Set up the Astro project
Your Astro config should define the output directory (usually dist):
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
outDir: 'dist'
});Commit a pandastack.json to declare the build configuration:
{
"type": "static",
"language": "nodejs",
"buildCommand": "npm run build",
"outputDir": "dist"
}This file isn't required for the API deploy, but it helps if someone uses the deploy button or CLI later.
Create the project on PandaStack
Use the API to register the Astro site as a project:
curl -X POST https://api.pandastack.io/v1/projects \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"slug": "static",
"name": "astro-docs",
"repositoryName": "acme/astro-docs",
"branch": "main",
"autoDeploy": false
}'Set autoDeploy: false because GitHub Actions will trigger deploys explicitly. The response includes a projectId — save it for the next step.
Write the GitHub Actions workflow
Create .github/workflows/deploy.yml:
name: Deploy to PandaStack
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Trigger PandaStack deploy
run: |
curl -X POST https://api.pandastack.io/v1/projects/<project-id>/deploy \
-H "Authorization: Bearer ${{ secrets.PANDASTACK_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"branch": "main"}'Replace with the projectId from the previous step. Commit and push this file to main.
What happens on push
Every time you push to main:
- 1GitHub Actions runs the workflow
- 2The workflow POSTs to
/v1/projects//deploy - 3PandaStack clones the repository, runs
npm installandnpm run build, uploadsdist/to a CDN - 4The deploy finishes in 60-90 seconds
- 5The site is live at
https://astro-docs.pandastack.app
Check the GitHub Actions log to see the API response. It includes a deploymentId you can use to fetch build logs.
Stream build logs in the workflow
The deploy endpoint returns a deploymentUuid. Use it to poll the build status:
- name: Trigger deploy
id: deploy
run: |
response=$(curl -s -X POST https://api.pandastack.io/v1/projects/<project-id>/deploy \
-H "Authorization: Bearer ${{ secrets.PANDASTACK_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"branch": "main"}')
echo "response=$response" >> $GITHUB_OUTPUT
deploymentUuid=$(echo $response | jq -r '.data.deploymentUuid')
echo "deploymentUuid=$deploymentUuid" >> $GITHUB_OUTPUT
- name: Wait for build
run: |
deploymentUuid=${{ steps.deploy.outputs.deploymentUuid }}
while true; do
status=$(curl -s https://api.pandastack.io/v1/projects/<project-id>/deployments/$deploymentUuid \
-H "Authorization: Bearer ${{ secrets.PANDASTACK_TOKEN }}" \
| jq -r '.data.status')
echo "Deploy status: $status"
if [ "$status" = "RUNNING" ]; then
exit 0
elif [ "$status" = "FAILED" ]; then
exit 1
fi
sleep 10
doneThis polls the deployment status every 10 seconds until it succeeds or fails. If the build fails, the GitHub Actions job fails too, and you get a notification.
Deploy preview environments for pull requests
Extend the workflow to deploy every pull request as a separate preview:
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Set branch and project name
id: vars
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "branch=${{ github.head_ref }}" >> $GITHUB_OUTPUT
echo "project_name=astro-docs-pr-${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
else
echo "branch=main" >> $GITHUB_OUTPUT
echo "project_name=astro-docs" >> $GITHUB_OUTPUT
fi
- name: Create or deploy project
run: |
curl -X POST https://api.pandastack.io/v1/projects \
-H "Authorization: Bearer ${{ secrets.PANDASTACK_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{
"slug": "static",
"name": "${{ steps.vars.outputs.project_name }}",
"repositoryName": "acme/astro-docs",
"branch": "${{ steps.vars.outputs.branch }}",
"autoDeploy": false
}' || true
curl -X POST https://api.pandastack.io/v1/projects/${{ steps.vars.outputs.project_name }}/deploy \
-H "Authorization: Bearer ${{ secrets.PANDASTACK_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"branch": "${{ steps.vars.outputs.branch }}"}'Now every pull request gets its own deploy at https://astro-docs-pr-42.pandastack.app. Reviewers can test changes before merging.
Clean up preview deploys
Add a workflow that deletes the preview project when the pull request closes:
name: Cleanup preview
on:
pull_request:
types: [closed]
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Delete preview project
run: |
curl -X DELETE https://api.pandastack.io/v1/projects/astro-docs-pr-${{ github.event.pull_request.number }} \
-H "Authorization: Bearer ${{ secrets.PANDASTACK_TOKEN }}"This keeps your dashboard clean and avoids paying for idle previews.
Use the CLI instead of curl
If you prefer the CLI, add a step that installs it:
- name: Install PandaStack CLI
run: |
curl -fsSL https://cli.pandastack.io/install.sh | sh
echo "$HOME/.pandastack/bin" >> $GITHUB_PATH
- name: Deploy
env:
PANDASTACK_TOKEN: ${{ secrets.PANDASTACK_TOKEN }}
run: |
panda login --token $PANDASTACK_TOKEN
panda projects deploy astro-docsThe CLI handles authentication and polling automatically, so the workflow is shorter. The trade-off is an extra dependency.
Debugging deploy failures
401 Unauthorized: The PANDASTACK_TOKEN secret is missing or invalid. Check Settings → Secrets in GitHub.
404 Project not found: The project name or ID is wrong. Verify it with curl https://api.pandastack.io/v1/projects -H "Authorization: Bearer $PANDASTACK_TOKEN".
Build succeeds but site doesn't update: Check that the outputDir in pandastack.json matches the actual build output. Astro uses dist by default, but some configurations change it to build or public.
GitHub Actions timeout: The deploy took longer than the workflow timeout (default 1 hour). Increase it with timeout-minutes: 120 in the job definition.
Compare to webhook-based deploys
An alternative to the API workflow is enabling autoDeploy: true on the project. PandaStack listens for GitHub push events and deploys automatically. The downside is less control: you can't run tests before deploying, and preview environments are harder to set up.
The API workflow gives you full control over when and how deploys happen, which is better for production workflows.
References
- [PandaStack API documentation](https://docs.pandastack.io/api)
- [GitHub Actions secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets)
- [Astro build configuration](https://docs.astro.build/en/reference/configuration-reference/)