Deploy from CI
This guide sets up a job that deploys your service whenever main changes, first for GitHub
Actions, then for GitLab CI. The same steps work in any CI system that can run a shell script:
install the CLI, provide an API key, run gpilot deploy, and act on the exit code.
What you need
Section titled “What you need”-
An organization API key with the permissions for the steps your job runs: deploying, and optionally purging and pushing persisted queries. Create it in the portal under your organization’s Settings → API keys. Which permissions each step needs is listed under jobs; grant only those. Name it after the pipeline: the portal shows the key’s name as who made each deployment.
-
The key as a CI secret named
GPILOT_TOKEN: in GitHub under Settings → Secrets and variables → Actions, in GitLab as a masked CI/CD variable under Settings → CI/CD → Variables. The CLI reads that variable and uses it in place of a login. -
Your service’s subdomain, the
<subdomain>inhttps://<subdomain>.<label>.graphpilot.cloud. -
gpilot.toml, and for a GraphQL APIschema.graphql, in the directory the step runs in, or paths to them (--config,--schema). If your schema is generated, generate it in an earlier step; the Apollo Server, GraphQL Yoga and PostGraphile pages show how.
1. Add the job
Section titled “1. Add the job”GitHub Actions
Section titled “GitHub Actions”name: Deploy to GraphPilot
on: push: branches: [main]
# Deploy one push at a time, in order, rather than racing two deployments.concurrency: group: graphpilot-shop-api cancel-in-progress: false
jobs: deploy: runs-on: ubuntu-latest env: GPILOT_TOKEN: ${{ secrets.GPILOT_TOKEN }} steps: - uses: actions/checkout@v5
- name: Install the gpilot CLI run: | curl --proto '=https' --tlsv1.2 -fsSL https://get.graphpilot.io | sh echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Deploy run: gpilot deploy --service shop-api --yesReplace shop-api with your subdomain.
GitLab CI
Section titled “GitLab CI”The same three steps as a GitLab job. resource_group plays the part of concurrency above: one
deployment at a time, in order.
deploy-graphpilot: stage: deploy image: debian:bookworm-slim rules: - if: $CI_COMMIT_BRANCH == "main" resource_group: graphpilot-shop-api before_script: - apt-get update && apt-get install -y --no-install-recommends curl ca-certificates - curl --proto '=https' --tlsv1.2 -fsSL https://get.graphpilot.io | sh - export PATH="$HOME/.local/bin:$PATH" script: - gpilot deploy --service shop-api --yesGPILOT_TOKEN reaches the job as the CI/CD variable you created. GitLab sets CI=true as well.
2. Understand what each part does
Section titled “2. Understand what each part does”Installing the CLI
Section titled “Installing the CLI”The install script fetches the latest release every time it runs and puts gpilot in
~/.local/bin, which the workflow then adds to the PATH (see
installation).
Installing the latest release on every run is deliberate. The CLI checks its version against a
minimum that GraphPilot publishes with its releases, and a CLI below that minimum refuses to run and exits
with code 6. A version that is merely behind prints a warning and carries on. A pinned or
cached binary works until the next minimum passes it, and then your deployments stop.
If a release you depend on is ever refused before you can upgrade, set
GPILOT_SKIP_VERSION_CHECK=1 on the step to skip the check for that run. Treat it as a
temporary escape, not a setting to leave in place.
Running without prompts
Section titled “Running without prompts”GitHub Actions and GitLab CI set CI=true, and the CLI switches to non-interactive mode when it sees it (see
interactive vs. non-interactive). In that mode
gpilot deploy proceeds without asking, so --yes is not strictly needed for it. Pass it
anyway: it states the intent in the workflow. gpilot purge, below, refuses to run without it.
What gpilot deploy does
Section titled “What gpilot deploy does”gpilot deploy (short for gpilot deployment create) uploads gpilot.toml and, for a GraphQL
API, schema.graphql, starts a deployment, and waits for it to finish, 20 minutes at most by default (--timeout 45m to
change that). When the build completes or fails, it prints the deployment’s
diagnostics (schema and configuration): warnings on a success, the reasons on a failure. The
same diagnostics are in the portal, which also lists the configuration keys that do nothing;
the CLI does not print that list (see findings).
Without a schema file in the working directory and without --schema, the service is deployed
as plain HTTP, with GraphQL features off, and the job log says No schema found: deploying as a plain HTTP service, GraphQL features are off. For a GraphQL API, make sure the step runs where
schema.graphql is, or pass --schema: a missing file does not fail the step, it deploys the
service without GraphQL. A --schema path that does not exist does fail it.
--detach returns as soon as the deployment has started. The job then succeeds without knowing
whether the deployment did, so use it only if something else watches the outcome.
3. Act on the exit code
Section titled “3. Act on the exit code”The job fails whenever gpilot exits with anything but 0. The codes are stable, so a script
can branch on them. These are the ones a deploy step returns; exit codes
has the full list.
| Code | Meaning in a deploy step |
|---|---|
0 | The deployment completed. Warnings, if any, were printed and do not fail the step. |
1 | The deployment failed, was canceled from somewhere else, or the wait timed out. Read the printed diagnostics. |
2 | A flag or value the CLI does not know. |
3 | The API key is missing, revoked, or rejected (HTTP 401). |
4 | The GraphPilot API could not be reached or returned an error, including a permission the key lacks: a valid key without deployment:edit exits 4, not 3. |
6 | This CLI is older than the minimum version. Reinstall to get the latest. |
7 | The outcome is unknown: the progress stream ended before the deployment reported one, and checking it until --timeout ran out did not settle it. The deployment is still open, or every check failed; check with gpilot status. |
--timeout covers the whole wait, watching and checking together. When the stream ends early, the
CLI keeps checking the deployment until it ends or --timeout runs out: a deployment that completed still exits 0, one that failed exits
1.
A 1 after the deployment started does not mean the service is down: a deployment that fails
never goes live, and the previous version keeps serving (see deployments).
4. Add the optional steps
Section titled “4. Add the optional steps”Purge after deploying
Section titled “Purge after deploying” Permission cache:purge
Deploying does not clear the cache (see the cache survives a deployment). Entries stored before the deployment keep the lifetime and partitioning they were stored with. If a change makes those entries wrong, purge after the deploy step:
- name: Purge the cache run: gpilot purge --all --yes --service shop-apiIn GitLab, add the same command as another line under script.
Only do this when the change calls for it. A full purge sends the next request for every operation to your origin. Purging through the API covers narrower options.
Push persisted queries
Section titled “Push persisted queries”If your clients send persisted queries, register the operations from your client build before that build is released:
- name: Push persisted queries run: gpilot pq push ./dist/persisted-documents.json --service shop-apigpilot pq push only adds: entries already registered are left alone, and nothing is removed.
Add --dry-run to check a manifest without registering anything.
Smoke-test the deployment
Section titled “Smoke-test the deployment”A deploy step that exits 0 says the new version is live, not that it answers the way you
expect. Send one request through the service and fail the job if it does not come back clean:
- name: Smoke test run: | body=$(curl -sS --fail-with-body "https://shop-api.k7m2qp.graphpilot.cloud/graphql" \ -H 'content-type: application/json' \ -H "GP-Correlation-ID: smoke-$GITHUB_SHA" \ -d '{"query":"query Smoke { products(first: 1) { id } }"}') echo "$body" | jq -e '.errors == null and (.data.products | length) == 1'--fail-with-body fails the step on an HTTP error status, and the jq -e check fails it on a
GraphQL error, which arrives with 200. The GP-Correlation-ID header labels the request, so on
Airliner (Business) and above gpilot requests --service shop-api --correlation-id "smoke-$GITHUB_SHA" --wait --timeout 60s finds exactly this request in its telemetry. To deploy
to staging first and promote the same commit after an approval, see the recipe promoting from
staging in CI.
Check that it works
Section titled “Check that it works”Push a change to main and watch the job. A green job with exit code 0 means the new version is
live. Confirm it from your machine:
gpilot status --service shop-apiThe newest deployment is completed, lowercase in both the table and -o json. In the portal, the service’s Deployments list shows the
same deployment with its findings. Then send a request through the service and check that it
still answers as before: a gp-cache: HIT on a request that was cached before the deployment is
expected, because the cache survives a
deployment.
Pitfalls
Section titled “Pitfalls”- A pinned or cached CLI stops working the day the minimum version passes it, with exit code
6. Install the latest release in every run. --detachturns every started deployment into a green job, whether it went live or not.gpilot purgewithout--yesis refused in CI.- Persisted queries pushed after the client ships leave the first users with hashes the registry cannot resolve. Push before you release the client.
- A schema deployed after the origin leaves every query that selects a new field uncached
until it lands (
CACHE_SKIPPED_UNRESOLVED_SELECTION). Run the GraphPilot deploy step before the step that releases your origin; see deploy the schema before the origin. - One service is one environment. To rehearse a change, deploy it to a second service first; see staging and rollback.
Related
Section titled “Related”- Staging and rollback
- Promoting from staging in CI
- Migrating to trusted documents, which pushes a manifest from CI
- Deployments
- API keys
- CLI overview