Migrate from GitHub Actions
.loha.yml is intentionally close to GitHub Actions syntax. Most workflows port over with no structural changes, just a few keyword and action substitutions.
Keyword and action mapping
| GitHub Actions | Loha |
|---|---|
runs-on: ubuntu-latest | resource_class: small (or larger) |
actions/checkout@v4 | loha/git-clone@v1 |
actions/cache@v4 | loha/cache@v1 |
actions/upload-artifact@v4 | artifacts.paths on the job (no step) |
actions/download-artifact@v4 | nothing: a job receives the files of the jobs in its needs: |
docker/build-push-action@v5 | loha/buildah-build@v1 |
aquasecurity/trivy-action | loha/trivy-scan@v1 |
${{ secrets.FOO }} (env injection) | secrets: [FOO] on the step |
${{ github.sha }} | ${COMMIT_SHA} (or ${SHORT_SHA}) |
${{ github.ref_name }} | no equivalent: filter on the trigger instead |
if: on a job | if: on the job, written by hand: the translator removes a job's if: with a warning. Loha's if: compares only branch, event, github.ref and github.event_name with == / != |
strategy.matrix | strategy.matrix |
strategy.max-parallel | strategy.max_parallel |
${{ matrix.node }} | ${{ matrix.node }} (unchanged) or $MATRIX_NODE in a run: script |
${{ ... }} is matrix-only in LohaLoha substitutes three tokens, and only inside with: values: ${COMMIT_SHA}, ${SHORT_SHA}, ${REPO_URL}. The one exception is ${{ matrix.<axis> }}, which works inside a job that declares a matrix. Any other ${{ ... }} copied over from a GitHub workflow is not rejected; it is passed through as literal text. See Context tokens.
Artifacts between jobs
GitHub Actions passes files between jobs with actions/upload-artifact in one job and actions/download-artifact in another. Loha has the same idea as a block on the job, artifacts:, and the translator converts it:
actions/upload-artifactbecomesartifacts.pathson the job, with no step. Thepath:input is split one entry per line. An entry Loha cannot express, such as a negation (!dist/*.map), an absolute path, a..path or one containing a${{ ... }}expression, is dropped with a warning. More than 20 entries are cut to 20 with a warning. Thename:,retention-daysandif-no-files-foundinputs have no equivalent: a declared path that matches no files fails the job.actions/download-artifactbecomes nothing. Every job that lists another job inneeds:receives that job's files at the same paths, and a job needing a matrix job receives every shard's. The translator only warns when the job has noneeds:, because then there is nothing for it to receive: addneeds: [<job>].
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build
run: npm ci && npm run build
- name: Save dist
uses: actions/upload-artifact@v4
with: { name: dist, path: dist/ }
test:
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get dist
uses: actions/download-artifact@v4
with: { name: dist }
- name: Test
run: npm test
Becomes:
jobs:
build:
steps:
- name: Checkout
uses: loha/git-clone@v1
- name: Build
run: npm ci && npm run build
artifacts:
paths: [dist/]
test:
needs: [build]
steps:
- name: Checkout
uses: loha/git-clone@v1
- name: Test
run: npm test
If the upload was meant as a download for people (a release binary, a test report) rather than input for a later job, use loha/upload-artifact instead.
What doesn't port over (yet)
- Matrix builds port, with limits. The translator converts a
strategy.matrixof plain value lists into Loha'sstrategy.matrix(1–5 axes, at most 20 values per axis and 20 combinations, values are strings), mapsmax-paralleltomax_parallel, and leaves${{ matrix.<axis> }}references as they are. It warns and dropsfail-fast,include:andexclude:, which have no equivalent: withoutexclude:the excluded combinations run. A matrix built from an expression (${{ fromJSON(...) }}), an axis of objects, or one over the limits is dropped with a warning, and the job runs once until you rewrite the matrix by hand. A matrix value inruns-on:does not change the runner: every instance runs inubuntu:latest, so varycontainer.imageinstead. - Reusable workflows /
extends:are planned for v0.7.0. - Approval gates / manual triggers between jobs are planned for v0.7.0.
- OIDC token publishing to AWS / GCP / Azure is planned for v0.8.0.
If your workflow depends on any of these, the roadmap tells you when to expect parity.
Worked example
A common Node.js workflow:
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm test
- uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
Equivalent .loha.yml:
version: '1'
on:
push:
branches: [main]
jobs:
build:
resource_class: small
steps:
- name: Checkout
uses: loha/git-clone@v1
- name: Install and test
image: node:20
run: |
npm ci
npm test
- name: Build and push image
uses: loha/buildah-build@v1
with:
IMAGE: registry.example.com/acme/web:${COMMIT_SHA}
Two differences worth noting. Every step needs a name:, which is how steps are identified in the build log. And there's no setup-node equivalent: pick the runtime with the step's image: instead of installing it at run time.