---
title: "GitLab Integration"
description: "Integrate Shorebird into your GitLab CI/CD pipelines"
---

This guide shows you how to create Shorebird releases and patches from [GitLab CI/CD](https://docs.gitlab.com/ci/) with a `.gitlab-ci.yml` file.

## Prerequisites

✅ Your app has been initialized with `shorebird init`, and `shorebird.yaml` is committed to your repository.

✅ A Shorebird key for the pipeline, as described in [Authentication](#authentication).

## Quick start

There is no Shorebird component for GitLab. A job installs Shorebird with the same script you use locally, then adds it to the `PATH`:

```
shorebird-example:
  # Any image with Git, a JDK and the Android SDK works.
  image: ghcr.io/cirruslabs/flutter:stable
  # Runs only when you start it, rather than on every push.
  when: manual
  allow_failure: true
  script:
    - curl -fsSL https://shorebird.dev/install.sh | bash
    - export PATH="$HOME/.shorebird/bin:$PATH"
    - shorebird --version
```

Shorebird installs its own copy of Flutter in `~/.shorebird`, so the Flutter version in the image does not matter. It does not install a JDK or the Android SDK, so the image needs those. `shorebird doctor --verbose` lists what it finds on the runner.

## Authentication

Creating releases and patches needs a signed-in Shorebird CLI. In CI, the CLI reads an API key from the `SHOREBIRD_TOKEN` environment variable, so nobody has to run `shorebird login` on the builder.

Use a key that belongs to a [service account](/account/service-accounts/). A service account belongs to your organization rather than to a person, so its key keeps working when whoever set up CI leaves or changes roles, and the audit log shows the job’s actions under the service account’s name. Service accounts are available on every plan. To create one, you need the Admin or Owner role in the organization:

1. In the [Shorebird Console](https://console.shorebird.dev), open your organization’s **Settings** and select **Service accounts**.
2. Click **Create service account**, name it after the CI system that will use it (such as `GitHub Actions`), and click **Create**.
3. Keep the **Developer** role, which can create releases and patches. Choose **App Manager** instead if the job also creates apps, for example by running `shorebird init`.
4. Under **API keys**, click **Create key**, enter a name, and choose an expiration.
5. Copy the key. It is shown only once.

Store the key as `SHOREBIRD_TOKEN` in your CI platform’s secrets manager, as the steps below show.

Caution

`SHOREBIRD_TOKEN` is a secret. Do not check it into source control or share it publicly.

If you can’t create a service account, a personal API key from **Account → API Keys** goes in `SHOREBIRD_TOKEN` the same way. It acts as you, can do at most what your own role allows, and stops working if your account is deleted. See [API Keys](/account/api-keys/).

Migrating from `shorebird login:ci`

Tokens from `shorebird login:ci` stopped working on October 1, 2026. Replace the value of your `SHOREBIRD_TOKEN` secret with a new key; the variable name and your workflow files stay the same.

Next, with the key copied, navigate to your GitLab project’s CI/CD variables via:

`"Settings" -> "CI/CD" -> "Variables"`.

Then, click `"Add variable"` and enter:

```
Key: SHOREBIRD_TOKEN
Value: <THE KEY YOU COPIED>
Visibility: Masked (or Masked and hidden)
Protect variable: checked
```

Masking keeps the key out of job logs. Protect variable makes GitLab pass the key only to pipelines on protected branches and tags, so a pipeline on any other branch can’t read it. GitLab protects your default branch unless your project or group settings change that; if yours do, protect it under `"Settings" -> "Repository" -> "Protected branches"`.

GitLab passes CI/CD variables to every job as environment variables, so the Shorebird CLI finds `SHOREBIRD_TOKEN` without any change to `.gitlab-ci.yml`.

Note

[Signing in with OIDC](/code-push/ci/github/#sign-in-with-oidc-instead-of-a-stored-key) is available for GitHub Actions only. GitLab jobs use an API key in `SHOREBIRD_TOKEN`.

## Android code signing

`shorebird release android` and `shorebird patch android` run `flutter build`, so signing works exactly as it does for any Flutter app. Follow [Flutter’s Android signing instructions](https://docs.flutter.dev/deployment/android#sign-the-app), or the [Shorebird version of them](/flutter-concepts/releasing-flutter-apps/android#step-3-configure-signing-in-gradle).

The only difference on CI is that the keystore is not already on the machine. The pipeline below expects four more CI/CD variables:

* `ANDROID_KEYSTORE_BASE64`: your upload keystore, base64-encoded on one line (`base64 -i upload-keystore.jks | tr -d '\n'`).
* `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS` and `ANDROID_KEY_PASSWORD`: the values your `key.properties` holds locally.

Check Protect variable on all four, as for `SHOREBIRD_TOKEN`, and mask each one GitLab accepts as masked (masked values need at least 8 characters and no spaces). Leave Expand variable reference cleared, which is the default since GitLab 18.6. If it is checked on `ANDROID_KEYSTORE_PASSWORD` or `ANDROID_KEY_PASSWORD`, GitLab reads a `$` in the password as a reference to another variable and the build fails with a keystore password error.

The jobs decode the keystore to a path outside your checkout and write `android/key.properties` before the Shorebird command runs.

Without `key.properties`, the release build fails, because the release signing config from the Gradle setup above gets no `storeFile`. An app still on Flutter’s default release config signs with the debug key instead, which the Play Console rejects.

## Create releases and patches

Add this to `.gitlab-ci.yml`. It adds two manual jobs to pipelines on your default branch: one creates an Android release, the other patches one. Your other jobs are left as they are.

```
# Settings shared by every Shorebird job.
.shorebird:
  # A default stage. If your file lists stages:, add deploy to it.
  stage: deploy
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: manual
  # Keep the pipeline from waiting on a manual job nobody runs.
  allow_failure: true
  before_script:
    # Install Shorebird and add it to the PATH for this job.
    - curl -fsSL https://shorebird.dev/install.sh | bash
    - export PATH="$HOME/.shorebird/bin:$PATH"

# Settings shared by the Android jobs below.
.android:
  extends: .shorebird
  # Any image with Git, a JDK and the Android SDK works.
  image: ghcr.io/cirruslabs/flutter:stable

# Decode the keystore and write key.properties for release signing.
.android_signing: &android_signing
  - echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > /tmp/upload-keystore.jks
  - |
    cat > android/key.properties <<EOF
    storePassword=$ANDROID_KEYSTORE_PASSWORD
    keyPassword=$ANDROID_KEY_PASSWORD
    keyAlias=$ANDROID_KEY_ALIAS
    storeFile=/tmp/upload-keystore.jks
    EOF

release-android:
  extends: .android
  variables:
    # Pin the Flutter version so every release builds the same way.
    FLUTTER_VERSION: '3.47.9'
  script:
    - *android_signing
    - shorebird release android --flutter-version="$FLUTTER_VERSION"
  artifacts:
    # The app bundle to upload to the Play Store.
    paths:
      - build/app/outputs/bundle/release/app-release.aab

patch-android:
  extends: .android
  variables:
    # The release to patch. "latest" is the most recently updated release.
    RELEASE_VERSION: latest
  script:
    - *android_signing
    - shorebird patch android --release-version="$RELEASE_VERSION"
      --track=stable
```

To run a job, open the pipeline and click Run (▶) on `release-android` or `patch-android`. To patch a release other than the latest, click the job’s name instead and set `RELEASE_VERSION` to that release’s version, such as `1.0.0+1`, before running it.

If your app uses flavors, pass `--flavor` to both commands and change the artifact path to match, for example `build/app/outputs/bundle/<flavor>Release/app-<flavor>-release.aab`.

GitLab sets `CI=true` in every job, so the Shorebird CLI runs non-interactively and never waits for input. A prompt with no safe default fails with an error that names the flag to pass. For example, `shorebird patch` fails on native code changes unless you pass `--allow-native-diffs`. See [Scripting the Shorebird CLI](/code-push/ci/scripting) for the full rules.

### Run on tags instead

To release or patch when you push a tag rather than by hand, give every Shorebird job its own `rules`, including the iOS jobs below. `extends` merges settings key by key but replaces lists, so a job’s `rules` replace the manual rule from `.shorebird` rather than adding to it. In `release-android` and `release-ios`:

```
rules:
  - if: $CI_COMMIT_TAG =~ /^v/
```

and in `patch-android` and `patch-ios`:

```
rules:
  - if: $CI_COMMIT_TAG =~ /^patch-/
```

Then remove `allow_failure: true` from `.shorebird`. It only keeps the pipeline from waiting on a manual job. On a job that runs on its own, it reports a failed release or patch as passed with warnings, so the tag pipeline looks green when nothing was published.

Protect these tag patterns under `"Settings" -> "Repository" -> "Protected tags"`, and allow only Maintainers to create them. Pipelines for unprotected tags don’t receive your protected variables, and protecting the tags keeps anyone who can push a branch from publishing a release or patch.

## iOS

`shorebird release ios` and `shorebird patch ios` need Xcode, so iOS jobs run on a macOS runner. On [GitLab’s hosted macOS runners](https://docs.gitlab.com/ci/runners/hosted_runners/macos/), which need a GitLab Premium or Ultimate plan, `tags` selects the machine and `image` selects the macOS and Xcode version. Add a job like this next to the Android ones:

```
release-ios:
  extends: .shorebird
  tags:
    - saas-macos-medium-m1
  # Pin the image so the Xcode version only changes when you change it.
  image: macos-26-xcode-26
  variables:
    FLUTTER_VERSION: '3.47.9'
  script:
    - shorebird release ios --flutter-version="$FLUTTER_VERSION"
  artifacts:
    # The IPA to upload to App Store Connect.
    paths:
      - build/ios/ipa/*.ipa
```

A `patch-ios` job takes the same `tags` and `image`, with the `variables` of `patch-android` and `shorebird patch ios --release-version="$RELEASE_VERSION" --track=stable` as its script. The hosted runner docs list the current images. Keep a release’s image while you patch it, and move to a new one with a new release; see [Build patches with the same toolchain as the release](/code-push/ci/generic/#build-patches-with-the-same-toolchain-as-the-release).

Every job on a hosted macOS runner starts on a fresh VM with no signing identity, so as written `release-ios` fails at code signing. Install your distribution certificate and provisioning profile in the job’s `script` before the Shorebird command, on every run. Use [fastlane match](/code-push/ci/fastlane#using-fastlane-on-ci), or store a base64-encoded `.p12` and profile in CI/CD variables and import them with `security`. To skip signing on CI, pass `--no-codesign` as the [generic CI guide](/code-push/ci/generic#build-without-code-signing-in-restricted-environments) describes. That builds no IPA, only `build/ios/archive/Runner.xcarchive`, so change the job’s artifact path to that archive to keep it, then sign and distribute it from Xcode with Manage Version and Build Number unchecked (see [Uploading from Xcode](/code-push/release#uploading-from-xcode)).

On a [self-hosted runner](https://docs.gitlab.com/runner/) on a Mac, use your runner’s tags instead. A shell runner ignores `image` and uses the Xcode installed on the machine. If it keeps its home directory between jobs, the install script stops when `~/.shorebird` already exists, so override `before_script` to install Shorebird only when it is missing:

```
before_script:
  - '[ -d "$HOME/.shorebird" ] || curl -fsSL https://shorebird.dev/install.sh |
    bash'
  - export PATH="$HOME/.shorebird/bin:$PATH"
```

A self-hosted runner needs the certificate and profile too, either installed in the keychain of the user the runner runs as or imported by the job as above.

For an example of a fully automated development workflow, see the Development Workflow Guide.

[Development Workflow Guide](/code-push/guides/development-workflow)Learn how to set up a fully automated development workflow with Shorebird.
