> ## 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 with tags: part three

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-04-12T10:00:00+00:00", image: "/images/posts/hands-on/guide-to-limit-deployments-to-platformsh-only-with-tags-part-three/Limit_deployments_to_psh-three.webp" }} />

In parts [one](/posts/hands-on/guide-to-limit-deployments-to-platformsh-only-with-tags-part-one) and
[two](/posts/hands-on/guide-to-limit-deployments-to-platformsh-only-with-tags-part-two) of this series, we covered how to limit
deployments to Platform.sh when a tag is pushed or created. Focusing first on using GitHub and the GitHub Actions
platform, and then progressing onto Gitlab and pipelines to accomplish this goal. But in the spirit of being a
[polyglot PaaS](https://platform.sh/marketplace/), let’s look at how we can accomplish the same goal—limiting deployment
to Platform.sh only when adding a git tag—using Bitbucket and their CI/CD system.

### **The assumptions**

Just as in the previous articles, for this article and the steps detailed within it, I will assume that you have:

* A Bitbucket account and repository with working code
* Administrative rights on the Bitbucket repository (so you can add [CI/CD variables](https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/))
* A Platform.sh account and a project for your code base with working code
* A default branch in BitBucket 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 BitBucket.

### **The `bitbucket-pipelines.yml` file**

While GitHub has a `.github/workflows` directory where any `*.yaml` file can be a workflow definition, Bitbucket has
[a similar approach to GitLab](https://docs.gitlab.com/ee/ci/#the-gitlab-ciyml-file) and consolidates your
[CI/CD configurations into a single file](https://support.atlassian.com/bitbucket-cloud/docs/bitbucket-pipelines-configuration-reference/)
named `bitbucket-pipelines.yml`. This file must be in the root of your repository, have a .yml extension, and contain
everything needed to run your Bitbucket [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-three/limit_post_3_1_e84f386d71.webp?fit=max&auto=format&n=tziXiwEbbKjwbX3l&q=85&s=7e451fdfe2263645f329f8683051927c" alt="Bitbucket bitbucket-pipelines.yml pipeline file" width="484" height="254" data-path="images/posts/hands-on/guide-to-limit-deployments-to-platformsh-only-with-tags-part-three/limit_post_3_1_e84f386d71.webp" />

### **Start your engines!**

Similar to GitHub Events, Bitbucket pipelines are triggered by
[start conditions](https://support.atlassian.com/bitbucket-cloud/docs/pipeline-start-conditions/), which allows you to
control the scenario when a pipeline should be triggered—and lucky for us, one of those conditions is focused on
[start conditions involving tags](https://support.atlassian.com/bitbucket-cloud/docs/pipeline-start-conditions/#Tags).
In the tags property, we can define all tag-specific build pipelines, where the name or expression in this section is
matched against tags in your repository. Glob patterns can also be used for matching tags. Following along with our
previous examples, I'll use a wildcard for all tags[^1].

```yaml {filename="bitbucket-pipelines.yaml"} theme={null}
pipelines:
  tags:
    '*':
```

### **The steps to limiting deployments**

Now that we have our event defined, we need to define our first `step` and despite sharing the same name, it is not at
all like GitHub's `step`. Instead, it is much closer to a `job` in GitHub as each BitBucket `step` in a pipeline will
start a separate Docker container to run the commands configured in the `script` option. Much like GitLab, Bitbucket
will clone your repository into the runner's file system. However, Bitbucket limits the clone to the last 50 commits.
For purposes of this tutorial, I will set the depth to full to ensure we have the commit associated with the tag in this
step.

```yaml {filename="bitbucket-pipelines.yaml"} theme={null}
pipelines:
  tags:
    '*':
      - step:
        clone:
          depth: full
```

Next, we need to define the series of steps we want to perform in this `step`. In Bitbucket, these go under the `script`
property, which is very similar to `steps` in GitHub: each one executes a shell command or script and is executed in
sequential order. The first step we'll execute is to make sure we have all the tags in our repository:

```yaml {filename="bitbucket-pipelines.yaml"} theme={null}
pipelines:
  tags:
    '*':
      - step:
        clone:
          depth: full
        name: Push tag to Platform.sh
        script:
          - git fetch --tags
```

I've also added an optional `name` property to the step to make it more easily identifiable when viewing my pipeline in
Bitbucket's pipelines interface.

Now that we have our repository checked out into our runner's workspace, we can check to see if the tag that triggered
this pipeline is the tag nearest to the most recent commit in the production branch. To do that, we'll use the Git
command [describe](https://git-scm.com/docs/git-describe) and will compare this to the predefined CI/CD variable
`BITBUCKET_TAG` that contains the commit tag name that triggered the pipeline. This allows us to ensure we're not
pushing a tag that has been added to an old commit, or on another branch and pushing unnecessary commits over to
Platform.sh.

<Note>
  **Please note**: to keep the sample code short, I'm `<snip>`ing the code we've already covered. The
  [complete workflow file](https://gist.github.com/platformsh-devrel/9662f8f82c18ec0c2168185f9f8ccd1d) is available at the
  [end of this article](https://platform.sh/blog/guide-to-limit-deployments-to-platformsh-only-with-tags-part-three/#file).
  I've also unindented the lines and reduced the font size for it to fit this page.
</Note>

```yaml {filename="bitbucket-pipelines.yaml"} theme={null}
<snip>
script:
  - git fetch --tags
  - |
    if [ "${BITBUCKET_TAG}" == "$(git describe --abbrev=0 --tags)" ]; then
      echo "We need to push this tag. Setting up the Platform CLI Tool..."
    else
      echo "Not a tag, or not the latest, so nothing to push. Exiting."
    fi
```

Before we can build out the rest of the steps—just like we did with GitHub and GitLab—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 ID of your Platform.sh project handy.

There are two ways you can locate your project's ID. The first is under the title of your project and to the right of
the project's region when viewing the project's page in the Platform.sh
[console](https://console.platform.sh/?_utm_medium=Email&_utm_campaign=2022-09-newsletter&_utm_source=devrelcareers):

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

Or 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
[Bitbucket directions](https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/#Repository-variables)
for adding a repository variable in the UI, adding one for `PLATFORMSH_CLI_TOKEN`[^2] and one for `PROJID`, setting the
values as appropriate.

From here the rest of the steps are almost identical to the steps we used in the GitHub Actions and Gitlab pipelines
articles:

* Set up the Platform.sh CLI tool
* Associate our Project ID with the repository in our runner workspace
* Determine the default branch in our Platform.sh project
* Push the tag to Platform.sh targeting the default branch

```yaml {filename="bitbucket-pipelines.yaml"} theme={null}
- |
  if [ "${BITBUCKET_TAG}" == "$(git describe --abbrev=0 --tags)" ]; then
    echo "We need to push this tag. Setting up the Platform CLI Tool..."
    curl -fsSL https://raw.githubusercontent.com/platformsh/cli/main/installer.sh | bash
    platform project:set-remote "${PROJID}"
    echo "Pushing tag ${BITBUCKET_TAG} to Platform.sh..."
    pshDefaultBranch=$(platform project:info default_branch)
    platform push "refs/tags/${BITBUCKET_TAG}^{commit}" --target "${pshDefaultBranch}"
  else
    echo "Not a tag, or not the latest, so nothing to push. Exiting."
  fi
```

Your `bitbucket-pipelines.yml` file is now ready to commit to your repository and push to Bitbucket. 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.

### **All set to deploy**

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

### **Wait a second, something’s different!**

Eagle-eyed readers may have noticed that the specific steps taken during the push to Platform.sh are distinctly
different from what I showed in parts one and two for Github and Gitlab, respectively.  While building out the pipeline
on Bitbucket, I discovered that the version of SSH on the public runner is 6.6.1p1 which is roughly 10 years old. As it
turns out, to use the SSH certificates our CLI supports, it needs version 7.3 of OpenSSH or above. Despite numerous
attempts, I was unable to find a way to easily update the runner to a newer version and use that version between
individual script calls.

While trying to resolve the issue, the amazing [Patrick Dawkins](https://github.com/pjcdawkins), developer and
maintainer of the Platform.sh CLI tool pointed out that the CLI can
[push](https://docs.platform.sh/administration/cli/reference.html#environmentpush) *any* Git reference, commit, or tag
to Platform.sh, not just a specific branch.

Despite using the CLI for over 5 years, I never noticed it would actually accept a source, always assuming it would push
the current commit of the branch you are on which it defaults to doing if no specific source is given. This method has
one big advantage over the methods I showed previously: you don't have to set up the SSH certificates as it will handle
authentication automatically for you via the `PLATFORMSH_CLI_TOKEN` we set up. This means we don't need to determine the
Git address, create the SSH certificates, or add the Platform.sh endpoint to the known\_hosts file, giving us a much
more streamlined process when performing the push.

It’s true that no matter how much you know or how deeply you know something, there's *always* more to learn!

### **Complete `bitbucket-pipelines.yml` file**

The complete Bitbucket pipeline file is [available on GitHub as a gist](https://gist.github.com/platformsh-devrel/9662f8f82c18ec0c2168185f9f8ccd1d).

***

[^1]: Free accounts on bitbucket are limited to 50 build minutes per month. If you regularly create tags that you do not
    want to trigger a push to Platform.sh, I strongly suggest using a pattern to limit how often your pipeline is triggered.

[^2]: You can use a different name for the CI/CD variable but if so, then you will need to add a
    [variables property](https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/#Secured-variables) to the
    job, mapping `PLATFORMSH_CLI_TOKEN` to the name you used.
