CI/CD Pipelines

GitBlixt runs continuous integration pipelines for your repositories. It understands both GitLab CI (.gitlab-ci.yml) and GitHub Actions (.github/workflows/*.yml) configuration, so most repositories work without changing their existing config. Jobs execute in isolated Docker containers, and you watch logs stream live in the web UI.

How pipelines trigger

A pipeline is created automatically whenever you push to a repository (over SSH or HTTP). You can also start one manually from the Pipelines tab: pick a branch and click Run pipeline. Pipelines are scoped to the branch (ref) and commit (SHA) they ran against.

Turning off Run CI/CD in the repository's General settings (builds_access_level in the API) stops pipelines from pushes, merges, merge requests and the Run pipeline button, stops re-runs and CI job retries (a deploy job can still be retried), and refuses merge when pipeline succeeds. A push to the branch of an environment that deploys on every push, or of any auto-deploy environment while CI/CD is off, gets a pipeline with no jobs that only runs the deploy.

The pipeline list lives at /<namespace>/<repo>/-/pipelines. Each pipeline links to a detail page at /-/pipelines/<pipeline_id>, and each job to /-/pipelines/<pipeline_id>/jobs/<job_id>.

Configuration

When a pipeline runs, GitBlixt looks for config in this order — the first match wins:

  1. GitHub Actions workflows in .github/workflows/ (files ending in .yml or .yaml)
  2. GitLab CI in .gitlab-ci.yml at the repository root

Only GitHub Actions workflows whose on: trigger matches a push to the pipeline's branch are run. If neither config exists, or none of its jobs run on the branch, the pipeline is skipped: it never counts as passing CI, though merge when pipeline succeeds still fires on it. On a branch an environment auto-deploys, it runs the deploy instead and takes the deploy's result.

GitLab CI example

Jobs are grouped into stages, which run in order. If you don't declare stages:, the defaults are build, test, deploy. A job's script runs inside the container named by its image.

stages:
  - test

unit:
  stage: test
  image: elixir:1.17
  script:
    - mix deps.get
    - mix test

Supported .gitlab-ci.yml keywords

Jobs take stage, image, script, before_script, after_script, variables, services, parallel and environment. needs and artifacts are accepted but not used yet. Which jobs a pipeline creates follows GitLab:

  • workflow: rules decide whether the pipeline gets any jobs. The matched rule's variables reach every job.
  • rules match in order and the first match decides; a job no rule matches isn't created. A rule takes if, exists, changes and variables, which the job gets. exists globs take ** (any directories), *, ? and $VAR; {a,b}, [abc] and \ escapes aren't supported. changes always matches, as GitBlixt doesn't compare against a previous commit, and a rule's allow_failure is ignored.
  • only and except take branch names, /regexes/, keywords such as branches, pushes and merge_requests, or the refs/variables/changes map. In the map form only needs every key to match, while any one key of except excludes the job, so an except with changes always does. They can't be combined with rules. Pipelines started with the Run pipeline button or the API have CI_PIPELINE_SOURCE push, so web and api never match.
  • when: on_success and when: always create the job (always behaves like on_success). never doesn't, and neither do manual, delayed and on_failure, which aren't supported yet.

An if expression sees the predefined and YAML variables and the project variables the job gets, matched to its environment: as described under CI/CD variables. It supports $VAR, quoted strings, null, ==, !=, =~ and !~ with a /regex/ (flags i, m, s), &&, || and parentheses. An expression or keyword GitBlixt can't evaluate fails the pipeline, and the reason, naming the job and the clause, goes to the server log. Rules are checked again when each job starts: a job they no longer create, say because a variable changed, fails with the reason in its log.

A push makes one pipeline, with CI_PIPELINE_SOURCE push. When its rules create no jobs and the branch has an open, ready merge request, it runs as that merge request's pipeline instead, with merge_request_event and the CI_MERGE_REQUEST_* variables, so configs that select jobs for merge request pipelines work.

GitHub Actions example

GitHub Actions jobs are translated into the same job/stage model. Stages are derived from the needs: dependency graph, and common setup actions are mapped to equivalent commands. Expressions like ${{ ... }} are evaluated where supported.

A job's runs-on label picks its image. Ubuntu labels, hosted ones like blacksmith-4vcpu-ubuntu-2204 included, run on that Ubuntu release, self-hosted on the latest one, and any other label is taken as the image name. Of a list of labels, like [self-hosted, linux, x64], the first Ubuntu or self-hosted one wins.

name: CI
on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: echo "Building on ${{ github.ref }}"
      - run: ./run-tests.sh

Stages and jobs

Each stage's jobs are dispatched when the previous stage completes; jobs in the earliest stage that has jobs start as soon as the pipeline begins. Every job is an entry in the pipeline detail view showing its stage, status, duration, and exit code.

Statuses

Pipelines and jobs share the same set of statuses:

Status Meaning
pending Created, waiting to start
running Jobs are executing
success All jobs finished successfully
failed At least one job failed
canceled Stopped before completion
skipped Pipelines only: no job ran for the commit

Job logs

Open any job to see its log. Output streams live over the websocket while the job runs and is persisted so you can review it afterward. Use the Copy log button to grab the full output.

Manual actions

From a pipeline or job page you can:

  • Cancel — stop a pending or running pipeline; any running job containers are killed
  • Re-run — re-trigger a finished (success/failed/canceled) pipeline against the same branch and commit
  • Clear cache & re-run — wipe the repository's CI build cache, then re-trigger

CI/CD variables

Variables are defined per repository under Settings. They are injected as environment variables into CI job containers. Each variable has:

Field Description
key Name (letters, digits, underscores; must start with a letter or underscore)
value The value, encrypted at rest
masked Hides the value in logs and the UI (value must be at least 8 characters)
protected Restricts exposure to protected branches/tags
environment_scope Which environment the variable applies to (defaults to *, all). A * inside a scope, as in review/*, matches any characters.

A scoped variable reaches a job when its scope matches an environment that deploys from the pipeline's branch, preview environments included. On a protected branch, a job that names an environment with environment: gets that environment's variables instead, as in GitLab; on other branches the name is ignored, so a job can't widen what it gets. When several scopes set a key, an exact name beats a wildcard, which beats *. Protected variables remain the security boundary: only protected keeps a value off unprotected branches. Jobs that name an environment also get CI_ENVIRONMENT_NAME and CI_ENVIRONMENT_SLUG. A GitHub Actions environment: holding an expression GitBlixt can't evaluate names none.

CI/CD variables are for CI pipeline jobs only. They are not passed to deployed applications — deployed apps get their own environment variables, configured separately on each app environment.

Artifacts

Jobs can produce artifacts — files saved after a job runs. Each artifact records a file_type (defaults to archive), file_name, file_path, size, and an optional expire_at timestamp after which it can be cleaned up.

External runners (GitLab Runner API)

In addition to the built-in Docker executor, GitBlixt exposes a GitLab Runner–compatible API so external runners can pull and execute jobs. Authenticate with a personal access token (PRIVATE-TOKEN or Authorization: Bearer header). The runner is handed the next pending job belonging to a repository owned by the authenticated user.

Endpoint Purpose
POST /api/v4/jobs/request Fetch the next pending job (204 when none are available)
PUT /api/v4/jobs/:id Update a job's status, exit code, and finish time
PATCH /api/v4/jobs/:id/trace Append raw text to the job's log

When a runner reports a job as success, failed, or canceled, GitBlixt recalculates the overall pipeline status automatically.