Work With Us

Automating Shopify Theme Deployments with GitLab CI/CD

by Matt Cotton
8 min read

Introduction

Shopify development projects often involve multiple team members working on different features simultaneously. Efficient collaboration and continuous integration are crucial for delivering high-quality Shopify stores. To streamline this process, we've developed an automated deployment process that integrates Shopify themes with a GitLab repo by utilising the GitLab CI/CD. 

This is done by defining pipelines in the .gitlab-ci.yml file that executes a shell script which determines how to deploy the branch to a Shopify theme. This could be anything from a feature branch to a production deployment. In this blog post, we'll delve into how this automation works and how you can implement this on your or your clients Shopify stores.

All files referenced below can be found in this gist.

GitLab CI Script

For Shopify projects, the GitLab CI/CD file manages the deployment workflow for various branches, including feature branches, staging, UAT (User Acceptance Testing), and the main/production branch. Let's break down the key stages of the script:

1. Review Stage

review:
  stage: review
  script:
    - echo "Contents of root directory before actions:"
    - ls -la
    - echo "Contents of shopify-deployment before actions:"
    - ls -la shopify-deployment || true
    - shopify version
    - mkdir -p ./shopify-deployment
    - chmod +x ./shopify-deployment-script.sh; ./shopify-deployment-script.sh
    - echo "Contents of shopify-deployment after actions:"
    - cat ./shopify-deployment/deployment-data
    - DYNAMIC_ENVIRONMENT_URL=$(grep -o '"preview_url":"[^}]*' ./shopify-deployment/deployment-data | grep -Eo '(http|https)://[^"]+')
    - echo "DYNAMIC_ENVIRONMENT_URL=$DYNAMIC_ENVIRONMENT_URL" >> deploy.env
  artifacts:
    reports:
      dotenv: deploy.env
  environment:
    name: review/$CI_COMMIT_REF_NAME
    url: $DYNAMIC_ENVIRONMENT_URL
    on_stop: stop_review
  cache:
    <<: *global_cache
  only:
    - branches
  except:
    - staging
    - uat
    - main

The Review stage deploys feature branches to a Shopify development theme. It uses the shopify-deployment-script.sh to deploy updates to a Shopify development theme if this branch hasn’t been deployed before, or updates an existing Shopify development theme if it has. This shell script is explained in detail in the Shopify Deployment Script section below.

The Shopify CLI is used to enable this automated deployment. This means that you will need to have the Shopify CLI installed on your GitLab Runner. It may be possible to install this directly but we are using a Docker container that has the Shopify CLI and all required packages pre-installed. This image is then saved to a GitLab Container Registry. The Dockerfile used to generate this image is available in this gist.

After the deployment occurs in the shopify-deployment-script.sh file the review stage then saves the Shopify development theme URL in GitLab as the App URL. It does this by setting the DYNAMIC_ENVIRONMENT_URL to the preview_url returned by the Shopify CLI. A grep command is used to extract this preview_url from the JSON data in the deployment-data file. Finally, this DYNAMIC_ENVIRONMENT_URL variable is written to the deploy.env file.

GitLab "View App" button example

All of this enables the deployed development theme to be opened directly within a GitLab merge request by clicking the ‘View app” button. Bear in mind that Shopify automatically removes development themes that have been inactive for seven days. Therefore if your review process takes longer than this you will need to rerun the pipeline after seven days to deploy this feature branch to a new Shopify development theme.

2. Staging and UAT Stages

staging:
  stage: staging
  script:
    - echo "Contents of root directory before actions:"
    - ls -la
    - echo "Contents of shopify-deployment before actions:"
    - ls -la shopify-deployment || true
    - shopify version
    - mkdir -p ./shopify-deployment
    - echo "Deploying updates to Shopify Staging theme:"
    - shopify theme push --theme $STAGING_THEME_ID --store $SHOPIFY_FLAG_STORE --password $SHOPIFY_CLI_THEME_TOKEN --json > ./shopify-deployment/deployment-data
    - echo "Contents of shopify-deployment after actions:"
    - cat ./shopify-deployment/deployment-data
    - DYNAMIC_ENVIRONMENT_URL=$(grep -o '"preview_url":"[^}]*' ./shopify-deployment/deployment-data | grep -Eo '(http|https)://[^"]+')
    - echo "DYNAMIC_ENVIRONMENT_URL=$DYNAMIC_ENVIRONMENT_URL" >> deploy.env
  artifacts:
    reports:
      dotenv: deploy.env
  environment:
    name: review/$CI_COMMIT_REF_NAME
    url: $DYNAMIC_ENVIRONMENT_URL
  cache:
    <<: *global_cache
  only:
    - staging
