Your FastAPI application has a dozen contributors, pull requests merge several times a day, and you need production to update automatically without manual dashboard clicks. The PandaStack REST API makes this a three-line curl command in GitHub Actions, triggered on every push to main.
The key is a psk_ API token with the organization baked in, so CI scripts do not need to manage session credentials or pass organization headers. One token, one API call, and the platform handles the rest — build, deploy, health checks, and rollback on failure.
Why CI deployments need an API, not a dashboard
Manual deployments introduce a human bottleneck. A developer merges a pull request, switches to the dashboard, navigates to the project, clicks Deploy, and waits for the build to finish. If the deploy fails, they troubleshoot in the dashboard UI, fix the issue, and redeploy.
This works for solo projects, but breaks down with teams. Multiple merges in an hour mean multiple manual deploys, and the person who merged the code is not always the person who deploys it. The deploy step becomes a coordination problem.
The PandaStack API solves this by making deployments scriptable. A GitHub Actions workflow triggers on push, calls the API to redeploy, and posts the build logs to Slack if it fails. The developer who merged the PR gets immediate feedback without leaving their terminal.
Generate a PandaStack API token
Log in to the PandaStack dashboard and navigate to Settings → API Tokens. Click "Create Token" and name it something CI-specific, like github-actions-deploy.
The token starts with psk_live_ and includes your organization ID in the token itself. This is the critical difference from session JWTs: a psk_ token does not require an x-organization-id header, so CI scripts stay simple.
Copy the token immediately — it is only shown once. Add it to your GitHub repository as a secret:
- 1Go to your GitHub repo → Settings → Secrets and variables → Actions
- 2Click "New repository secret"
- 3Name it
PANDASTACK_TOKEN - 4Paste the
psk_live_...token
This secret is now available to GitHub Actions workflows as ${{ secrets.PANDASTACK_TOKEN }}.
Create a FastAPI application
Start with a minimal FastAPI app that connects to PostgreSQL and exposes a health check endpoint:
# main.py
import os
from fastapi import FastAPI
from sqlalchemy import create_engine, text
DATABASE_URL = os.getenv("DATABASE_URL")
if not DATABASE_URL:
raise ValueError("DATABASE_URL environment variable is required")
engine = create_engine(DATABASE_URL, pool_pre_ping=True)
app = FastAPI()
@app.get("/health")
def health():
return {"status": "ok"}
@app.get("/users")
def get_users():
with engine.connect() as conn:
result = conn.execute(text("SELECT COUNT(*) FROM users"))
count = result.scalar()
return {"user_count": count}Add dependencies in requirements.txt:
fastapi
uvicorn[standard]
sqlalchemy
psycopg2-binaryCommit this to a GitHub repository and push to main.
Deploy the FastAPI app via the REST API
The PandaStack API lives at https://api.pandastack.io/v1. To create a project from a Git repository, POST to /v1/projects with repository details and environment variables:
curl -X POST https://api.pandastack.io/v1/projects \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"slug": "container",
"name": "fastapi-prod",
"repositoryName": "yourusername/fastapi-app",
"branch": "main",
"autoDeploy": true,
"env": [
{ "name": "DATABASE_URL", "value": "postgres://user:pass@host:5432/dbname" }
]
}'The slug field controls deployment type: "auto" detects static vs container, "static" forces a static build, and "container" treats it as a container app. FastAPI is a container app, so use "container".
The response envelope looks like this:
{
"success": true,
"data": {
"projectId": 42,
"name": "fastapi-prod",
"deploymentId": 1001,
"deploymentUuid": "d4f7c3b9-8a2e-4f1d-9c6b-5e3a7b2d8f4c"
}
}Save projectId — you will need it for redeployments and log queries.
Automate redeployments with GitHub Actions
Create .github/workflows/deploy.yml in your repository:
name: Deploy to Production
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Trigger PandaStack deployment
run: |
curl -X POST https://api.pandastack.io/v1/projects/42/deploy \
-H "Authorization: Bearer ${{ secrets.PANDASTACK_TOKEN }}" \
-H "Content-Type: application/json"
- name: Notify Slack on failure
if: failure()
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} \
-H "Content-Type: application/json" \
-d '{"text": "FastAPI production deploy failed. Check logs at https://dashboard.pandastack.io/projects/42"}'Replace 42 with the projectId from the earlier API response. This workflow triggers on every push to main, calls the PandaStack API to redeploy, and posts to Slack if the build fails.
The /v1/projects/{id}/deploy endpoint rebuilds from the latest commit on the configured branch, runs health checks, and promotes the new deployment to production. If health checks fail, the old deployment stays live.
Add a deploy button to the README
For one-click deployments from the repository landing page, add a deploy button:
[](https://dashboard.pandastack.io/deploy?repo=yourusername/fastapi-app&type=container&lang=python&env=DATABASE_URL)The query parameters pre-seed the deploy form:
repo(required):owner/repoformattype:staticorcontainerlang:auto,nodejs,python,go, ordockerenv: comma-separated list of environment variable names (DATABASE_URL,JWT_SECRET)
When someone clicks the button, the deploy screen prompts for DATABASE_URL before starting the build. This prevents the silent failure mode where the app builds successfully but crashes on first request due to missing configuration.
If the repository contains a pandastack.json file, its values override the query parameters. This is the same Vercel-parity behavior — a repo's Vercel button URL works with just a domain swap.
Stream build logs from the API
The API exposes build logs at /v1/projects/{id}/deployments/{deploymentId}/logs. GitHub Actions can poll this endpoint to stream logs into the CI job output:
- name: Stream deployment logs
run: |
DEPLOYMENT_ID=$(curl -s https://api.pandastack.io/v1/projects/42/deployments \
-H "Authorization: Bearer ${{ secrets.PANDASTACK_TOKEN }}" \
| jq -r '.data[0].deploymentId')
curl -N https://api.pandastack.io/v1/projects/42/deployments/$DEPLOYMENT_ID/logs \
-H "Authorization: Bearer ${{ secrets.PANDASTACK_TOKEN }}"The -N flag disables curl's buffering, so logs appear in real time. This is useful for debugging failed builds without switching to the dashboard.
For live troubleshooting, the dashboard Logs tab is more reliable. The CLI's panda projects logs command currently has streaming issues, so avoid building workflows around it.
Provision a managed PostgreSQL database
If you do not yet have a DATABASE_URL, provision a managed PostgreSQL instance through the API:
curl -X POST https://api.pandastack.io/v1/databases \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "fastapi-db",
"engine": "postgres",
"version": "16"
}'The response includes the connection string. Update the FastAPI project's environment variables:
curl -X PATCH https://api.pandastack.io/v1/projects/42/env \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"env": [
{ "name": "DATABASE_URL", "value": "postgres://postgres:generatedpass@fastapi-db.internal:5432/postgres" }
]
}'Trigger a redeploy to inject the new variable:
curl -X POST https://api.pandastack.io/v1/projects/42/deploy \
-H "Authorization: Bearer $PANDASTACK_TOKEN"The free tier includes one database with seven days of backup retention and a 50-connection limit. Paid plans increase retention to 15 or 30 days and raise the connection limit to 300 or 1000.
Rollback a failed deployment
If a deployment introduces a breaking change, roll back to the previous version via the API:
curl -X POST https://api.pandastack.io/v1/projects/42/rollback \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"deploymentId": 1000
}'Replace 1000 with the deploymentId of the last known-good deployment. The platform reverts to that version immediately without rebuilding.
The free tier retains 10 days of deployment history, so rollback works for recent deploys. Pro retains 30 days, and Premium retains 90 days.
Why API-driven deployments scale better
Manual deployments work until they do not. The first time two developers try to deploy simultaneously, or a critical hotfix needs to go out while the person with dashboard access is offline, the manual workflow breaks down.
API-driven deployments remove the human step. Merge a pull request, and the deployment happens automatically. The build logs appear in Slack or the GitHub Actions job output, so the entire team sees the result without switching contexts.
The psk_ token model keeps CI scripts simple. There is no session management, no token refresh logic, and no organization ID to track separately. One token, valid until revoked, with the organization baked in.
For teams already using GitHub Actions for testing, adding deployment is a three-line workflow file. The same pattern works in GitLab CI, CircleCI, or any CI system that can run curl.
Start with the free tier: five container apps, five static sites, one database, and 300 build minutes per month. For production workloads, the Pro plan at $15 per month adds 1000 build minutes, 500 GB bandwidth, and 15-day backup retention.
References
- [FastAPI deployment documentation](https://fastapi.tiangolo.com/deployment/)
- [PandaStack API reference](https://docs.pandastack.io/api/)
- [GitHub Actions workflow syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions)
- [PandaStack deploy button guide](https://docs.pandastack.io/projects/deploy-button)