> For the complete documentation index, see llms.txt.
Skip to main content

Check out Port for yourself ➜ 

Migrate repositories from GitHub to GitLab using Port self-service actions

Implement with AI

Send this guide to your coding agent.

Prerequisite: Install Port MCP

Open plan mode. Implement this Port guide in my org via MCP:

https://docs.port.io/guides/all/migrate-project-from-github-to-gitlab

Goal: get the guide's core flow working end-to-end in my org; adapting it to fit my existing setup takes priority over matching the guide 1:1.

Plan:
1. Confirm MCP is connected, in the right org, with sufficient permissions.
2. Diff the guide's data model (blueprints, properties, relations, actions, agents, automations, integrations, secrets) against mine.
3. Propose adaptations for gaps, reusing existing blueprints/relations over guide-named duplicates.
4. Flag what needs a UI click, credential, or secret from me, testing MCP capability empirically before ruling anything out.
5. Stop on any blocker and give me options. Approving this plan authorizes the writes it lists; pause only for writes beyond what's listed.

Build:
- Extend blueprint schema additively when upserting; don't remove or overwrite existing properties, and treat type conflicts as a blocker, not an auto-fix.
- List any mock data in the plan, minimal and labeled mock; once approved, seed it without re-asking, and tell me what you seeded.
- For anything the guide writes downstream (e.g. a webhook target), use a real entity, not a mock.
- For pages/widgets, use the real page identifier from the app URL, not a guessed slug.
- When you hit a UI step confirmed (not assumed) unsupported via MCP, pause, give exact clicks, then resume via MCP.
- Validate and give links after each meaningful step (only a tool-returned URL, no guessed paths); don't proceed if the last run wasn't a success.

Done:
- Confirm the guide's expected output exists and runs in Port.
- Summarize adaptations, seeded data, what was mocked or skipped, remaining UI steps, and how to verify.

This guide demonstrates how to migrate repositories from GitHub to GitLab using Port's self-service actions together with a GitHub Actions workflow.

We will use Port to model your repositories, trigger a standardised migration flow, and keep visibility of progress using logs and entity tracking.

Once implemented you will be able to:

  • Set up GitHub integration so Port discovers repositories and metadata.
  • Model repositories with blueprints to track your GitHub repositories.
  • Trigger a repeatable migration flow from Port that runs a GitHub Actions workflow.
  • Automatically create GitLab projects and push complete repository history.

Prerequisites

You should have the following in place for this migration:

  • A Port account with the onboarding process completed.
  • A GitHub organization containing the repositories you want to migrate.
  • A GitLab account (self-hosted or cloud) with permissions to create projects.
  • A GitLab namespace or group to serve as the migration destination.

Set up data model

To represent your GitHub repositories in Port, we need to create a blueprint, set up the GitHub integration, and configure the data source. Skip to the section below if you already have an existing GitHub integration.

Create the GitHub Repository blueprint

  1. Go to the data model page in Port.

  2. Click on + Blueprint.

  3. Click on the {...} Edit JSON button in the top right corner.

  4. Copy and paste the following JSON schema:

    GitHub Repository blueprint (click to expand)
    {
    "identifier": "githubRepository",
    "title": "GitHub Repository",
    "icon": "Github",
    "schema": {
    "properties": {
    "url": {
    "title": "URL",
    "format": "url",
    "type": "string",
    "icon": "Link"
    },
    "readme": {
    "title": "README",
    "type": "string",
    "format": "markdown",
    "icon": "Book"
    },
    "defaultBranch": {
    "title": "Default Branch",
    "type": "string",
    "icon": "GitVersion"
    },
    "language": {
    "title": "Language",
    "type": "string",
    "icon": "Code"
    }
    },
    "required": []
    },
    "mirrorProperties": {},
    "calculationProperties": {},
    "aggregationProperties": {},
    "relations": {}
    }
  5. Click on Save to create the blueprint.

Set up GitHub integration

  1. Set up GitHub in Port by following the GitHub Ocean integration guide.

  2. Configure the mapping:

    1. From the data sources page, locate the GitHub integration you installed and click on it.

    2. Under the Mapping field, paste the following mapping configuration:

      GitHub mapping configuration (click to expand)
      createMissingRelatedEntities: true
      resources:
      - kind: repository
      selector:
      query: 'true'
      port:
      entity:
      mappings:
      identifier: .name
      title: .name
      blueprint: '"githubRepository"'
      properties:
      readme: file://README.md
      url: .html_url
      defaultBranch: .default_branch
      language: .language
  3. Click on the Save & Resync button at the bottom right corner.

Set up self-service actions

We'll create a self-service action that allows users to trigger the migration flow from Port's UI.

