◆ docs

Migrating from GitHub Actions

Bring your workflows as they are. rickub reads the same .github/workflows files, runs the same actions, and evaluates the same expressions — this page covers what moves untouched, what you have to move by hand, and what genuinely differs.

A migration is three steps: push the repository, re-create your secrets, push a commit. You do not rewrite your workflow YAML — rickub parses GitHub Actions workflows, not a dialect of them. The same .github/workflows/ directory that runs on GitHub runs here.

That is not a claim we make from a spec: rickub builds and ships itself from workflow files that run on both hosts. The same repository's files are gated on github.server_url so each host runs its half — one set of YAML, two CI systems, no fork of the config.

What runs unchanged

Workflows live where they already live — .github/workflows/*.yml (rickub also reads .rickub/workflows/, should you prefer that). Everything in this list is parsed and honoured with GitHub's semantics:

  • Triggerspush (branches, tags, path filters), pull_request (dispatched when the merge request opens and re-run on every push to its head branch, including a push to a fork's branch), workflow_dispatch with typed inputs, schedule cron entries, and release. types:, branch and path filters all apply.
  • Job graphneeds: ordering, job outputs:, if: conditions including failure() and always(), and continue-on-error.
  • Matricesstrategy.matrix with include/exclude, fail-fast, and max-parallel.
  • Environmentenv: at workflow, job and step level, defaults.run.shell / working-directory, and timeout-minutes.
  • Reusable workflowsjobs.<id>.uses against a workflow_call workflow, with with: inputs and secrets: (including inherit).
  • Service containersservices: and container jobs run against a real Docker daemon inside the job's machine. A postgres:16-alpine service is exactly how rickub's own test jobs get their database.
  • Step summaries — anything a step appends to $GITHUB_STEP_SUMMARY is rendered on the run page.

The actions you already use

  • actions/checkout — pinned to a commit SHA or written as @v4, either is fine, because the version is never consulted: rickub clones your repository onto the job's work disk before the first step runs, so the tree is already there when checkout looks for it. (A checkout of a different repository — with: repository: — does run a real clone, and does not yet carry credentials, so a private cross-repository checkout won't work.)
  • Node-based actionsactions/setup-go, setup-node, setup-python and the rest of the node20 ecosystem run as they do on a GitHub-hosted runner.
  • Composite actions from your own repositoryuses: ./.github/actions/whatever resolves against the checked-out tree.
  • Docker actions and plain docker build — on a docker-capable runner class (see below), docker/login-action, docker build and docker push all work.
Tip

Keep your SHA pins. An action is fetched at the exact ref you pinned, so a supply-chain posture you already enforce travels with the file.

Runner classes

runs-on: resolves against rickub's runner catalog. Every job runs in its own Linux x86-64 microVM, thrown away when the job ends.

Ask for large when a job builds or runs containers. A job that names no runs-on: gets the repository's default class from Settings → Actions.

Heads up

windows-latest and macos-latest are not available, and are deliberately not aliased to Linux: a job asking for one fails immediately with the list of supported labels rather than queueing forever waiting for a runner that is never coming.

timeout-minutes: is honoured per job. A job that declares none gets 60 minutes; the ceiling for a job that declares one is 360 minutes.

Secrets

Secrets do not travel with a git push — this is the one thing you genuinely have to move by hand. Re-create them under the repository's Settings → Secrets and variables (organizations have org-level secrets too, scoped to the repositories an owner has allowed). Non-sensitive values go in Variables and resolve as ${{ vars.NAME }}.

Heads up

Secret names may not start with GITHUB_ or RICKUB_ — those prefixes belong to the values rickub injects into every job, so the form rejects them. If a workflow reads a secret called something like GITHUB_DEPLOY_KEY, rename it (for example to DEPLOY_KEY) in both the settings page and the YAML.

About secrets.GITHUB_TOKEN

Every job still gets a GITHUB_TOKEN — minted per job, scoped to the repository, masked in logs, and resolvable as ${{ secrets.GITHUB_TOKEN }} and ${{ github.token }}. It authorizes rickub's GitHub-compatible REST endpoints, so actions that cut releases, comment on merge requests or read contents work unmodified.

Heads up

It is not a github.com credential. rickub's GITHUB_TOKEN cannot authenticate to github.com or to ghcr.io — it is signed by rickub and github.com has never heard of it. This is the classic migration trap: a job that pushed to GHCR with password: ${{ secrets.GITHUB_TOKEN }} will fail on authentication here. For anything on GitHub's side, bring your own PAT and store it as a rickub secret.

Pushing to GHCR from a rickub job, with a classic PAT that has write:packages:

- name: Log in to GHCR
  uses: docker/login-action@v3
  with:
    registry: ghcr.io
    username: your-github-user
    password: ${{ secrets.GHCR_PAT }}   # a github.com PAT you created

The default token is read-only. A job that writes must say so with a permissions: block (contents: write, issues: write, …) — the same grant you would declare on GitHub, and it travels with the workflow file.

The registry

If your workflow builds images, the shortest migration is to point them at registry.rickub.com — because then there is no login step at all. Every trusted job gets a per-job registry credential injected as RICKUB_REGISTRY_TOKEN, the host as RICKUB_REGISTRY_HOST, and the job's Docker is logged in before the first step runs.

jobs:
  image:
    runs-on: large
    permissions:
      packages: write   # needed the first time a name is pushed
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t "$RICKUB_REGISTRY_HOST/acme/api:${{ github.sha }}" .
      - run: docker push  "$RICKUB_REGISTRY_HOST/acme/api:${{ github.sha }}"

permissions: packages: write is what lets a job claim a new image name — the first push binds that name to this repository. Without it a job may only push names already bound to its repository. See the Container registry page for visibility, bindings and deploy tokens.

Note

Jobs from forks get no registry credential at all, by design — see what's different.

Artifacts and cache

actions/upload-artifact, actions/download-artifact and actions/cache work unchanged, against rickub's own endpoints. Uploaded artifacts are listed and downloadable from the run page. The limits:

  • 2 GiB per artifact, 100 artifacts and 10 GiB per run.
  • 90-day artifact retention. A shorter retention-days: is honoured; a longer one is clamped to 90.
  • 10 GiB of cache per repository, evicted least-recently-used when it fills, and an entry unused for 7 days is swept. Cache entries are scoped per branch, exactly as on GitHub.

What's different

Everything below is a real gap or a real difference. Read this section before you cut over a deploy pipeline.

Gating a deploy without environment:

Since environment approvals are not enforced, gate the deploy on something that is: the presence of a secret. Seeding the secret is the deliberate act that turns deployment on; deleting it turns deployment off — and no workflow edit is involved either way.

- name: Deploy
  env:
    DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
  run: |
    set -uo pipefail
    if [ -z "${DEPLOY_TOKEN}" ]; then
      echo "DEPLOY_TOKEN is not set — continuous deployment is OFF."
      exit 0
    fi
    set -e
    ./deploy.sh
Tip

This is the pattern rickub uses on itself. The absence of the secret is the off switch, and it is visible in one place — the repository's secrets page — instead of buried in a settings screen nobody reads.

Migration checklist

  1. Push the repository — or import it, which brings code, issues and pull requests along.
  2. Leave .github/workflows/ alone. Resist the urge to rewrite; start from what already works.
  3. Re-create your secrets under Settings → Secrets and variables, renaming anything that starts with GITHUB_ or RICKUB_ (in the settings page and in the YAML).
  4. Pick runner classes — add runs-on: large to any job that builds or runs containers; leave the rest on ubuntu-latest.
  5. Re-point image pushes at registry.rickub.com (no login step, add permissions: packages: write) — or keep pushing to GHCR with your own PAT stored as a secret.
  6. Audit anything that talked to github.comGITHUB_TOKEN, github.repository_owner, hardcoded org names.
  7. Replace environment: gates with the secret-presence pattern above.
  8. Push a commit and watch it run under the repository's Actions tab. Fix forward from a real run, not from a guess.
Note

Never gonna give your pipeline up: if a workflow behaves differently here than it did on GitHub, that is a bug worth reporting, not a rewrite you owe us.