Skip to main content

GitLab to GitHub Migration

We are migrating all source code repositories from GitLab (publicala group) to GitHub (publicala org, Enterprise plan). This document covers motivation, scope, approach, and current state.

Why​

We think this is what we would like to be using in 6 months.

  • GitHub is the standard for open-source collaboration and AI-assisted development tooling
  • Better integration with our tooling like Laravel Cloud, Claude Code, Codex and others
  • Accelerate adoption of agentic tooling

Scope​

  • Code and history only. Migration uses git clone --mirror / git push --mirror to transfer all branches, tags, and commit history. GitLab remains read-only as an archive for historical merge request references.
  • Not migrated: GitLab issues, merge requests, wiki pages, snippets, or CI/CD variables (secrets are re-created manually in GitHub).
  • Archived repos excluded. The 76 archived repositories on GitLab are not migrated. GitLab remains as the reference for those.

Current State​

MetricCount
Total GitLab repos110
Already archived76
Active repos34
Active repos with CI18
Active repos without CI16
Already on GitHub3

Already on GitHub​

RepoGitHub Location
publicalanewwebsitepublicala/publicalanewwebsite
claude-md-guidepublicala/claude-md-guide
publicanowpublicala/publicanow

GitHub Org​

  • Organization: publicala (Enterprise Cloud, via GitHub for Startups grant)
  • Seats: 6 active members, 13 pending invitations. Seats auto-expand on Enterprise Cloud (usage-based billing); no manual increase needed
  • Existing repos: 11
  • Team: See Team Members checklist for invitation status. Invite to the Organization, not the Enterprise; Enterprise seats follow automatically.

Access Management​

Add a new team member: invite to the org, then add to Core Team:

gh api /orgs/publicala/teams/core-team/memberships/USERNAME -X PUT -f role=member

Grant Core Team access to a new repo:

gh api /orgs/publicala/teams/core-team/repos/publicala/REPO_NAME -X PUT -f permission=push
How access works (base role, Core Team, outside collaborators)

GitHub does not have GitLab's group-level role assignment (where adding someone to a group automatically grants access to all projects). Instead, access is managed through org base permissions and GitHub Teams.

  • Base role: Read (all org members can clone/pull all internal repos)
  • Core Team: GitHub Team (core-team) with Write permission. All org members who need push access should be in this team.
  • Outside Collaborators: Third parties (contractors, partners) are added as Outside Collaborators on specific repos, not as org members. This prevents them from inheriting the base role on all repos.

GitHub requires explicitly adding teams to each repo. When migrating a repo, always add the Core Team as part of the post-migration checklist.

GitLab Group Structure​

GitLab uses nested subgroups. GitHub is flat (no nested orgs).

publicala/                              # ~90 flat repos
publicala/fenice/dep/dependencies/ # 1 repo (volpe-bundle)
publicala/fenice/dep/env/ # 12 repos (env configs, fastlane certs)
publicala/fenice/dev/ # 10 repos (app variants, monorepo)
publicala/fenice-legacy-archived/ # 3 repos (all archived)
publicala_exercises/ # 1 repo (search-inside-a-book)

Namespace Flattening​

Most repos live directly under publicala/ and map 1:1 to GitHub. The nested fenice/ subgroup repos flatten with a fenice- prefix (e.g., fenice/dev/monorepo becomes fenice-monorepo). See the Repository Inventory for per-repo mappings.

Fenice: flatten vs. restructure

The fenice/ subgroup contains ~23 repos across three levels of nesting. Migrating them 1:1 with a fenice- prefix produces ~23 flat repos in the GitHub org (12 of which are per-tenant env configs).

The situation: GitLab subgroups provided implicit grouping and scoped permissions. GitHub has neither; naming conventions and Teams are the only tools.

Options:

  1. Flatten as-is with fenice- prefix (table above). Simple, no code changes, but noisy.
  2. Consolidate env/config repos into a single fenice-config repo. Reduces 12+ repos to 1.
  3. Fold everything into the monorepo. Env configs, fastlane certs, volpe-bundle, tenant variants. Maximum consolidation.
  4. Hybrid: consolidate configs (option 2) now, evaluate monorepo absorption later.