Create the migration action

  1. Go to the Self-service page.

  2. Click on + Action.

  3. Click on {...} Edit JSON to enter JSON mode.

  4. Copy and paste the following action configuration:

    Self-service action configuration (click to expand)
    {
    "identifier": "migrate_to_gitlab",
    "title": "Migrate Repository to GitLab",
    "icon": "GitLab",
    "description": "Migrate a GitHub repository to GitLab with full history and create the project automatically",
    "trigger": {
    "type": "self-service",
    "operation": "DAY-2",
    "userInputs": {
    "properties": {
    "org": {
    "title": "GitHub organization",
    "type": "string",
    "description": "The GitHub organization or user that owns the repository (for example, erioluwa-port)"
    },
    "project_name": {
    "title": "GitLab Project Name",
    "type": "string",
    "description": "Override GitLab project name (leave empty to use repository name)"
    },
    "visibility": {
    "title": "Visibility",
    "type": "string",
    "enum": ["private", "internal", "public"],
    "default": "private",
    "description": "Project visibility level"
    }
    },
    "required": ["org", "visibility"]
    },
    "blueprintIdentifier": "githubRepository"
    },
    "invocationMethod": {
    "type": "INTEGRATION_ACTION",
    "installationId": "YOUR_GITHUB_INTEGRATION_ID",
    "integrationActionType": "dispatch_workflow",
    "integrationActionExecutionProperties": {
    "org": "YOUR_GITHUB_ORG",
    "repo": "YOUR_GITHUB_REPO",
    "workflow": "migrate-to-gitlab.yml",
    "workflowInputs": {
    "{{ spreadValue() }}": "{{ .inputs }}",
    "port_context": {
    "runId": "{{ .run.id }}",
    "blueprint": "{{ .action.blueprint }}",
    "entity": "{{ .entity }}"
    }
    },
    "reportWorkflowStatus": true
    }
    },
    "requiredApproval": false,
    "allowAnyoneToViewRuns": true
    }
  5. Replace the following fields:

    • installationId: The ID of your GitHub integration in Port (find it on the data sources page).
    • org: Your GitHub organization where the workflow resides.
    • repo: The repository where the GitHub Actions workflow is stored.
  6. Click Save to create the action.

Workflow name

Make sure the workflow name matches the filename of your GitHub Actions workflow (in this guide, migrate-to-gitlab.yml).

Create the GitHub Actions workflow

Create a file in your repository at .github/workflows/migrate-to-gitlab.yml. This workflow handles the actual migration steps - creating the GitLab project and pushing the complete repository history.

GitHub Actions workflow (Click to expand)
name: Migrate to GitLab

on:
workflow_dispatch:
inputs:
org:
description: 'GitHub organization or user that owns the repository (for example, erioluwa-port)'
required: true
type: string
project_name:
description: 'Project name (leave empty to use repository name)'
required: false
type: string
visibility:
description: 'Project visibility'
required: true
type: choice
options:
- private
- internal
- public
default: private
port_context:
description: 'JSON string with runId, blueprint, and entity from Port'
required: true
type: string

jobs:
migrate:
runs-on: ubuntu-latest

steps:
- name: Inform Port - migration started
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
logMessage: "Starting GitHub → GitLab migration... 🚀"

- name: Clone GitHub repository (full history)
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GITHUB_ORG: ${{ inputs.org }}
GITHUB_REPO: ${{ fromJson(inputs.port_context).entity.identifier }}
run: |
git clone --mirror \
"https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_ORG}/${GITHUB_REPO}.git" \
repo.git
echo "Cloned ${GITHUB_ORG}/${GITHUB_REPO} with full history"

- name: Set project name
id: project
env:
INPUT_PROJECT_NAME: ${{ inputs.project_name }}
GITHUB_REPO: ${{ fromJson(inputs.port_context).entity.identifier }}
run: |
PROJECT_NAME=""

if [ -n "$INPUT_PROJECT_NAME" ] && [ "$INPUT_PROJECT_NAME" != "null" ]; then
PROJECT_NAME="$INPUT_PROJECT_NAME"
fi

if [ -z "$PROJECT_NAME" ] || [ "$PROJECT_NAME" = "null" ]; then
# Strip org prefix (e.g. "org/repo" → "repo")
PROJECT_NAME="${GITHUB_REPO##*/}"
echo "Using repository name as fallback: $PROJECT_NAME"
fi

echo "name=$PROJECT_NAME" >> $GITHUB_OUTPUT
echo "Using project name: $PROJECT_NAME"

- name: Create GitLab project
id: create_project
env:
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
GITLAB_HOST: ${{ secrets.GITLAB_HOST }}
GITLAB_NAMESPACE: ${{ secrets.GITLAB_NAMESPACE }}
PROJECT_NAME: ${{ steps.project.outputs.name }}
VISIBILITY: ${{ inputs.visibility }}
run: |
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "https://${GITLAB_HOST}/api/v4/projects" \
-H "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"${PROJECT_NAME}\",
\"path\": \"${PROJECT_NAME}\",
\"namespace_id\": \"$(curl -s "https://${GITLAB_HOST}/api/v4/namespaces?search=${GITLAB_NAMESPACE}" -H "PRIVATE-TOKEN: ${GITLAB_TOKEN}" | jq -r '.[0].id')\",
\"visibility\": \"${VISIBILITY}\",
\"initialize_with_readme\": false
}")

HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)

if [ "$HTTP_CODE" -eq 201 ]; then
echo "✅ GitLab project created successfully"
PROJECT_URL=$(echo "$BODY" | jq -r '.http_url_to_repo')
echo "url=$PROJECT_URL" >> $GITHUB_OUTPUT
echo "Project URL: $PROJECT_URL"
elif [ "$HTTP_CODE" -eq 400 ] && echo "$BODY" | jq -e '.message.path[]' | grep -q "has already been taken"; then
echo "⚠️ Project already exists, will use existing project"
PROJECT_URL="https://${GITLAB_HOST}/${GITLAB_NAMESPACE}/${PROJECT_NAME}.git"
echo "url=$PROJECT_URL" >> $GITHUB_OUTPUT
else
echo "❌ Failed to create project. HTTP code: $HTTP_CODE"
echo "Response: $BODY"
exit 1
fi

- name: Inform Port - GitLab project ready
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
logMessage: "GitLab project '${{ steps.project.outputs.name }}' is ready. Pushing repository history... 📦"

- name: Configure Git
run: |
git config --global user.name "GitHub Actions Bot"
git config --global user.email "actions@github.com"

- name: Push to GitLab
env:
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
GITLAB_URL: ${{ steps.create_project.outputs.url }}
run: |
cd repo.git
GITLAB_HOST=$(echo $GITLAB_URL | sed -E 's|https?://([^/]+)/.*|\1|')
GITLAB_PATH=$(echo $GITLAB_URL | sed -E 's|https?://[^/]+/(.*)|/\1|')
git remote add gitlab https://oauth2:${GITLAB_TOKEN}@${GITLAB_HOST}${GITLAB_PATH}
# Push all branches and tags
git push gitlab --mirror --force

- name: Verify migration
run: |
echo "✅ Migration completed successfully!"
echo "Repository has been pushed to: ${{ steps.create_project.outputs.url }}"
echo ""
echo "Refs pushed:"
cd repo.git && git show-ref

- name: Inform Port - migration complete
if: success()
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
logMessage: |
Migration completed successfully! ✅
Repository is now available at: ${{ steps.create_project.outputs.url }}

- name: Inform Port - migration failed
if: failure()
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).runId }}
status: FAILURE
logMessage: "Migration failed ❌. Check the workflow logs for details."
Required secrets

You'll need to set up the following secrets in your GitHub repository (under Settings → Secrets and variables → Actions):

  • GH_TOKEN: A GitHub Personal Access Token with repo scope for cloning the source repositories. Create one at: GitHub → Settings → Developer settings → Personal access tokens
  • GITLAB_TOKEN: Your GitLab Personal Access Token with api scope. Create one at: GitLab → Settings → Access Tokens
  • GITLAB_HOST: Your GitLab instance hostname (e.g. gitlab.com or gitlab.mycompany.com)
  • GITLAB_NAMESPACE: Your GitLab username or group (e.g. myusername or mycompany/engineering)
  • PORT_CLIENT_ID and PORT_CLIENT_SECRET: Used by the Port GitHub Action to send log updates back to Port. Find these in Port → Settings → Credentials

Let's test it

  1. Go to the Self-service page.

  2. Find the "Migrate Repository to GitLab" action.

  3. Click Execute.

  4. Select a GitHub repository from the dropdown.

  5. Optionally override the GitLab project name.

  6. Choose the visibility level (private, internal, or public).

  7. Click Execute to start the migration.

  8. Monitor the action execution in Port's logs.

  9. Verify that the repository is successfully created in your GitLab namespace with full commit history.

What happens during migration

  1. Repository selection - you select a GitHub repository from your Port catalog.
  2. Project creation - the workflow creates a new GitLab project via the GitLab API.
  3. History preservation - full Git history is fetched, including all branches and tags.
  4. Push to GitLab - all branches and tags are pushed to the new GitLab project.
  5. Verification - the workflow confirms successful migration and displays the new GitLab URL.

Troubleshooting

"Failed to create project" error

  • Verify your GITLAB_TOKEN has api scope permissions.
  • Check that GITLAB_NAMESPACE exists and is accessible.
  • Ensure the project name doesn't already exist in the namespace.

"Authentication failed" error

  • Verify all three secrets are correctly set in GitHub.
  • Check that your GitLab token hasn't expired.
  • Confirm the token has access to the specified namespace.

"Project already exists" warning

  • The workflow will attempt to use the existing project.
  • If you want a fresh migration, delete the existing GitLab project first.
  • Or provide a different project name to avoid conflicts.