uat:
  stage: uat
  script:
    - echo "Contents of root directory before actions:"
    - ls -la
    - echo "Contents of shopify-deployment before actions:"
    - ls -la shopify-deployment || true
    - shopify version
    - mkdir -p ./shopify-deployment
    - echo "Deploying updates to Shopify UAT theme:"
    - shopify theme push --theme $UAT_THEME_ID --store $SHOPIFY_FLAG_STORE --password $SHOPIFY_CLI_THEME_TOKEN --json > ./shopify-deployment/deployment-data
    - echo "Contents of shopify-deployment after actions:"
    - cat ./shopify-deployment/deployment-data
    - DYNAMIC_ENVIRONMENT_URL=$(grep -o '"preview_url":"[^}]*' ./shopify-deployment/deployment-data | grep -Eo '(http|https)://[^"]+')
    - echo "DYNAMIC_ENVIRONMENT_URL=$DYNAMIC_ENVIRONMENT_URL" >> deploy.env
  artifacts:
    reports:
      dotenv: deploy.env
  environment:
    name: review/$CI_COMMIT_REF_NAME
    url: $DYNAMIC_ENVIRONMENT_URL
  cache:
    <<: *global_cache
  only:
    - uat

Similar to the Review stage, these stages deploy updates to Shopify staging and UAT themes, respectively. As with the Review stage it does this by using the Shopify CLI. Unlike the Review stage, this doesn’t require using the shopify-deployment-script.sh script though as the staging and UAT Shopify theme are created as permanent themes in the Shopify theme library. This has been implemented this way so that the Staging and UAT themes will not be automatically removed by Shopify after seven days.

The Staging and UAT themes need to be created in Shopify by duplicating the main theme and renaming it. Thereby adding these duplicate themes to the theme library. Then the theme IDs for both of these themes need to be added to the GitLab repo under Settings → CI/CD → Variables. In the example .gitlab-ci.yml file these variables are STAGING_THEME_ID and UAT_THEME_ID. These steps only have to be completed once when first adding this functionality to the project.

3. Generate Version Stage

generate_version:
  stage: prep_deploy
  image: alpine:latest
  rules:
    - if: $CI_COMMIT_TAG
      when: never
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  script:
    - echo "RELEASE_TAG = $(date '+%Y%m%d.%H%M')" >> variables.env
  artifacts:
    reports:
      dotenv: variables.env
    expire_in: 1 week

This stage is the first stage in a production deployment. It generates a version tag for deployments of the default branch (e.g. main) if a commit has not already been tagged. This allows for improved version tracking for the live theme.

4. Deploy Stage

deploy:
  stage: deploy
  script:
    - echo "Contents of root directory before actions:"
    - ls -la
    - echo "Contents of shopify-deployment before actions:"
    - ls -la shopify-deployment || true
    - shopify version
    - mkdir -p ./shopify-deployment
    - shopify theme push --store $SHOPIFY_FLAG_STORE --password $SHOPIFY_CLI_THEME_TOKEN --live --allow-live --json > ./shopify-deployment/deployment-data
    - echo "Contents of shopify-deployment after actions:"
    - cat ./shopify-deployment/deployment-data
    - ID=$(grep -o '"id":[^"]*' ./shopify-deployment/deployment-data | grep -o '[^:]*$' | grep -o '^[^,]*')
    - PREVIEW_URL=$(grep -o '"preview_url":"[^}]*' ./shopify-deployment/deployment-data | grep -Eo '(http|https)://[^"]+')
    - DYNAMIC_ENVIRONMENT_URL=${PREVIEW_URL}?preview_theme_id=${ID}
    - echo "DYNAMIC_ENVIRONMENT_URL=$DYNAMIC_ENVIRONMENT_URL" >> deploy.env
  artifacts:
    reports:
      dotenv: deploy.env
  environment:
    name: live
    url: $DYNAMIC_ENVIRONMENT_URL
  cache:
    <<: *global_cache
  only:
    - main

The Deploy stage is the second stage in production deployment and handles the actual deployment of the live theme to Shopify. As with the Review, Staging and UAT stages, it pushes updates to the theme using the Shopify CLI.

It is very important to note that any changes made on the live store through the Shopify theme editor or code editor that are not contained in the code base may be overwritten by this deployment stage. It is recommended to always manually run the shopify theme pull command to pull and commit the latest changes from the live store before merging and pushing your changes to the default (e.g. main) branch. 

5. Release Stage

release:
  stage: finalise_deploy
  image: registry.gitlab.com/gitlab-org/release-cli:latest
  rules:
    - if: $CI_COMMIT_TAG
      when: never
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  script:
    - echo "Running release $RELEASE_TAG..."
  release:
    name: 'Release $RELEASE_TAG'
    tag_name: '$RELEASE_TAG'
    description: '$RELEASE_TAG'
    ref: '$CI_COMMIT_SHA'
  artifacts:
    expire_in: 1 week

This stage is the third stage in production deployment and is triggered when a new tag is created. It runs the release CLI to finalise the deployment and create a release tag.

6. Stop Review Stage