To be discussed with JP before Fenice migration (Tier 5/6).

Migration Approach​

Repos transfer via git clone --mirror / git push --mirror (see Migration Guide). CI/CD translates via gh actions-importer (see CI/CD Translation Reference). Secrets are re-created manually as GitHub org/repo secrets. All repos standardize on main as default branch.

Priority Tiers​

Migration proceeds in tiers, starting with low-risk repos to validate the process before tackling critical infrastructure.

Tier 1: Pilot (validate process)​

RepoRationale
docsSimple CI, already on main, Cloudflare Pages target
picchioNo CI, trivial
claude-md-guideNo CI. Done (transferred to org)

Tier 2: Cloudflare Workers (simple CI)​

RepoRationale
gitlab-to-slack-proxySimple Wrangler deploy
vitoSmall Workers project
micelioWorkers with staging/production
publicalanewwebsiteAlready on GitHub, needs CI migration

Tier 3: Laravel Cloud + tools (moderate CI)​

RepoRationale
farfalla-https-guardLaravel Cloud deploy hooks
mentatLaravel Cloud, complex CI
formicaTests only, no deploy
delfinonpm registry migration (GitLab Packages to GitHub Packages)

Tier 4: Core services (Vapor, complex CI, DinD)​

RepoRationale
coniglioHybrid Cloud/Vapor
castoroLFS, Vapor, custom Docker image
medusaVapor, Sentry notifications
farfalla-integrationsVapor, DinD
farfallaCore monolith, 18 CI jobs, Vapor DinD

Tier 5: Complex/special​

RepoRationale
volpeLFS + Cloudflare Pages + npm publish + cross-repo validation
feniceApp Store/Play Store, macOS runners, Fastlane, 26 tags, subgroups
cricetoPlaywright, GitLab Pages reports

Tier 6: Remaining active repos​

zoo, botesito, all fenice subgroup repos, utility repos (epub2-upgrader, pdf-extractor, pdf-processing-benchmark-tool, etc.), playground, prototipi, and others with no CI.

Third-Party Runners​

GitHub-hosted runners are insufficient for two use cases: Docker-in-Docker (Vapor deploys) and macOS/iOS/Android builds (Fenice). We use Depot as a third-party runner provider.

Dashboard: https://depot.dev/orgs/20vvk4c75n/github-actions

What Depot Replaces​

GitLab Runner TagDepot EquivalentProjects
saas-linux-medium-amd64 (DinD)depot-ubuntu-24.04-4farfalla, medusa, coniglio, castoro, farfalla-integrations
saas-linux-large-amd64depot-ubuntu-24.04-8 or largerfarfalla, medusa, coniglio, criceto
saas-macos-medium-m1depot-macos-14 / depot-macos-15fenice
Depot details (runner types, capabilities, constraints)

Why Depot:

  • Full VM-based Linux runners (Docker runs natively, no DinD workarounds)
  • macOS Apple Silicon runners (M2) for iOS/Android builds
  • Per-second billing with no minimum (rate expressed per-minute)
  • Up to 64 vCPU Linux runners

Runner Types:

OSHardwareSizesUse Case
Linux x64AMD EPYC Genoa2-64 vCPUTests, Docker builds, Vapor deploys
Linux ARM64Graviton42-64 vCPUARM-specific builds
macOSApple M28 vCPU / 24 GBFenice iOS/Android builds, Detox E2E

Constraints:

  • Only native GitHub Actions functionality is used (no Depot CLI or proprietary actions)
  • Workflows must remain portable; switching runner providers should only require changing runs-on labels
  • macOS runners do not support Docker (standard Apple Silicon limitation across all providers)

Post-Migration Checklist​

See Phase 3: Cross-Cutting Updates in the Migration Guide.

Tools​

ToolVersionPurpose
ghlatestGitHub CLI
gh actions-importerv1.3.6Translate .gitlab-ci.yml to GitHub Actions
glablatestGitLab CLI (for archive operations)
X

Graph View