> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Limit deployments to Platform.sh only when Git tagged: part two

export const PostMeta = ({data = {}}) => {
  const {author, date, image} = data;
  const authors = Array.isArray(author) ? author : author ? [author] : [];
  const resolveAuthor = slug => {
    const entry = AUTHOR_MAP[slug] || ({});
    const name = entry.name || slug;
    const github = entry.github || null;
    const linkedin = entry.linkedin || null;
    const url = github ? `https://github.com/${github}` : linkedin || null;
    const avatarUrl = github ? `https://github.com/${github}.png?size=64` : null;
    return {
      name,
      url,
      avatarUrl
    };
  };
  const formattedDate = date ? new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }) : null;
  if (!image && authors.length === 0 && !formattedDate) return null;
  const AUTHOR_MAP = {
    "aaron-collier": {
      "name": "Aaron Collier"
    },
    "aaron-dudenhofer": {
      "name": "Aaron Dudenhofer"
    },
    "aaron-porter": {
      "name": "Aaron Porter"
    },
    "adriaan-odendaal": {
      "name": "Adriaan Odendaal"
    },
    "ajmal": {
      "name": "Ajmal Siddiqui"
    },
    "akalipetis": {
      "name": "Antonis Kalipetis"
    },
    "alexander-varwijk": {
      "name": "Alexander Varwijk"
    },
    "alicia-bevilacqua": {
      "name": "Alicia Bevilacqua"
    },
    "amelie-deguerry": {
      "name": "Amelie Deguerry"
    },
    "anacidre": {
      "name": "Ana Cidre",
      "linkedin": "https://www.linkedin.com/in/ana-cidre"
    },
    "andoni": {
      "name": "Andoni Auzmendi"
    },
    "andrei-taranu": {
      "name": "Andrei (Alex) Taranu",
      "linkedin": "https://www.linkedin.com/in/andrei-alex-taranu/"
    },
    "andrew-baxter": {
      "name": "Andrew Baxter"
    },
    "andrew-melck": {
      "name": "Andrew Melck"
    },
    "antoine-crochet-damais": {
      "name": "Antoine Crochet Damais"
    },
    "augustin-delaporte": {
      "name": "Augustin Delaporte",
      "linkedin": "https://www.linkedin.com/in/augustindelaporte/"
    },
    "branislav-bujisic": {
      "name": "Branislav Bujisic"
    },
    "carl-smith": {
      "name": "Carl Smith"
    },
    "caroline-leroy": {
      "name": "Caroline Leroy"
    },
    "cati-mayer": {
      "name": "Cati Mayer"
    },
    "catplat": {
      "name": "C Trinkwon"
    },
    "ceelolulu": {
      "name": "Celeste van der Watt"
    },
    "chadwcarlson": {
      "name": "Chad Carlson",
      "github": "chadwcarlson",
      "linkedin": "https://www.linkedin.com/in/chadwcarlson"
    },
    "chris-ward": {
      "name": "Chris Ward"
    },
    "chris-yates": {
      "name": "Chris Yates"
    },
    "christian-sieber": {
      "name": "Christian Sieber"
    },
    "christopher-lockheardt": {
      "name": "Christopher Lockheardt"
    },
    "christopher-skene": {
      "name": "Christopher Skene"
    },
    "chuck-morgan": {
      "name": "Chuck Morgan"
    },
    "corey-dockendorf": {
      "name": "Corey Dockendorf"
    },
    "crell": {
      "name": "Crell"
    },
    "damz": {
      "name": "Damz"
    },
    "dan-morrison": {
      "name": "Dan Morrison"
    },
    "davidbonachera": {
      "name": "David Bonachera",
      "github": "davidbonachera",
      "linkedin": "https://www.linkedin.com/in/davidbonachera"
    },
    "dereliahmet1": {
      "name": "Ahmet Faruk Dereli"
    },
    "devicezero": {
      "name": "Jonas Kröger",
      "github": "devicezero",
      "linkedin": "https://www.linkedin.com/in/jonaskroeger/"
    },
    "doug-goldberg": {
      "name": "Doug Goldberg"
    },
    "duncan-naves": {
      "name": "Duncan Naves",
      "github": "duncannaves",
      "linkedin": "https://www.linkedin.com/in/duncan-naves-a94423aa"
    },
    "erika-bustamante": {
      "name": "Erika Bustamante"
    },
    "fabpot": {
      "name": "Fabien Potencier"
    },
    "flovntp": {
      "name": "Florent Huck",
      "github": "flovntp",
      "linkedin": "https://www.linkedin.com/in/florenthuck"
    },
    "fred-plais": {
      "name": "Fred Plais"
    },
    "gauthier-garnier": {
      "name": "Gauthier Garnier"
    },
    "gilzow": {
      "name": "Paul Gilzow"
    },
    "gmoigneu": {
      "name": "Guillaume Moigneu",
      "github": "gmoigneu",
      "linkedin": "https://www.linkedin.com/in/guillaumemoigneu/"
    },
    "gregqualls": {
      "name": "Greg Qualls"
    },
    "guguss": {
      "name": "Augustin Delaporte"
    },
    "haylee-millar": {
      "name": "Haylee Millar"
    },
    "ivana-kotur": {
      "name": "Ivana Kotur"
    },
    "jackrabbithanna": {
      "name": "Mark Hanna"
    },
    "jared-wright": {
      "name": "Jared Wright",
      "github": "jww-sh",
      "linkedin": "https://www.linkedin.com/in/jaredwaynewright"
    },
    "jessica-orozco": {
      "name": "Jessica Orozco"
    },
    "joey-stanford": {
      "name": "Joey Stanford"
    },
    "john-grubb": {
      "name": "John Grubb"
    },
    "jonas-kruger": {
      "name": "Jonas Kruger"
    },
    "kathryn-frazer": {
      "name": "Kathryn Frazer"
    },
    "kemiojo": {
      "name": "Kemi Elizabeth Ojogbede"
    },
    "kieronsambrook-smith": {
      "name": "Kieronsambrook Smith"
    },
    "laurent-arnoud": {
      "name": "Laurent Arnoud"
    },
    "letoya-boyne": {
      "name": "Letoya Boyne"
    },
    "lolautruche": {
      "name": "Jérôme Vieilledent"
    },
    "lyly-lepinay": {
      "name": "Lyly Lepinay"
    },
    "manauwar-alam": {
      "name": "Manauwar Alam"
    },
    "marc-antoine-porri": {
      "name": "Marc Antoine Porri"
    },
    "maria-antinkaapo": {
      "name": "Maria Antinkaapo"
    },
    "maria-de-anton": {
      "name": "Maria De Anton"
    },
    "mark-dorison": {
      "name": "Mark Dorison"
    },
    "markus-hausammann": {
      "name": "Markus Hausammann"
    },
    "mary-thomas": {
      "name": "Mary Thomas"
    },
    "mathias-bolt-lesniak": {
      "name": "Mathias Bolt Lesniak"
    },
    "mathieu-strauch": {
      "name": "Mathieu Strauch"
    },
    "matthias-van-woensel": {
      "name": "Matthias Van Woensel",
      "linkedin": "https://www.linkedin.com/in/matthias-van-woensel-267a069"
    },
    "michael-sharp": {
      "name": "Michael Sharp"
    },
    "mupsi": {
      "name": "Marine Gandy"
    },
    "natalie-harper": {
      "name": "Natalie Harper"
    },
    "ngommenginger": {
      "name": "Nicolas Gommenginger",
      "linkedin": "https://www.linkedin.com/in/nicolas-gommenginger"
    },
    "nicholas-bennison": {
      "name": "Nicholas Bennison"
    },
    "nicholas-vahalik": {
      "name": "Nicholas Vahalik"
    },
    "nick-hardiman": {
      "name": "Nick Hardiman"
    },
    "nickanderegg": {
      "name": "Nickanderegg"
    },
    "nicolas-grekas": {
      "name": "Nicolas Grekas",
      "github": "nicolas-grekas",
      "linkedin": "https://www.linkedin.com/in/nicolasgrekas/"
    },
    "niti-malwade": {
      "name": "Niti Malwade"
    },
    "opensocialteam": {
      "name": "Opensocialteam"
    },
    "ori-pekelman": {
      "name": "Ori Pekelman"
    },
    "otavio-santana": {
      "name": "Otavio Santana"
    },
    "palwandi": {
      "name": "Pawan Alwandi",
      "github": "pawpy",
      "linkedin": "https://www.linkedin.com/in/pawanalwandi"
    },
    "patrick-boest": {
      "name": "Patrick Boest"
    },
    "patrick-dawkins": {
      "name": "Patrick Dawkins",
      "github": "pjcdawkins",
      "linkedin": "https://www.linkedin.com/in/patrickdawkins"
    },
    "patrick-klima": {
      "name": "Patrick Klima"
    },
    "pjcdawkins": {
      "name": "Pjcdawkins"
    },
    "prineet-kaurbhurji": {
      "name": "Prineet Kaurbhurji"
    },
    "quentin-sinig": {
      "name": "Quentin Sinig"
    },
    "ralt": {
      "name": "Florian Margaine",
      "github": "ralt",
      "linkedin": "https://www.linkedin.com/in/florian-margaine-43971136"
    },
    "ramanathanramakrishnamurthy": {
      "name": "Ramanathanramakrishnamurthy"
    },
    "remi-lejeune": {
      "name": "Rémi Lejeune"
    },
    "ribel": {
      "name": "Taras Kruts"
    },
    "robert-douglass": {
      "name": "Robert Douglass"
    },
    "rudy-weber": {
      "name": "Rudy Weber"
    },
    "ryan-hicks": {
      "name": "Ryan Hicks"
    },
    "sabri-helal": {
      "name": "Sabri Helal"
    },
    "savannah-bergeron": {
      "name": "Savannah Bergeron"
    },
    "shannon-vettes": {
      "name": "Shannon Vettes"
    },
    "shawn-ogasawara": {
      "name": "Shawn Ogasawara",
      "linkedin": "https://www.linkedin.com/in/shawn-ogasawara-83a9a0/"
    },
    "shawna-spoor": {
      "name": "Shawna Spoor"
    },
    "shedrack-akintayo": {
      "name": "Shedrack Akintayo"
    },
    "simon-ruggier": {
      "name": "Simon Ruggier"
    },
    "sophie-van-der-kindere": {
      "name": "Sophie Van Der Kindere"
    },
    "stefanos-thampis": {
      "name": "Stefanos Thampis"
    },
    "stephen-weinberg": {
      "name": "Stephen Weinberg"
    },
    "sukhman-virk": {
      "name": "Sukhman Virk"
    },
    "sumaira-nazir": {
      "name": "Sumaira Nazir"
    },
    "sumer": {
      "name": "Sümer Cip"
    },
    "syed-raza": {
      "name": "Syed Raza"
    },
    "tamara-bacchia": {
      "name": "Tamara Bacchia"
    },
    "tara-arnold": {
      "name": "Tara Arnold"
    },
    "theosakamg": {
      "name": "Mickael Gaillard",
      "github": "theosakamg"
    },
    "thomasdiluccio": {
      "name": "Thomas di Luccio"
    },
    "tim-anderson": {
      "name": "Tim Anderson"
    },
    "tom-helmer-hansen": {
      "name": "Tom Helmer Hansen"
    },
    "tylermills": {
      "name": "Tyler Mills"
    },
    "upsun": {
      "name": "Upsun"
    },
    "veronika-tolkachova": {
      "name": "Veronika Tolkachova",
      "linkedin": "https://www.linkedin.com/in/veronika-tolkachova-169167a2"
    },
    "vince-parker": {
      "name": "Vince Parker"
    },
    "vinnie-russo": {
      "name": "Vincenzo Russo"
    },
    "vrobert78": {
      "name": "Vincent Robert",
      "github": "vrobert78",
      "linkedin": "https://www.linkedin.com/in/vincent-robert-498a883"
    },
    "yuriy-babenko": {
      "name": "Yuriy Babenko"
    },
    "yuriy-gerasimov": {
      "name": "Yuriy Gerasimov"
    }
  };
  return <div className="post-meta">
      {(authors.length > 0 || formattedDate) && <div className="post-meta-info">
          {authors.length > 0 && <div className="post-meta-authors">
              {authors.map(slug => {
    const {name, url, avatarUrl} = resolveAuthor(slug);
    const inner = <>
                    {avatarUrl && <img src={avatarUrl} alt={name} className="post-meta-avatar" />}
                    <span className="post-meta-author-name">{name}</span>
                  </>;
    return url ? <a key={slug} href={url} target="_blank" rel="noopener noreferrer" className="post-meta-author">
                    {inner}
                  </a> : <span key={slug} className="post-meta-author">{inner}</span>;
  })}
            </div>}
          {authors.length > 0 && formattedDate && <span className="post-meta-separator" aria-hidden="true">·</span>}
          {formattedDate && <span className="post-meta-date">{formattedDate}</span>}
        </div>}
      {image && <img src={image} alt="" className="post-meta-image" aria-hidden="true" />}
    </div>;
};