stop_review:
  stage: cleanup
  script:
    - ./teardown-environment
    - dep ci:destroy cd --revision=$CI_COMMIT_SHA
  when: manual
  environment:
    name: review/$CI_COMMIT_REF_NAME
    action: stop
  only:
    - branches
  except:
    - main
    - uat
    - staging

This manual stage is used to tear down Review environments for feature branches.

Shopify Deployment Script

# IMPORTANT: Deployment results files (e.g. deployment-data) cannot use the file type '.json' as Shopify will interpret these as being theme files and the theme deployment will fail

# When the deployment-data file already exisits (loaded from the cache) then try to redeploy to this theme
if [ -f ./shopify-deployment/deployment-data ]
then
    # Get the theme id from the deployment-data json file
    ID="$(grep -o '"id":[^"]*' ./shopify-deployment/deployment-data | grep -o '[^:]*$' | grep -o '^[^,]*')"
    echo "Deploying updates to Shopify development theme with id: $ID"
    echo "shopify theme push --theme $ID --store $SHOPIFY_FLAG_STORE --password $SHOPIFY_CLI_THEME_TOKEN > ./shopify-deployment/result"
    shopify theme push --theme $ID --store $SHOPIFY_FLAG_STORE --password $SHOPIFY_CLI_THEME_TOKEN > ./shopify-deployment/result

    # If the redeployment output includes the word 'Error' then deploy a development new theme
    ERROR="$(grep -w "Error" ./shopify-deployment/result)"
    if [ ! -z "$ERROR" ]
    then
        echo "WARNING: There was an error with the deployemnt. This is probably due to this Shopify development theme being inactive for more than 7 days. The theme will now be redeployed. This will result in the Shopify development theme having a new id and url."
        echo "shopify theme push --development --store $SHOPIFY_FLAG_STORE --password $SHOPIFY_CLI_THEME_TOKEN --json > ./shopify-deployment/deployment-data"
        shopify theme push --development --store $SHOPIFY_FLAG_STORE --password $SHOPIFY_CLI_THEME_TOKEN --json > ./shopify-deployment/deployment-data
    fi
    rm -f ./shopify-deployment/result
# When the deployment-data file doesn't exisit deploy a new development theme
else
    echo "Deploying a new Shopify development theme:"
    echo "shopify theme push --development --store $SHOPIFY_FLAG_STORE --password $SHOPIFY_CLI_THEME_TOKEN --json > ./shopify-deployment/deployment-data"
    shopify theme push --development --store $SHOPIFY_FLAG_STORE --password $SHOPIFY_CLI_THEME_TOKEN --json > ./shopify-deployment/deployment-data
fi

The Shopify deployment shell script (shopify-deployment-script.sh) complements the .gitlab-ci.yml file by handling the deployment specifics for feature branches. This script is used by the Review stage and is required as there is more complex logic required for a feature branch deployment than a staging, UAT or live deployment. This more complex logic is slightly easier to write and understand in a shell file than if written directly in the .gitlab-ci.yml file. Here's a summary of the functionality of this shell script:

As the Shopify CLI returns fairly verbose command line messages, checking if a deployment has failed has been implemented in a fairly unusual way. The deployment response is redirected to a result file. A grep command is then used to determine if the result file contains the word "Error". If it does then the script triggers the development of a new theme. This will result in the Shopify development theme having a new theme ID and URL. As mentioned in 1. Review Stage this new URL will be set as the DYNAMIC_ENVIRONMENT_URL. This means that the ‘View app’ link in the MR is automatically updated to this new development theme URL.

Something you may have noticed when looking at this shell script and the .gitlab-ci.yml file is that the JSON output of the shopify theme push commands is not redirected into a file with a file extension of .json. This is because the Shopify CLI will interpret these as theme files and attempt to deploy these files. As they will contain data that doesn't match the expected format of any Shopify .json file the theme deployment will fail. To get around this issue these files have not been given any file extension.

Setup of GitLab CI/CD Variables

For the shopify-deployment-script.sh and .gitlab-ci.yml functionality to work the following variables need to be added to the GitLab repo under Settings → CI/CD → Variables.

GitLab CI/CD Variables example

Shopify Store Name

The store name variable is required to tell the Shopify CLI instance that is running in your GitLab Runner which Shopify store it is communicating with. This variable can have any key, but for the example files in this gist use SHOPIFY_FLAG_STORE.

Theme Access Password

The theme Access password is needed to authorise the Shopify CLI instance that is running on your GitLab Runner to communicate with your store. This is created by:

For security, I recommend masking this variable so that it is not displayed in your pipeline job logs.

Staging and UAT Theme IDs

If your development process requires you to have a Staging and/or UAT store then these themes can be created and deployed by following the steps below.

Summary

Has this post struck a chord with you? Have you been struggling with manual Shopify theme deployments? We can help! Contact us for a free consultation to discuss how to automate your workflows and streamline your development processes.

Written by Matt Cotton
Senior Frontend Software Engineer