OrkStageOrkStage

CI/CD intake

Connect GitHub Actions, Jenkins, GitLab CI/CD or AWS CodePipeline to OrkStage. Publish image versions by digest, validate templates, deploy and wait.

Three endpoints exist for pipelines. Pipelines also use the regular deploy and deployment endpoints.

MethodPathScopeBody and result
PUT/api/v1/applications/:nameapplications:write{ displayName, repository, port, registryKind?, healthPath?, readinessPath? }
POST/api/v1/applications/:name/versionsapplications:write{ version, imageUri, digest: "sha256:…", architectures? }
POST/api/v1/templates/validatetemplates:read{ source, tenantId? }, returns a validation report; with tenantId, also that tenant's limits and feature flags
POST/api/v1/instances/:id/deployinstances:deploy{}, returns 202 and a deployment summary
GET/api/v1/deployments/:id/events?after=instances:readProgress events

Register the application

Every image version belongs to an application in the catalog. Create the application from the pipeline before the first publish. The call is idempotent: the same body sent again changes nothing, so you can run it on every build.

curl -fsS -X PUT "$ORK_API_URL/api/v1/applications/orders-api"   -H "Authorization: Bearer $ORK_TOKEN" -H "Content-Type: application/json"   -d '{ "displayName": "Orders API", "repository": "123456789012.dkr.ecr.us-east-1.amazonaws.com/orders-api", "port": 8080, "healthPath": "/health" }'
  • A name has 2 to 63 characters: lowercase letters, digits and dashes, starting with a letter. Other names get 422 INVALID_NAME.
  • registryKind is ecr (the default), acr, ocir, gar, dockerhub, ghcr or other. healthPath defaults to /health.
  • The response contains "created": true the first time. Every change is audited as CREATE or UPDATE on Application.
  • The catalog is shared by all tenants, so the call needs an organization key with the PlatformAdmin role and the applications:write scope.

Publish an image version

Templates refer to images through the application catalog (image: catalog://orders-api). When OrkStage compiles an instance, each reference resolves to the most recently published version of that application, by digest. A deployment therefore always runs an immutable image.

curl -fsS -X POST "$ORK_API_URL/api/v1/applications/orders-api/versions" \
  -H "Authorization: Bearer $ORK_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "version": "1.14.2",
    "imageUri": "123456789012.dkr.ecr.us-east-1.amazonaws.com/orders-api:1.14.2",
    "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "architectures": ["amd64", "arm64"]
  }'
  • digest must be sha256: followed by 64 lowercase hex characters.
  • architectures defaults to ["amd64"].
  • Versions are immutable. Publishing a version again with the same digest is idempotent (the response has "idempotent": true). With a different digest it fails with 409 VERSION_EXISTS.
  • Every publish is audited as PUBLISH on ApplicationVersion.
  • Publishing needs the same organization key as registration: role PlatformAdmin, scope applications:write.

Validate a template in a pull request

The validation endpoint is stateless. It needs no template id and stores nothing.

jq -Rs '{source: .}' < .ork/template.yaml \
  | curl -fsS -X POST "$ORK_API_URL/api/v1/templates/validate" \
      -H "Authorization: Bearer $ORK_TOKEN" -H "Content-Type: application/json" -d @- \
  | tee report.json | jq -e '.valid'

The report lists every issue with its stage, severity, code, message and path.

Deploy and wait

DEPLOYMENT=$(curl -fsS -X POST "$ORK_API_URL/api/v1/instances/$INSTANCE_ID/deploy" \
  -H "Authorization: Bearer $ORK_TOKEN" -H "Content-Type: application/json" -d '{}' | jq -r '.id')

while :; do
  STATUS=$(curl -fsS "$ORK_API_URL/api/v1/deployments/$DEPLOYMENT" -H "Authorization: Bearer $ORK_TOKEN" | jq -r '.status')
  case "$STATUS" in
    SUCCEEDED) echo "deployed"; break ;;
    FAILED|PARTIALLY_FAILED|CANCELLED) echo "deployment $STATUS"; exit 1 ;;
    AWAITING_APPROVAL) echo "waiting for approval in the console…" ;;
  esac
  sleep 5
done

If you deploy while another operation is running, the API answers 409 INSTANCE_BUSY. Treat it as "try again later", not as a failed build.

The examples below call this loop as scripts/orkstage-deploy.sh <instance id>: save it in your repository with INSTANCE_ID="$1" at the top. Every CI system follows the same three steps:

  1. Store the API key as a secret of the CI system and expose it as ORK_TOKEN, with ORK_API_URL as a plain variable. Never print it or put it in the repository.
  2. After the image is pushed, publish its version by digest (POST /applications/:name/versions).
  3. Deploy the instance and wait (scripts/orkstage-deploy.sh). Production deployments may stop at AWAITING_APPROVAL until someone approves them in the console.

