Deploying from CI/CD
Deploy on every push from GitHub Actions or GitLab CI/CD, with a narrow credential and a job that fails when the deployment does.
A pipeline does four things: install the CLI, sign in with a credential kept in the CI system, create the application the first time or update it every time after, and wait for the deployment so the job fails when it fails. Two ready-made templates do exactly that. This page shows them in full, explains each part, and covers what goes wrong.
1. Mint a credential for the pipeline
Do not reuse the credential on your own machine. Mint one for CI alone, with only what the pipeline needs, so it can be revoked without signing you out and cannot do more than deploy if it leaks.
export ISOGRID_CONFIG_HOME="$(mktemp -d)"
isogrid --api https://api.isogrid.skyvault.pro login \
--scope application:create,application:write-any \
--name github-ci
The browser opens on the approval page. Check the organization shown there: the credential is pinned to it and will be refused anywhere else. Approve, then copy the token out of the temporary configuration and delete it:
jq -r '.contexts[].token' "$ISOGRID_CONFIG_HOME/config.json"
rm -rf "$ISOGRID_CONFIG_HOME"
Using a temporary ISOGRID_CONFIG_HOME keeps your own sign-in untouched. Without
it, the new credential would replace the one in ~/.isogrid/config.json for
that address, and you would need isogrid login --force to get yours back.
Which scopes
| Scope | What the pipeline can do with it |
|---|---|
application:create |
Create the application on the first run. |
application:write-any |
Change, build and deploy every application in the organization. |
application:deploy-any |
Build, deploy, start and stop every application, but not change their settings. |
The templates create the application if it does not exist, so they need
application:create and application:write-any. Once it exists, you can mint
a narrower one without application:create. application:deploy-any is
enough only if the pipeline never passes settings: no --env-file and no
change of branch.
A scope never adds to what you hold. It is intersected with your own role and groups on every request, so a scope you do not hold is silently dropped, and demoting you narrows the pipeline's credential too. Some things a credential can never do, whatever it asks for: billing, appointing administrators, and minting or revoking credentials. See Roles, permission groups and access.
A credential lasts 90 days unless you chose a shorter life on the approval page. Put a reminder in your calendar to replace it before then.
2. Tell the CI system
| Setting | GitHub Actions | GitLab CI/CD |
|---|---|---|
ISOGRID_TOKEN |
Repository secret | CI/CD variable, Masked |
ISOGRID_API_URL |
Repository variable, e.g. https://api.isogrid.skyvault.pro |
CI/CD variable |
ISOGRID_APP |
Optional variable. Defaults to the repository's name. | Optional variable. Defaults to the project's name. |
ISOGRID_CLUSTER |
Optional. Region slug; needed when more than one region is open to you. | The same |
ISOGRID_TIER |
Optional. Instance type; the region's smallest by default. | The same |
ISOGRID_NETWORK |
Optional. Network slug; your organization's own by default. | The same |
ISOGRID_ENV_FILE |
Optional. A dotenv file in the repository to apply on every deployment. | The same |
On GitHub these are under Settings → Secrets and variables → Actions. On
GitLab they are under Settings → CI/CD → Variables; mark ISOGRID_TOKEN
Protected too if only protected branches deploy.
The CLI reads ISOGRID_TOKEN from the environment and never writes it to
disk, so a pipeline that caches the home directory does not carry the token
into later jobs.
3. Add the pipeline
The CLI writes the file for you:
isogrid ci init github --app shop --cluster eu-west # .github/workflows/isogrid-deploy.yml
isogrid ci init gitlab --app shop --cluster eu-west # .gitlab-ci.yml
isogrid ci init github --output - # print it instead
Values you pass (--app, --cluster, --tier, --network, --env-file) are
written into the file. Anything you leave out is read from the CI variables
above. An existing file is not overwritten without --force. After writing, it
prints the variables still to set.
You can also download the templates without the CLI:
curl -fsSL https://api.isogrid.skyvault.pro/api/v1/cli/ci-templates/github -o .github/workflows/isogrid-deploy.yml
curl -fsSL https://api.isogrid.skyvault.pro/api/v1/cli/ci-templates/gitlab -o .gitlab-ci.yml
Before the first run
The repository must be connected to your organization and open to the person
whose credential the pipeline uses. isogrid repos list shows it once it is;
the YOU column must include create-app and deploy. See
Git repositories.
The GitHub Actions template
# Deploy to ISOGrid from GitHub Actions.
#
# Save as .github/workflows/isogrid-deploy.yml (or generate it with
# `isogrid ci init github`). On every push to main, and whenever you run it by
# hand, it installs the ISOGrid CLI and either updates the application and
# deploys the pushed commit, or - the first time - creates the application.
#
# Set these in the repository (Settings -> Secrets and variables -> Actions):
#
# Secrets
# ISOGRID_TOKEN A CLI credential. Mint a narrow one for CI: run
# `isogrid login --scope application:create,application:write-any --name github-ci`
# on your machine, then copy the token from
# ~/.isogrid/config.json. Never commit it.
#
# Variables
# ISOGRID_API_URL Your platform's API, e.g. https://api.isogrid.skyvault.pro
# ISOGRID_APP Application name (default: this repository's name)
# ISOGRID_CLUSTER Region slug (optional when only one region is open to you)
# ISOGRID_TIER Instance type name (optional: the region's smallest)
# ISOGRID_NETWORK Network slug (optional: your organization's network)
# ISOGRID_ENV_FILE A dotenv file in the repository to apply (optional)
#
# The repository must already be connected to the organization through the
# ISOGrid GitHub app (`isogrid repos list` shows it once it is).
name: Deploy to ISOGrid
on:
push:
branches: [main]
workflow_dispatch:
# One deployment of a branch at a time; a newer push waits rather than racing.
concurrency:
group: isogrid-deploy-${{ github.ref }}
cancel-in-progress: false
env:
ISOGRID_API_URL: ${{ vars.ISOGRID_API_URL }}
ISOGRID_TOKEN: ${{ secrets.ISOGRID_TOKEN }}
ISOGRID_APP: ${{ vars.ISOGRID_APP || github.event.repository.name }}
ISOGRID_CLUSTER: ${{ vars.ISOGRID_CLUSTER }}
ISOGRID_TIER: ${{ vars.ISOGRID_TIER }}
ISOGRID_NETWORK: ${{ vars.ISOGRID_NETWORK }}
ISOGRID_ENV_FILE: ${{ vars.ISOGRID_ENV_FILE }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# Only needed for ISOGRID_ENV_FILE: the platform clones the repository
# itself to build it.
- uses: actions/checkout@v4
- name: Install the ISOGrid CLI
run: |
curl -fsSL "$ISOGRID_API_URL/api/v1/cli/install.sh" | ISOGRID_INSTALL_DIR="$HOME/.local/bin" sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Deploy
run: |
set -eu
isogrid version
ENV_ARGS=""
if [ -n "$ISOGRID_ENV_FILE" ]; then
ENV_ARGS="--env-file $ISOGRID_ENV_FILE"
fi
# --exact: an application called "api" must not be taken for "api-gateway".
if isogrid apps get "$ISOGRID_APP" --exact --json > /dev/null 2>&1; then
# Builds this exact commit and waits; the step fails if the build or
# the deployment does.
isogrid apps update "$ISOGRID_APP" \
--branch "${{ github.ref_name }}" \
$ENV_ARGS \
--deploy --ref "${{ github.sha }}" --wait
else
isogrid apps create \
--name "$ISOGRID_APP" \
--provider github \
--url "${{ github.server_url }}/${{ github.repository }}" \
--branch "${{ github.ref_name }}" \
--cluster "$ISOGRID_CLUSTER" \
--tier "$ISOGRID_TIER" \
--network "$ISOGRID_NETWORK" \
$ENV_ARGS \
--wait
fi
The GitLab CI/CD template
# Deploy to ISOGrid from GitLab CI/CD.
#
# Save as .gitlab-ci.yml at the root of the project (or generate it with
# `isogrid ci init gitlab`), or merge the job into the one you have. On every
# push to the default branch, and when run by hand, it installs the ISOGrid CLI
# and either updates the application and deploys the pushed commit, or - the
# first time - creates the application.
#
# Set these under Settings -> CI/CD -> Variables:
#
# ISOGRID_TOKEN A CLI credential; mark it Masked (and Protected if only
# protected branches deploy). Mint a narrow one for CI:
# `isogrid login --scope application:create,application:write-any --name gitlab-ci`
# on your machine, then copy the token from
# ~/.isogrid/config.json. Never commit it.
# ISOGRID_API_URL Your platform's API, e.g. https://api.isogrid.skyvault.pro
#
# and optionally override the defaults below the same way (project variables
# take precedence over the values written in this file):
#
# ISOGRID_APP Application name (default: the project's name)
# ISOGRID_CLUSTER Region slug (optional when only one region is open to you)
# ISOGRID_TIER Instance type name (optional: the region's smallest)
# ISOGRID_NETWORK Network slug (optional: your organization's network)
# ISOGRID_ENV_FILE A dotenv file in the repository to apply (optional)
#
# The project must already be connected to the organization through the
# ISOGrid GitLab integration (`isogrid repos list` shows it once it is).
stages:
- deploy
variables:
ISOGRID_APP: "$CI_PROJECT_NAME"
ISOGRID_CLUSTER: ""
ISOGRID_TIER: ""
ISOGRID_NETWORK: ""
ISOGRID_ENV_FILE: ""
isogrid-deploy:
stage: deploy
image: alpine:3.20
# One deployment of a branch at a time.
resource_group: isogrid-$CI_COMMIT_REF_SLUG
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == "web"
before_script:
- apk add --no-cache curl
- curl -fsSL "$ISOGRID_API_URL/api/v1/cli/install.sh" | ISOGRID_INSTALL_DIR=/usr/local/bin sh
- isogrid version
script:
- |
set -eu
ENV_ARGS=""
if [ -n "$ISOGRID_ENV_FILE" ]; then
ENV_ARGS="--env-file $ISOGRID_ENV_FILE"
fi
# --exact: an application called "api" must not be taken for "api-gateway".
if isogrid apps get "$ISOGRID_APP" --exact --json > /dev/null 2>&1; then
# Builds this exact commit and waits; the job fails if the build or
# the deployment does.
isogrid apps update "$ISOGRID_APP" \
--branch "$CI_COMMIT_REF_NAME" \
$ENV_ARGS \
--deploy --ref "$CI_COMMIT_SHA" --wait
else
isogrid apps create \
--name "$ISOGRID_APP" \
--provider gitlab \
--url "$CI_PROJECT_URL" \
--branch "$CI_COMMIT_REF_NAME" \
--cluster "$ISOGRID_CLUSTER" \
--tier "$ISOGRID_TIER" \
--network "$ISOGRID_NETWORK" \
$ENV_ARGS \
--wait
fi
--provider gitlab is given explicitly, so a self-hosted GitLab whose address
does not contain "gitlab" works as well, and $CI_PROJECT_URL carries nested
groups as they are.
What the pipeline does, step by step
Installs the CLI from your platform, verifying its checksum. The CLI and the platform are always the same generation, because one serves the other.
Asks whether the application exists with apps get --exact. Without
--exact, a new application called api would be taken for an existing
api-gateway, and the pipeline would deploy the wrong thing.
Updates it when it exists. --branch keeps the application following the
branch that was pushed. --deploy --ref <commit> builds that exact commit, not
whatever the branch points to by the time the build starts, so two quick pushes
cannot deploy each other's code. --env-file merges the file into the
application's environment; keys you removed from the file stay on the
application unless you add --replace-env.
Creates it the first time, from the repository that triggered the run.
Empty --cluster, --tier and --network mean the defaults.
Waits. --wait returns only when the build and the deployment have
finished, and exits 1 when either failed, including a deployment that was
rolled back to the previous version. That exit code is what fails the job.
Runs one deployment at a time per branch: concurrency on GitHub,
resource_group on GitLab. A second push waits for the first rather than
racing it.
Writing your own
The core of both templates is five lines of shell, so any CI system that can run a shell can use it:
curl -fsSL "$ISOGRID_API_URL/api/v1/cli/install.sh" | sh
if isogrid apps get "$APP" --exact --json > /dev/null 2>&1; then
isogrid apps update "$APP" --branch "$BRANCH" --deploy --ref "$COMMIT_SHA" --wait
else
isogrid apps create --name "$APP" --provider github --url "$REPO_URL" --branch "$BRANCH" --wait
fi
Variant: building the image in CI
If your pipeline already builds a container image, let it push the image to your organization's registry and have the platform deploy it, rather than building a second time.
Create the application once, from the image:
isogrid apps create --name worker --provider registry --url acme/worker:latest --cluster eu-west
Then each run pushes a new tag and points the application at it:
docker build -t "$REGISTRY/$NAMESPACE/worker:$COMMIT_SHA" .
docker push "$REGISTRY/$NAMESPACE/worker:$COMMIT_SHA"
isogrid apps update worker --tag "$COMMIT_SHA" --deploy --wait
The pipeline needs registry credentials beside ISOGRID_TOKEN: a robot account
from the console's Images page, stored as ISOGRID_REGISTRY_USER and a
secret or masked ISOGRID_REGISTRY_PASSWORD, with ISOGRID_REGISTRY (the
registry host) and ISOGRID_NAMESPACE (your namespace). Both templates carry
this job, commented out, at the bottom of the file. Image names must be
lowercase, so set ISOGRID_APP explicitly when the repository name is not.
When it goes wrong
Make isogrid whoami the first command of the job while you set things up. It
prints who the credential belongs to, the organization it is pinned to, and the
permissions it will actually be allowed.
"not signed in". ISOGRID_TOKEN is empty in the job. On GitHub, a secret
is not passed to workflows triggered from forks. On GitLab, a Protected
variable is not passed to unprotected branches.
The credential expired, or was revoked. Mint a new one and replace the secret. Nothing else needs to change.
A permission error on apps create. The credential lacks
application:create, or you do not hold it yourself. whoami shows which.
A permission error on apps update. The credential cannot change or deploy
that application. Either it lacks application:write-any, or it only has
application:deploy-any and the command passed a setting (--env-file, or a
different --branch).
"is not a repository you have been given access to", or "You do not have
'repo:deploy' on acme/api". The token acts as you, and you cannot build from
that repository. Someone else connected the account it comes through and has
not opened the repository to you. Ask them, or an administrator if they have
left, to run isogrid repos grant acme/api --member you@example.com --preset repo-deployer. If the repository is open to administrators and you are
one, mint the credential with github:manage in its scopes too: that is the
permission administrator access to repositories rests on. See
Git repositories.
"pinned to" another organization. The credential was approved while the
browser was looking at a different organization, or ISOGRID_ORGANIZATION or
--organization names one it is not pinned to. Switch organizations in the
console and mint the credential again.
"choose a region with --cluster". More than one region is open to you. Set
ISOGRID_CLUSTER.
The job timed out but the deployment finished. --wait gave up after
--timeout (20 minutes by default); the deployment carried on. Raise it with
--timeout 40m for slow builds.
The job failed and the site is still up. The new version never became
healthy and the previous one was put back. That is a failure worth failing the
job for. Read isogrid apps logs <app>.
Exit codes
| Code | Meaning |
|---|---|
0 |
Deployed, or nothing to change. |
1 |
Something failed: an API or permission error, a failed build, a failed or rolled-back deployment, a timeout. |
2 |
The command was called wrongly: an unknown flag, a missing value, or a delete without --yes. Almost always a mistake in the pipeline file. |