Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild

GitLab Export Project: What the Export File Contains and What It Silently Drops

Sean

Platform Writer

Aug 27, 2026
8 min read

GitLab’s project export produces a single tarball containing the repository, issues, merge requests, milestones, and most project metadata. What it does not contain is the part that catches people out, and the list is longer than you would expect.

GitLab Export Project: What the Export File Contains and What It Silently Drops

Export is the right tool for moving one project between GitLab instances. It is not a backup, it is not a complete copy, and treating it as either is how a migration ends with a team asking where their CI variables went.

Table of contents

Running the export

The export is triggered from the project’s settings and delivered as a download link by email, or fetched via the API.

  1. Open the project and go to Settings, then General.
  2. Expand the Advanced section.
  3. Select Export project.
  4. Wait for the email with the download link, or return to the same page where the link appears once generation finishes.

You need at least the Owner role on the project. If the Export project button is missing entirely, the instance administrator has disabled exports in the application settings - it is an instance-wide toggle, not a per-project one.

The API route is the better option for anything repeatable, because it lets you poll for completion rather than waiting on an email.

# Trigger the export
curl --request POST \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/export"

# Poll status until export_status is finished
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/export"

# Download
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  --output project.tar.gz \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/export/download"

The download link expires, so fetch the file promptly. On a large project, generation can take a long time and the request will appear to be doing nothing - poll the status endpoint rather than assuming it failed.

What is in the tarball

Unpack it and you get a directory tree with a bundled repository and a set of serialised metadata files.

tar -tzf project.tar.gz | head -20

# Typical contents
# project.bundle          - the Git repository as a bundle
# project.json / tree/    - serialised project metadata
# uploads/                - files attached to issues and comments
# lfs-objects/            - Git LFS objects, if any
# VERSION                 - the export format version

The metadata covers the things people actually care about in a migration: issues with their comments and labels, merge requests with their discussions and approvals, milestones, boards, snippets, releases, and the wiki repository.

The repository arrives as a Git bundle rather than a bare clone. If you only want the code and history, you can extract it without going through an import at all.

tar -xzf project.tar.gz
git clone project.bundle recovered-repo
cd recovered-repo
git log --oneline | head

That trick is worth knowing on its own. When an import fails for some structural reason, you can still recover the full history from the bundle and push it somewhere else while you work out what went wrong.

What it does not carry

This is the section to read before planning a migration. The export deliberately omits several categories, mostly for security reasons.

  • CI/CD variables. Not exported. They are secrets, and the export file is a downloadable artifact. You will need to recreate them by hand or via the API on the destination.
  • Pipeline history and job artifacts. Job logs, artifacts, and past pipeline runs do not come across. The CI configuration file does, because it lives in the repository.
  • Deploy keys, deploy tokens, and webhooks. All recreated manually on the destination.
  • Container registry images and package registry contents. Stored separately from the project and not included.
  • Group-level anything. Group labels, group milestones, group variables, and group runners belong to the group, not the project. If you export a project out of a group, the group-level objects do not follow.
  • Merge request approval rules on some tiers, and certain security and compliance settings.
  • Project members, unless the same users exist on the destination and are matched by email during import.

That last point produces the most confusing symptom. If a user’s email does not exist on the destination instance, issues and comments they authored are reassigned to the importing user, and the original author is noted in the comment body instead. The history is not lost, but the attribution changes and it cannot be undone afterwards.

The practical response is to create the user accounts on the destination first, with matching email addresses, before running the import. Doing it in that order costs nothing; doing it afterwards means re-importing.

Importing on the other side

Import is the mirror operation and has one hard requirement that is easy to miss.

  1. On the destination, create a new project and choose Import project.
  2. Select GitLab export as the source.
  3. Choose the target namespace and enter a project name.
  4. Upload the tarball and start the import.

The requirement: the GitLab version on the destination must be the same as or newer than the source. Exports are not backward compatible. Trying to import a file produced by a newer GitLab into an older one fails, sometimes with an unhelpful error, so check both versions before you start.

# Import via API
curl --request POST \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  --form "path=imported-project" \
  --form "namespace=mygroup" \
  --form "[email protected]" \
  "https://gitlab.example.com/api/v4/projects/import"

# Check progress
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$NEW_ID/import"

File size limits apply on hosted instances, and a large project with years of attachments can exceed them. If you hit that wall, the direct transfer feature - which moves a project between instances over the API without producing a file - is the alternative worth investigating.

Export is not a backup

It is tempting to schedule exports and call it a backup strategy. It is not one, for three reasons worth being explicit about.

First, it is incomplete by design, as the omissions list above shows. A restore from an export leaves you rebuilding CI variables, webhooks, and registry contents by hand, from memory, during whatever incident prompted the restore.

Second, it is per-project. A hundred projects means a hundred exports to orchestrate, monitor, and store, with no consistency guarantee between them.

Third, the format is version-coupled. An export sitting in cold storage for two years may not import into the GitLab you are running by then, and you will discover that at the worst possible moment.

For self-managed GitLab, the instance backup tooling is the actual answer - it captures the database, repositories, uploads, and registry together. For hosted GitLab, the platform handles it. Use project export for what it is good at: moving one project deliberately, with a checklist of the things you know you have to recreate.

How this fits the rest of the stack

A migrated repository is only half the move - the other half is standing the application back up somewhere. Push the repo to RunxBuild and you get a build log, a live route, environment variables held by the platform rather than a file, runtime logs, and rollback to the previous deploy. The RunxBuild hosting calculator shows the service, a managed Postgres, and storage as separate line items, so the destination has a cost attached before you start moving anything.

Useful related references:

FAQ

What does a GitLab project export include?

The repository as a Git bundle, plus issues, merge requests and their discussions, milestones, labels, boards, snippets, releases, the wiki, and files attached to issues and comments. Everything arrives in a single compressed tarball.

Are CI/CD variables included in a GitLab export?

No. Variables are secrets and the export file is a downloadable artifact, so they are deliberately excluded. Pipeline history, job artifacts, deploy keys, deploy tokens, and webhooks are also omitted and must be recreated on the destination.

Can I import an export into an older GitLab version?

No. Exports are only compatible with the same version or newer. Check both instances before starting a migration - importing a file produced by a newer GitLab into an older one fails, sometimes with an error that does not name the cause.

Why did my imported issues lose their original authors?

Because the authoring users do not exist on the destination instance with matching email addresses. GitLab reassigns those items to the importing user and notes the original author in the comment body. Create the user accounts first, then import - the attribution cannot be corrected afterwards.

Is GitLab project export a backup?

No. It is incomplete by design, operates one project at a time, and is coupled to the GitLab version that produced it. For self-managed instances use the instance backup tooling, which captures the database, repositories, uploads, and registry together.

#gitlab export project#gitlab import export#gitlab migration#gitlab backup#devops