CI/CD Translation Reference
Mapping GitLab CI concepts and patterns to GitHub Actions equivalents. Use alongside gh actions-importer for automated translation, then review and adjust using this reference.
Pipeline Structure​
| GitLab CI | GitHub Actions | Notes |
|---|---|---|
.gitlab-ci.yml | .github/workflows/*.yml | GitHub supports multiple workflow files |
stages: | Jobs with needs: dependencies | No explicit stage ordering; use needs: for DAG |
extends: | Reusable workflows / composite actions | uses: ./.github/workflows/reusable.yml |
include: | Reusable workflows | uses: org/repo/.github/workflows/shared.yml@main |
needs: | needs: | Same concept, same keyword |
resource_group: | concurrency: groups | concurrency: { group: ..., cancel-in-progress: false } |
environment: | environment: | Same concept; GitHub adds protection rules |
Docker & Services​
| GitLab CI | GitHub Actions | Notes |
|---|---|---|
image: | runs-on: + container: | runs-on selects the runner; container sets the Docker image |
services: | services: in container jobs | Requires container: on the job; services don't work on bare runners |
macOS runners (saas-macos-medium-m1) | runs-on: macos-14 | M1-based, available on Enterprise |
pages job | actions/deploy-pages or Cloudflare Pages | See docs hosting section |
Artifacts & Cache​
| GitLab CI | GitHub Actions | Notes |
|---|---|---|
artifacts: | actions/upload-artifact / actions/download-artifact | GitLab passes artifacts between stages automatically; GitHub requires explicit steps |
cache: | actions/cache | Or use built-in caching in setup actions like actions/setup-node with cache: npm |
Variables & Secrets​
| GitLab CI | GitHub Actions | Notes |
|---|---|---|
| CI/CD Variables | Repository/org secrets + variables | Secrets are encrypted; variables are plaintext |
CI_JOB_TOKEN | GITHUB_TOKEN | Auto-generated per workflow run |
CI_REGISTRY / CI_REGISTRY_IMAGE | ghcr.io | GitHub Container Registry |
CI_COMMIT_SHA | github.sha | Context expressions |
CI_COMMIT_REF_NAME | github.ref_name | Context expressions |
CI_PIPELINE_SOURCE | github.event_name | push, pull_request, workflow_dispatch, etc. |
| Protected variables | Environment secrets | Secrets scoped to specific environments |
Triggers & Conditions​
| GitLab CI | GitHub Actions | Notes |
|---|---|---|
rules: / only: / except: | on: triggers + job if: conditions | GitHub triggers are more explicit |
when: manual | workflow_dispatch or environment protection | workflow_dispatch makes the whole workflow manual; for a single job, use env protection |
Laravel Vapor Deploys​
The GitLab setup shipped a separate vapor-base.Dockerfile, pushed it to the GitLab container registry, and referenced it from staging.Dockerfile / production.Dockerfile. That whole pipeline is unnecessary on GitHub: vapor deploy builds the Docker image internally, so the env Dockerfiles can inherit from laravelphp/vapor:phpXX directly and fold in any runtime extras (ghostscript, fontconfig, etc.). Validated on coniglio (commit 9ec2f8b, before coniglio moved to Cloud) and farfalla-integrations. Applies to farfalla, castoro.
GitLab exposes $CI_COMMIT_MESSAGE automatically; GitHub does not. For vapor deploy --message, set GITHUB_COMMIT_MESSAGE: ${{ github.event.head_commit.message }} (or github.event.pull_request.title for PR-triggered staging) in the deploy step's env: block.
Laravel Cloud Deploys​
Laravel Cloud deploys fire from a webhook URL. The GitHub Actions job only POSTs to that URL; the actual build runs server-side on Laravel Cloud.
curl -X POST <hook> returns immediately; the deploy happens in the background on Laravel Cloud. Don't script post-deploy health checks expecting the deploy to be done when curl returns. And don't reach for a beefier runner to speed up the deploy step — the GitHub runner only fires a curl, the image build happens on Cloud. Validated on coniglio, medusa, farfalla-https-guard.
Bare curl -X POST returns 0 on 4xx/5xx, so a rejected hook silently marks the deploy green. Use the same form as all migrated repos:
curl --fail-with-body -sS \
--retry 3 --retry-all-errors \
--connect-timeout 10 --max-time 30 \
-X POST "${{ secrets.LARAVEL_CLOUD_*_DEPLOY_HOOK }}?commit_hash=${{ github.sha }}"
--fail-with-body is the load-bearing flag; the retries/timeouts guard against transient network blips.
github.sha works for push events (merges to dev/main), but on pull_request events it's the ephemeral merge commit GitHub creates against the base branch — a SHA that doesn't exist on the PR's branch and won't resolve on Cloud's dashboard. For PR-triggered deploy jobs (e.g. manual-deploy-staging), use github.event.pull_request.head.sha instead. See Deploy approval pattern for the full job split.
.cloud/config.jsonWhen the Laravel Cloud project's source integration is switched from GitLab to GitHub, Cloud generates a .cloud/config.json file in the repo with the project's organization and application IDs. Commit it — Cloud reads it to identify the project on subsequent deploys. Non-secret. See coniglio and farfalla-https-guard for examples.
Gotchas​
Sentry release tracking and deploy notifications need to be reconfigured for the GitHub integration. Projects moving to Laravel Cloud will transition from Sentry to Nightwatch. Affected repos: farfalla, medusa, farfalla-integrations, coniglio.
GitLab has built-in cross-project pipeline triggers. GitHub Actions uses repository_dispatch events with a PAT or GitHub App token to trigger workflows in other repos. Affected repos: micelio (triggers criceto), volpe/farfalla (Delfino version validation).