The typical Angular deployment story ends at ng build --configuration production and a pile of static files uploaded somewhere. The part that breaks in production is what happens when the app needs runtime configuration — API endpoints that differ between staging and prod, feature flags, or third-party keys that should never touch version control.
PandaStack lets you script deploys from CI using its REST API, inject secrets at build time, and rotate them later without a Git commit. This is the setup for teams that treat deploys as an API call, not a manual dashboard operation.
The problem: build-time variables in a static app
Angular apps compile to static HTML and JavaScript. Environment variables must be baked in at build time because there is no server to read process.env when a user loads the page. If you hard-code API_URL in environment.ts and commit it, every developer sees production keys. If you leave it blank, the build works but the deployed app cannot call your backend.
The fix is to inject variables during the CI build step, after the code is cloned and before ng build runs. PandaStack reads the env array you send via the API and makes those values available to the buildpack during the install and build phases.
Deploy via the API with a bearer token
Create a project and trigger the first deploy from your CI pipeline. You will need a psk_live_ token from the dashboard under Settings → API Tokens.
curl -X POST https://api.pandastack.io/v1/projects \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"slug": "static",
"name": "angular-prod",
"repositoryName": "acme/angular-storefront",
"branch": "main",
"autoDeploy": true,
"buildCommand": "npm run build --configuration production",
"outputDir": "dist/angular-storefront/browser",
"env": [
{ "name": "NG_APP_API_URL", "value": "https://api.acme.com" },
{ "name": "NG_APP_STRIPE_KEY", "value": "pk_live_..." }
]
}'The response includes projectId and deploymentUuid. Store the project ID in your CI environment so subsequent deploys can reference it.
PandaStack clones the repo, installs dependencies (respecting your lock file to choose npm, yarn, or pnpm automatically), runs the build command with those environment variables set, and serves the output directory from a CDN. The build logs stream to the dashboard Logs tab in real time.
Wire the variables into Angular's build
Angular does not read arbitrary environment variables by default. You must configure the build to substitute them into the compiled code. One common pattern is to replace placeholders in environment.prod.ts during the build.
Install dotenv and yargs for a build script:
npm install --save-dev dotenv yargsCreate scripts/set-env.ts:
import { writeFileSync } from 'fs';
const targetPath = './src/environments/environment.prod.ts';
const envConfigFile = `export const environment = {
production: true,
apiUrl: '${process.env['NG_APP_API_URL']}',
stripeKey: '${process.env['NG_APP_STRIPE_KEY']}'
};
`;
writeFileSync(targetPath, envConfigFile);
console.log(`Wrote environment config to ${targetPath}`);Update package.json to run this before the build:
{
"scripts": {
"config": "tsx scripts/set-env.ts",
"build:prod": "npm run config && ng build --configuration production"
}
}Change the PandaStack API payload to use npm run build:prod as the build command. Now NG_APP_API_URL flows from the API request into environment.production.apiUrl and gets compiled into the app bundle.
Rotate a secret without re-deploying
Secrets expire. API keys rotate. Feature flags flip. Committing a new value to environment.ts and waiting for CI to rebuild is slow and leaks the secret into Git history.
Instead, update the environment variable via the API and redeploy:
curl -X PUT https://api.pandastack.io/v1/projects/$PROJECT_ID/env \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"env": [
{ "name": "NG_APP_STRIPE_KEY", "value": "pk_live_new..." }
]
}'
curl -X POST https://api.pandastack.io/v1/projects/$PROJECT_ID/deploy \
-H "Authorization: Bearer $PANDASTACK_TOKEN"The platform re-runs the build with the updated value. The old bundle stays live until the new one finishes, so there is no downtime. Users who load the app after the deploy get the new key; in-flight sessions keep working with cached assets until they refresh.
Use pandastack.json to document required variables
The API is great for CI, but another developer cloning the repo and clicking "Deploy" in the dashboard will not know which variables to set. Commit a pandastack.json to the repo root to prompt for them:
{
"type": "static",
"name": "angular-storefront",
"buildCommand": "npm run build:prod",
"outputDir": "dist/angular-storefront/browser",
"env": [
{
"key": "NG_APP_API_URL",
"description": "Backend API base URL"
},
{
"key": "NG_APP_STRIPE_KEY",
"description": "Stripe publishable key (pk_live_ or pk_test_)"
}
]
}When someone deploys from the dashboard, PandaStack reads this file from the public GitHub API and shows a form with labeled input fields. They fill in the values, click Deploy, and the build gets the same treatment as the CI path.
This is the difference between a repo that only you can deploy (because you memorized the required variables) and one that the whole team can ship.
Set up GitHub Actions to deploy on merge
Add a workflow file at .github/workflows/deploy.yml:
name: Deploy to PandaStack
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Trigger deploy
run: |
curl -X POST https://api.pandastack.io/v1/projects/${{ secrets.PANDASTACK_PROJECT_ID }}/deploy \
-H "Authorization: Bearer ${{ secrets.PANDASTACK_TOKEN }}" \
-fStore PANDASTACK_TOKEN and PANDASTACK_PROJECT_ID in the repo's GitHub Actions secrets. Every push to main triggers a rebuild and deploy. The -f flag fails the job if the API returns an error, so broken deploys block the CI pipeline.
For multi-environment setups, create separate projects for staging and production, each with its own set of environment variables. The workflow can deploy to staging on every push and to production only on tagged releases.
Debugging a failed build
If the build fails, the dashboard Logs tab shows the full output. Common issues:
- Build command not found: The buildpack auto-detects
npmvsyarnvspnpmbased on the lock file. If you use Bun, set an install command override (not yet exposed in the API, use the dashboard or CLI for now). - Output directory missing: Angular changed its default output path between versions. Check
angular.json→projects.architect.build.options.outputPathand match it to theoutputDirin your API payload. - Environment variable undefined: The build script runs before
ng build, so variables must be set by the platform. Logprocess.envinset-env.tsto verify they are present.
The free tier includes 300 build minutes per month, enough for dozens of Angular builds. Paid plans increase this to 1000 or 2500 minutes, and builds run in parallel so multiple commits can deploy simultaneously without queuing.
Why this beats committing secrets
Secrets in Git eventually leak. A contractor clones the repo, your laptop gets stolen, or someone force-pushes an old commit with production keys still in it. Storing them in the platform's encrypted environment store and injecting them at deploy time breaks the link between code and credentials.
When a key expires, you rotate it in one place (the API or dashboard) instead of committing to every repo that uses it. When an employee leaves, you revoke their platform access and regenerate tokens, and the repos stay unchanged.
PandaStack's API-first design makes this the default workflow instead of an advanced feature you configure later.
References
- [Angular deployment guide](https://angular.io/guide/deployment)
- [PandaStack API documentation](https://docs.pandastack.io)
- [GitHub Actions documentation](https://docs.github.com/en/actions)