---
title: CI/CD
nav: CI/CD
group: Automate and AI
updated: 2026-11-13
description: Test and deploy your SashiDo app's cloud code automatically. Staging and production apps, GitHub Actions that test, push and wait for the deployment, and environment variables from CI.
keywords: parse server ci cd, sashido github actions, deploy parse cloud code automatically, parse server staging, continuous deployment parse
---

SashiDo deploys whatever is pushed to your app's repository's `master` branch: a push is a deploy. A pipeline adds what comes before (tests, a staging app) and after (waiting until the deployment is live, then checking it).

## Staging and production

Use two SashiDo apps, e.g. `shop-staging` and `shop`, each with its own repository, keys and database. Keep your code in your own repository, and add both SashiDo repositories as remotes:

```bash
git remote add staging    git@github.com:<sashido-org>/<staging-repo>.git
git remote add production git@github.com:<sashido-org>/<production-repo>.git

git push staging main:master       # deploy to staging
git push production main:master    # promote the same commit
```

The repositories' addresses are in **Core › Cloud Code** of each app.

## A GitHub Actions pipeline

Test, deploy to staging, wait until it's live and healthy:

```yaml
# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 18 }
      - run: npm ci
      - run: npx jest

  staging:
    needs: test
    runs-on: ubuntu-latest
    env:
      SASHIDO_TOKEN: ${{ secrets.SASHIDO_TOKEN }}
      APP: ${{ vars.SASHIDO_STAGING_APP }}
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - name: Push to the staging app
        run: |
          git remote add staging "https://x-access-token:${{ secrets.SASHIDO_REPO_TOKEN }}@github.com/${{ vars.SASHIDO_STAGING_REPO }}.git"
          git push staging HEAD:master --force
      - name: Wait until it's live
        run: |
          api() { curl -sf -H "Authorization: Bearer $SASHIDO_TOKEN" -H "X-SashiDo-Client: github-actions" "$@"; }
          before=$(api "https://api.sashido.io/apps/$APP/deployments?limit=1" | jq '.data[0].number')
          for i in $(seq 1 90); do
            read number phase <<< "$(api "https://api.sashido.io/apps/$APP/deployments?limit=1" | jq -r '.data[0] | "\(.number) \(.phase)"')"
            if [ "$number" != "$before" ]; then
              case "$phase" in
                Active) break ;;
                Failed|Cancelled) echo "Deployment $number: $phase"; exit 1 ;;
              esac
            fi
            sleep 10
          done
          api "https://api.sashido.io/apps/$APP/parse/health" | jq -e '.status == "ok"'
```

What it needs:

| Secret or variable | |
|---|---|
| `SASHIDO_TOKEN` | A [personal access token](/en/docs/developers/authenticate) with `apps:read`, limited to the staging app |
| `SASHIDO_REPO_TOKEN` | A GitHub token of an account with access to the SashiDo repository |
| `SASHIDO_STAGING_APP` | The app's `objectId` (`GET /users/info`) |
| `SASHIDO_STAGING_REPO` | The repository, `<org>/<repo>` |

A push builds first and then deploys, so the loop waits for a deployment newer than the one before the push. Promote to production the same way, e.g. on a release tag or with a manual approval.

## Environment variables from CI

Keep secrets in your CI's secret store and set them through the API: `PUT /apps/{id}/env` replaces your variables and redeploys the app with them. See the [recipe](/en/docs/developers/api#change-one-environment-variable).

## Before a Parse Server upgrade

Run your tests against the new version locally (`npm install parse-server@<new version>`), and ask `plan_parse_server_upgrade` through [MCP](/en/docs/developers/mcp) what your code needs. Then switch the staging app's version, run the pipeline, and only then production.