<PostMeta data={{ author: ["gilzow"], date: "2024-03-14T10:00:00+00:00", image: "/images/posts/hands-on/guide-to-limit-deployments-to-platformsh-only-with-tags-part-two/Limit_deployments_to_psh-two.webp" }} />

In [part one of this series](https://platform.sh/blog/guide-to-limit-deployments-to-platformsh-only-with-tags-part-one/),
we covered how you could limit deployments to Platform.sh only when a tag is pushed/created, focusing primarily on using
GitHub and the GitHub Actions platform to accomplish this goal. But we’re a
[polyglot PaaS](https://platform.sh/marketplace/) and strive to be agnostic in our users’ source code management terms
of the service. With that in mind, let’s look at how we can accomplish the same goal using GitLab and your CI/CD system.

Just like last time, there are some assumptions to consider:

### **The assumptions**

For the purpose of this article and the steps detailed, I will assume that you have:

* A GitLab account and a repository with working code
* Administrative rights on the GitLab repository (so you can add [CI/CD variables](https://docs.gitlab.com/ee/ci/variables/index.html#define-a-cicd-variable-in-the-ui))
* A Platform.sh account and a project for your code base with working code
* A default branch in GitLab which is the same branch as your production branch in Platform.sh
* A default branch in Platform.sh which is also your production branch
* You do *not* have a source code integration created between Platform.sh and GitLab.

### **The `.gitlab-ci.yaml` file**

While GitHub has a `.github/workflows` directory where any `*.yaml` file can be a workflow definition, GitLab
consolidates the [CI/CD configuration into a single file](https://docs.gitlab.com/ee/ci/#the-gitlab-ciyml-file) named
`.gitlab-ci.yml`. This file must be in the root of your repository and contain everything needed to run your GitLab
[pipeline](https://docs.gitlab.com/ee/ci/pipelines/).

<img src="https://mintcdn.com/upsun-c9761871/tziXiwEbbKjwbX3l/images/posts/hands-on/guide-to-limit-deployments-to-platformsh-only-with-tags-part-two/limitdeploy1_c208b29752.webp?fit=max&auto=format&n=tziXiwEbbKjwbX3l&q=85&s=67d60d0544034965634af1a3bcdaf6af" alt="The .gitlab-ci.yml pipeline file" width="444" height="250" data-path="images/posts/hands-on/guide-to-limit-deployments-to-platformsh-only-with-tags-part-two/limitdeploy1_c208b29752.webp" />

### **The event**

Unlike GitHub workflows, GitLab pipelines are triggered[^1] when *any* commit is pushed to your repository. In our case,
we only want the pipeline to run when a tag is pushed or created. Instead of defining the specific event we want to
trigger our pipeline, we'll build a set of rules to define which type of push should trigger our pipeline. To build
these rules we'll utilize the `workflow` [property](https://docs.gitlab.com/ee/ci/yaml/workflow.html) which controls
when pipelines are run.

```yaml {filename=".gitlab-ci.yml"} theme={null}
workflow:
name: Deploy to Platform.sh on tag
rules:
  - if: $CI_COMMIT_TAG && $CI_PIPELINE_SOURCE == "push"
```

The predefined CI/CD variable `$CI_COMMIT_TAG` contains the commit tag name and, most importantly, is only set in tag
pipelines. The `$CI_PIPELINE_SOURCE` variable contains how the pipeline was triggered. Since we want a tag push, we want
to only run this pipeline if the trigger is a `push` event. I've also added a `name` property so this pipeline is easier
to identify in the repository's pipelines area.

### **The stages**

Like with GitHub, we'll need to define a series of jobs that perform the tasks. And also just like with GitHub, jobs run
in parallel by default. However, unlike GitHub Actions, GitLab gives us a way to control the order in which jobs run
with [stages](https://docs.gitlab.com/ee/ci/yaml/#stages). The stages are executed in order[^2], and later stages can
only start at earlier stages that have been completed.

Before we define our jobs, we'll define the stages that should occur, and then we'll attach jobs[^3] to their relevant
stage.

```yaml {filename=".gitlab-ci.yml"} theme={null}
stages:         
  - setup
  - perform
```

I've defined a `setup` stage, where I'll add the jobs that check to see if the tag that was pushed is one we want to
deploy to Platform.sh, and a `perform` stage where I'll add the jobs to perform the push.

### **Caching**

You might be wondering, "Why is there a section on caching when we just want to see if we need to push, and why is it
before we've defined any jobs?" Unlike GitHub and its
[outputs](https://docs.github.com/en/actions/using-jobs/defining-outputs-for-jobs), GitLab does not provide a
straightforward way to share small bits of data between jobs and stages.  To facilitate data sharing they instead
utilize [artifacts](https://docs.gitlab.com/ee/ci/caching/#artifacts) and
[cache](https://docs.gitlab.com/ee/ci/caching/#cache).

In either case, we have to use a file to store the data we want to share. Since
[job artifacts](https://docs.gitlab.com/ee/ci/jobs/job_artifacts.html) were specifically designed for sharing
intermediate build results between stages, in the case of just needing to pass a small bit of data to the next stage,
it's overkill. Instead, for this pipeline, we'll set up caching and cache a single file to share data between our stages.

Caching needs to be set up on a per-job basis but since we want both stages and their jobs to access the same cache,
we'll define our cache instead in the [default](https://docs.gitlab.com/ee/ci/yaml/#default) area of our pipeline.

```yaml {filename=".gitlab-ci.yml"} theme={null}
default:
  cache: &global_cache
    key: push-to-psh
    paths:
      - "push.txt"
    when: always
```

Here I'm using a [yaml anchor](https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases) ( `&global_cache`) to set up a
reusable cache definition at the global level that I then reference in each job, but with the ability to override any
properties as needed. A cache is required to have a [unique](https://docs.gitlab.com/ee/ci/yaml/index.html#cachekey)
`key`, and jobs that use the same cache key will use the same cache. Perfect for what we need!

We then define the file we want to cache (`push.txt`) using the `paths`
[property](https://docs.gitlab.com/ee/ci/yaml/index.html#cachepaths) and instruct GitLab to `always` save the cache with
`when`.

### **The jobs**

Now I'll define my jobs and attach them to the stage where they should run. To make things easy to read, I'll define the
job in the `setup` stage first and attach it to `setup` using the `stage` property:

```yaml {filename=".gitlab-ci.yml"} theme={null}
should_we_push:
  stage: setup
```

By default, GitLab checks out a
[shallow clone](https://docs.gitlab.com/ee/ci/pipelines/settings.html#limit-the-number-of-changes-fetched-during-clone)
of your repository based on the commit that was pushed. In our case, we want to see if the tag that was pushed is the
closest tag to the latest commit on our default branch so we'll instruct GitLab instead to checkout the default branch.

We can do so by using the `before_script` property. Any steps defined in `before_script` are executed before the job is
started which allows us to alter the default behavior:

```yaml {filename=".gitlab-ci.yml"} theme={null}
should_we_push:
  stage: setup
  before_script:
    - git fetch origin "${CI_DEFAULT_BRANCH}"
    - git fetch --tags
    - git checkout "${CI_DEFAULT_BRANCH}"
```

Now before the `should_we_push` job begins, we'll fetch any changes from GitLab, then fetch all the tags, and finally
checkout the default branch. This is fairly similar to what we accomplished in the GitHub Actions article when we used
the actions/checkout action to checkout our repository using the default branch with a depth of 0. The `script` section
seen below contains the commands we want our runner to execute.

<Note>
  **Note:** to keep the sample code short, I'm `<snip>`ing the code we've already covered. The complete workflow file is
  available at the end of this article.
</Note>

```yaml {filename=".gitlab-ci.yml"} theme={null}
should_we_push:
  stage: setup
  before_script:
    <snip>
  script: |
    pushToPSH="no"
    if [ "${CI_COMMIT_TAG}" == "$(git describe --abbrev=0 --tags)" ]; then
      pushToPSH="yes"
    fi
    
    echo "${pushToPSH}" >> push.txt
```

Now, we check to see if the pushed tag is the most recent from the latest commit in our default branch (`git describe`),
exactly the same as we did in the GitHub workflow. If so, we set the value of `pushToPSH` to `yes` and save that value
in the push.txt that we'll save in our cache.

Last we need to set up the `cache` configuration for the job:

```yaml {filename=".gitlab-ci.yml"} theme={null}
should_we_push:
  stage: setup
  before_script:
    <snip>
  script: |
    <snip>
  cache:
    <<: *global_cache
    policy: push
```

Here I'm referencing the YAML block I set up earlier for cache ( `*global_cache`) which tells the YAML parser to grab
that named section and reuse it. The `<<:` informs the YAML parser that I'm going to add value or override existing ones
to the section.

Since we never want this job to reuse the cache from a previous run, I'll override the
[default](https://docs.gitlab.com/ee/ci/yaml/index.html#cachepolicy) `policy` of `pull-push` to only `push` for this job
(i.e. only upload a cache when the job is complete, never download cache when the job starts).

I'll next define our second job and attach it to the perform stage:

```yaml {filename=".gitlab-ci.yml"} theme={null}
we_should_push:
  stage: perform
```

Since this job will utilize the data stored in the cache, it’s time to go ahead and set up the cache configuration[^4]:

```yaml {filename=".gitlab-ci.yml"} theme={null}
we_should_push:
  stage: perform
  cache:
    <<: *global_cache
    policy: pull
```

Similar to the `should_we_push` job, I'm reusing the cache configuration I created in the default section
(`*global_cache`), but this time I'm changing the policy to `pull`. This ensures the cache will always be downloaded
before the job begins, but never uploads the cache when the job completes.

Before we can build out the rest of the steps, just like we did with GitHub, we'll need to create a couple of repository
variables that we can access in our steps. Make sure you've
[generated a Platform.sh API token](https://docs.upsun.com/administration/cli/api-tokens.html#2-create-an-api-token),
and have the Platform.sh ID of your project.

To locate your project's ID, if you are logged into the
[console](https://console.platform.sh/?_utm_medium=Email&_utm_campaign=2022-09-newsletter&_utm_source=devrelcareers),
you can locate your project's ID under the title of your project, to the right of the project's region when viewing the
project's page:

<img src="https://mintcdn.com/upsun-c9761871/tziXiwEbbKjwbX3l/images/posts/hands-on/guide-to-limit-deployments-to-platformsh-only-with-tags-part-two/limitdeploy2_c2bbc92cba.webp?fit=max&auto=format&n=tziXiwEbbKjwbX3l&q=85&s=77604f122085722c1264601443863f47" alt="Locate project id in console" width="623" height="334" data-path="images/posts/hands-on/guide-to-limit-deployments-to-platformsh-only-with-tags-part-two/limitdeploy2_c2bbc92cba.webp" />

Or to locate your project’s ID from the command line, when in the local copy of your project, you can run the following
command:

```bash {filename="Terminal"} theme={null}
❯ platform project:info id
ropxcgtns2wgy
```

From there, follow the
[GitLab directions](https://docs.gitlab.com/ee/ci/variables/index.html#define-a-cicd-variable-in-the-ui) for adding a
CI/CD variable in the UI, adding one for `PLATFORM_CLI_TOKEN`[^5] and one for `PROJID`, setting the values as appropriate.

<img src="https://mintcdn.com/upsun-c9761871/tziXiwEbbKjwbX3l/images/posts/hands-on/guide-to-limit-deployments-to-platformsh-only-with-tags-part-two/limitdeploy3_5fab53e625.webp?fit=max&auto=format&n=tziXiwEbbKjwbX3l&q=85&s=e759e329e2b649ca6994d1521a617c34" alt="Add CICD variable to GitLab repository" width="433" height="788" data-path="images/posts/hands-on/guide-to-limit-deployments-to-platformsh-only-with-tags-part-two/limitdeploy3_5fab53e625.webp" />

<Note>
  **Note:** [Take a look at the GitLab CI/CD documentation for more information](https://docs.gitlab.com/ee/ci/variables/index.html)
</Note>

And finally, we’ve arrived at the steps for pushing our new code to Platform.sh—if the `should_we_push` job determined
this was a tag we want to deploy to Platform.sh, that is.

```yaml {filename=".gitlab-ci.yml"} theme={null}
we_should_push:
  <snip>
  script: |
    if [ $(cat push.txt | grep yes) ]; then
      echo "setting up the platform.sh cli"
      curl -fsSL https://raw.githubusercontent.com/platformsh/cli/main/installer.sh | bash
      platform project:set-remote "${PROJID}"
      echo "set up a new ssh cert"
      platform ssh-cert:load --new --no-interaction
      pshWholeGitAddress=$(git remote get-url platform --push)
      pshGitAddress=$(TMP=${pshWholeGitAddress#*@};echo ${TMP%:*})
      echo "Adding psh git address ${pshGitAddress} to known hosts"
      ssh-keyscan -t rsa "${pshGitAddress}" >> ~/.ssh/known_hosts
      echo "Pushing tag ${CI_COMMIT_TAG} to Platform.sh..."
      pshDefaultBranch=$(platform project:info default_branch)
      git push platform "refs/tags/${CI_COMMIT_TAG}^{commit}:refs/heads/${pshDefaultBranch}"
    else
      echo "no changes to push. exiting"
    fi
```

Beyond checking to make sure "`yes`" is contained in our cached `push.txt` file, the rest of the steps are identical to
the steps we used in the GitHub Actions article for the `we_should_push` job.

Your `.gitlab-ci.yml` file is now ready to commit to your repository and push it to GitLab. You'll need to follow
whatever workflow path you use to get the file into your production branch, be it pushing directly, if allowed, or
through a pull request process.

### **You’re all set**

Now that the `.gitlab-ci.yml` file is committed into your default branch, any future tags that are pushed to GitLab, or
created on GitLab when creating a release will trigger the pipeline.  If the tag is newer than any other tags (closest
to the current commit), our `we_shou` job will run and push that tag to Platform.sh and deploy your new code base!

[Take a look at the complete GitLab pipeline file](https://gist.github.com/platformsh-devrel/1ee70770c045cbbfe1f11f373e863871).

***

[^1]: They can also be triggered by other pipelines, an API call, on a schedule, etc.

[^2]: Jobs within stages are still executed in parallel.

[^3]: Jobs that are in the same stage then run in parallel.

[^4]: The order of where you define the cache strategy is irrelevant as long as it is a property of the job.

[^5]: You can use a different name for the CI/CD variable but if so, then you will need to add a
    [variables property](https://docs.gitlab.com/ee/ci/yaml/#variables) to the job, mapping `PLATFORMSH_CLI_TOKEN` to the
    name you used.