GitHub Actions

.github/workflows/release.yml
name: release
on:
  push:
    tags: ['v*']

jobs:
  publish-and-deploy:
    runs-on: ubuntu-latest
    env:
      ORK_API_URL: https://orkstage.example.com
      ORK_TOKEN: ${{ secrets.ORK_TOKEN }}   # organization key (PlatformAdmin): preset "CI/CD deploy" + applications:write
    steps:
      - uses: actions/checkout@v5
      # … build and push the image, capture its digest in steps.push.outputs.digest …
      - name: Publish the version to OrkStage
        run: |
          curl -fsS -X POST "$ORK_API_URL/api/v1/applications/orders-api/versions" \
            -H "Authorization: Bearer $ORK_TOKEN" -H "Content-Type: application/json" \
            -d "{\"version\":\"${GITHUB_REF_NAME#v}\",\"imageUri\":\"$IMAGE:${GITHUB_REF_NAME}\",\"digest\":\"${{ steps.push.outputs.digest }}\"}"
      - name: Deploy staging
        run: ./scripts/orkstage-deploy.sh "${{ vars.STAGING_INSTANCE_ID }}"

To keep publishing and deploying apart, use two keys: an organization key with the Image publishing preset for the publish step, and a tenant key with the CI/CD deploy preset for the deploy step.

Jenkins

Store the key as a Secret text credential with the id orkstage-token.

Jenkinsfile
pipeline {
  agent any
  environment {
    ORK_API_URL = 'https://orkstage.example.com'
    ORK_TOKEN   = credentials('orkstage-token')
  }
  stages {
    // ... build and push the image, set IMAGE and DIGEST ...
    stage('Publish version') {
      steps {
        sh '''curl -fsS -X POST "$ORK_API_URL/api/v1/applications/orders-api/versions"           -H "Authorization: Bearer $ORK_TOKEN" -H "Content-Type: application/json"           -d "{\"version\":\"$BUILD_NUMBER\",\"imageUri\":\"$IMAGE\",\"digest\":\"$DIGEST\"}"'''
      }
    }
    stage('Deploy staging') {
      steps { sh './scripts/orkstage-deploy.sh "$STAGING_INSTANCE_ID"' }
    }
  }
}

GitLab CI/CD

Add ORK_TOKEN under Settings > CI/CD > Variables, masked and protected.

.gitlab-ci.yml
deploy-staging:
  stage: deploy
  image: alpine:3.22
  variables:
    ORK_API_URL: https://orkstage.example.com
  before_script:
    - apk add --no-cache curl jq
  script:
    # ... the image was pushed in an earlier job; IMAGE and DIGEST come from its dotenv report ...
    - >
      curl -fsS -X POST "$ORK_API_URL/api/v1/applications/orders-api/versions"
      -H "Authorization: Bearer $ORK_TOKEN" -H "Content-Type: application/json"
      -d "{\"version\":\"$CI_COMMIT_SHORT_SHA\",\"imageUri\":\"$IMAGE\",\"digest\":\"$DIGEST\"}"
    - ./scripts/orkstage-deploy.sh "$STAGING_INSTANCE_ID"
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

AWS CodePipeline

Add a CodeBuild action after the build stage. Keep the key in Secrets Manager and let CodeBuild read it; the CodeBuild role needs secretsmanager:GetSecretValue on that secret only.

buildspec-deploy.yml
version: 0.2
env:
  variables:
    ORK_API_URL: https://orkstage.example.com
  secrets-manager:
    ORK_TOKEN: orkstage/ci-token
phases:
  build:
    commands:
      # IMAGE and DIGEST come from the build stage (exported variables or an artifact).
      - >
        curl -fsS -X POST "$ORK_API_URL/api/v1/applications/orders-api/versions"
        -H "Authorization: Bearer $ORK_TOKEN" -H "Content-Type: application/json"
        -d "{\"version\":\"$CODEBUILD_RESOLVED_SOURCE_VERSION\",\"imageUri\":\"$IMAGE\",\"digest\":\"$DIGEST\"}"
      - ./scripts/orkstage-deploy.sh "$STAGING_INSTANCE_ID"

The same shape works in Azure Pipelines, CircleCI, Bitbucket Pipelines or any runner with curl and jq: a secret for the key, one call to publish, one script to deploy and wait.

Notifications instead of polling

Subscribe a webhook to deployment.succeeded, deployment.failed and deployment.awaiting_approval. Your systems then react to the result without a job that keeps polling.

On this page