Skip to content

Set up CI/CD from Git

On this page we will show you how to push changes from Git to UbiOps for a CI/CD workflow. More specifically, we will show you how to set up a GitLab CI/CD Pipeline or a GitHub Action that allows you to continuously push your code to a UbiOps deployment. Every time you push changes to your main branch, your deployment is updated with a new deployment version.

The CI/CD script in this how-to includes the creation of an environment, a deployment, and a new deployment version. From that, this script can serve as a starting point for building a CI/CD workflow that features any operation with the UbiOps API.

If you want to route requests from the request endpoint of your deployment to the newly created version, make sure that you update your new deployment version as the default version of your deployment.

Requirements

  • A UbiOps account (a free trial account can be created here)

  • A GitLab or GitHub repository

Creating the API token

To deploy on UbiOps, we need to create an API Token. Start by going to Permissions in the sidebar, then navigate to the API tokens tab. Here you can click the [+]Add button to create a new API token. Give the new token a name, for instance "Git-token".

User & permissions

You will be prompted to add roles to the token. Click on the [+]Assign roles to user button and select deployment-editor from the dropdown menu. You can set the role level on Deployment and select the specific deployment you want to control with Git CI in the Applied to field. Lastly, copy the API token and save it in a secure spot. You will not be able to retrieve this token again.

Basic CI workflow

Now that we have our token, we can set up the Git CI. We will first cover simple deployment scripts for GitLab and GitHub, and after that a more advanced deployment script.

GitLab

Go to the GitLab repository that contains the code you want to push to UbiOps. To make sure GitLab has the right permissions to push to UbiOps, we need to add the API token we created in the previous step as an environment variable to GitLab. You can do so by navigating to Settings, then to CI/CD, and then going to Variables to add a new variable. Create a visible or masked variable with the name "UBIOPS_API_TOKEN".

Your API token looks like Token abcd123.... When you create a masked variable in GitLab, spaces are not allowed. When you create a visible variable, you are allowed to include spaces. The bearer of this API token is added in the CI/CD script, so that both masked and visible variables can be added. Therefore, add the API token without the Token bearer. For more information on GitLab variables see here.

Now that the API token is added as a variable, make a gitlab-ci.yml in your repository. This is the deployment script that pushes your code from the GitLab repository to UbiOps. Below is an example of a gitlab-ci.yml file.

stages:
  - deploy

deploy:
  stage: deploy
  image: python:3.11
  variables:
    DEPLOYMENT_NAME: <your-deployment-name>
    ENVIRONMENT_NAME: <your-environment-name>
    # Choose an available UbiOps base environment, in the WebApp go to Environments > Base environments
    BASE_ENVIRONMENT: ubiops-ubuntu24-04-python3-13
    # Add the directory path of your deployment.py script
    ENVIRONMENT_DIR_PATH: ./path/to/environment-package/
    DEPLOYMENT_DIR_PATH: ./path/to/deployment-package/
    DEPLOYMENT_CONFIG_PATH: ./path/to/deployment-config.yaml
  before_script:
    - apt-get update -qq && apt-get install -y -qq jq
    - pip install ubiops-cli
    # Authorize using the API token 
    - ubiops signin --token -p "Token ${UBIOPS_API_TOKEN}"
  script:
    # Construct the version name from the Commit SHA
    - VERSION_NAME="v-${CI_COMMIT_SHA:0:7}"
    # Deploy an environment and wait for it to build
    - ubiops environments get ${ENVIRONMENT_NAME} -q || ubiops environments create ${ENVIRONMENT_NAME}
    - ubiops environments deploy ${ENVIRONMENT_NAME} -dir ${ENVIRONMENT_DIR_PATH} --base_environment_name ${BASE_ENVIRONMENT}
    - ubiops environments wait ${ENVIRONMENT_NAME}
    # Deploy a new deployment package
    - ubiops deployments get ${DEPLOYMENT_NAME} || ubiops deployments create ${DEPLOYMENT_NAME} -f ${DEPLOYMENT_CONFIG_PATH}
    - ubiops deployments deploy ${DEPLOYMENT_NAME} -v ${VERSION_NAME} 
      -dir ${DEPLOYMENT_DIR_PATH} --environment ${ENVIRONMENT_NAME}
    - ubiops deployment_versions wait ${VERSION_NAME} -d ${UBIOPS_DEPLOYMENT_NAME} --stream_logs
    - ubiops deployments update ${DEPLOYMENT_NAME} --default_version ${VERSION_NAME}
  only:
    - main

The script deploys the deployment package in your repository as a new version of the given deployment every time new commits are pushed to the main branch. The version name is constructed automatically from the first seven characters of the Commit-SHA. We create a new deployment version using the UbiOps CLI.

To make this script work, you need to configure some variables. At minimum, you need to provide the name of your deployment, DEPLOYMENT_NAME, and your environment, ENVIRONMENT_NAME, in lowercase letters and with dashes instead of underscores. Also provide the path to the deployment package in your repository, DIR_PATH. Use path ./ if your deployment file (the deployment.py) is in the root of your repository. The script part of .gitlab-ci.yml can be expanded to serve your needs. You can call any API endpoint of UbiOps with the function of ubiops-cli, as long as your used token permits it.

When you've added the .gitlab-ci.yaml file to your repository and configured your variables, you are ready to go!

GitHub

Go to the GitHub repository that contains the code you want to push to UbiOps. To make sure GitHub has the permission to push to UbiOps, we need to add an API token as a repository secret. You can add this token in GitHub by navigating to Settings > Secrets and variables > Actions, and then click the button New repository secret. Name the variable UBIOPS_API_TOKEN and paste the saved token as value in the format abcd123..., without the Token bearer, and click Add secret. The bearer will be added in the script. For more information on repository secrets in GitHub see here.

Now that the token is added as a secret to your repository, we can make a new GitHub Actions script to push code from our GitHub repository to UbiOps. Go to Actions and click Create a new workflow. You will be redirected to .github/workflows/name-of-your-workflow.yml. Below is the .yml we need.

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      DEPLOYMENT_NAME: <your-deployment-name>
      ENVIRONMENT_NAME: <your-environment-name>
      UBIOPS_API_TOKEN: ${{ secrets.UBIOPS_API_TOKEN }}
      # Choose an available UbiOps base environment, in the WebApp go to Environments > Base environments
      BASE_ENVIRONMENT: ubiops-ubuntu24-04-python3-13
      # Add the directory path of your deployment.py script
      ENVIRONMENT_DIR_PATH: ./path/to/environment-package/
      DEPLOYMENT_DIR_PATH: ./path/to/deployment-package/
      DEPLOYMENT_CONFIG_PATH: ./path/to/deployment-config.yaml

    steps:
      - uses: actions/checkout@v2

      - name: Set up Python 3.11
        uses: actions/setup-python@v2
        with:
          python-version: 3.11

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install ubiops-cli

      # Authorize using an API token
      - name: Sign in
        run: ubiops signin --token -p "Token $UBIOPS_API_TOKEN"

      - name: Create/update environment
        run: |
          VERSION_NAME="v-${GITHUB_SHA:0:7}"
          ubiops environments get "$ENVIRONMENT_NAME" -q || ubiops environments \
          create "$ENVIRONMENT_NAME"
          ubiops environments deploy "$ENVIRONMENT_NAME" -dir "$ENVIRONMENT_DIR_PATH" \
          --base_environment_name "$BASE_ENVIRONMENT"
          ubiops environments wait "$ENVIRONMENT_NAME"

      # Deploy your deployment.py as a new deployment version
      - name: Deploy
        run: |
          VERSION_NAME="v-${GITHUB_SHA:0:7}"
          ubiops deployments get "$DEPLOYMENT_NAME" -q || ubiops deployments \
          create "$DEPLOYMENT_NAME" -f "$DEPLOYMENT_CONFIG_PATH"
          ubiops deployments deploy "$DEPLOYMENT_NAME" -v "$VERSION_NAME" \
          -dir "$DEPLOYMENT_DIR_PATH" --environment "$ENVIRONMENT_NAME"
          ubiops deployment_versions wait "$VERSION_NAME" -d "$UBIOPS_DEPLOYMENT_NAME" --stream_logs
          ubiops deployments update "$DEPLOYMENT_NAME" --default_version "$VERSION_NAME"

The script deploys the deployment package in your repository as a new version of the given deployment every time new commits are pushed to the main branch. The version name is constructed automatically from the first seven characters of the Commit-SHA. We create a new deployment version using the UbiOps CLI. Inside the script part of this .yaml file you can call any function of the CLI, as long as your used token permits it.

To make this script work, you need _to configure some variables. At minimum, you need to provide the name of you deployment, DEPLOYMENT_NAME, your environment, ENVIRONMENT_NAME, and path of the deployment package in your repository, DIR_PATH. Use path ./ if your deployment file (the deployment.py) is in the root of your repository.

When you've added the .yml file to your repository and configured your variables you are ready to go!

Extended CI workflow

Aside from the basic examples for setting up a workflows with GitHub Actions or GitLab CI Pipelines, a more complete workflow is also included in this how-to, that can be used with both GitLab and GitHub. This more advanced setup contains few files aside from the CI workflow we created previously: a deploy.sh script with deployment instructions and a config.env where all environment variables are configured. In this setup, the workflow lives in the in deploy.sh script, which is triggered by .gitlab-ci.yml when ran in a GitLab repository and by .github/workflows/deploy.yml when ran in a GitHub repository.

Besides updating an environment, deployment and deployment version, this extended script also adds environment variables, scales down old deployment versions, uses git diff to update environments, and creates/updates a UbiOps service for the deployment.

First, let's look at how the workflow files are set up to deploy with the deploy.sh script.

GitLab

Since we will move our deployment logic to the deploy.sh script, our .gitlab-ci.yml becomes shorter. The only thing happening in .gitlab-ci.yml is that the runner is being prepared: Python and the necessary packages are installed.

deploy:
  image: python:3.13
  variables:
    GIT_DEPTH: 0
    COMMIT_SHA: $CI_COMMIT_SHA
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_PIPELINE_SOURCE == "web" 
  before_script:
    - apt-get update -qq && apt-get install -y -qq jq
    - pip install --upgrade ubiops-cli
    - git config --global --add safe.directory "$CI_PROJECT_DIR" 
  script:
    - bash deploy.sh

GitHub

The GitHub script also calls the deploy.sh script.

name: Deploy vLLM to UbiOps

on:
  push:
    branches: [main]
  workflow_dispatch: {}

permissions:
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      COMMIT_SHA: ${{ github.sha }}
      UBIOPS_API_TOKEN: ${{ secrets.UBIOPS_API_TOKEN }}
      SECRET_HF_TOKEN: ${{ secrets.SECRET_HF_TOKEN }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-python@v5
        with:
          python-version: '3.13'
      - run: pip install --upgrade ubiops-cli
      - run: bash deploy.sh

Unlike GitLab, environment variables are not being added to the CI runtime of GitHub automatically, so they need to be added manually into the .github/workflows/deployment.yml like shown below. It is important to add the SECRET_ prefix to the environment variable name, since this will be used later to decide if the environment variable should be added to the UbiOps deployment as a secret environment variable or not.

    env:
      UBIOPS_API_TOKEN: ${{ secrets.UBIOPS_API_TOKEN }}
      SECRET_HF_TOKEN: ${{ secrets.SECRET_HF_TOKEN }}

Deployment script

The CI workflow logic is processed in the runners when calling the deploy.sh script. In this script, environment variables are called from a separate configuration file config.env in the root directory. The configuration for the UbiOps objects (settings for the deployment, the deployment version, the environment, and the service) are all stored in separate files, in the folder /config. Make sure that all the environment variables that are passed in the config.env are present in the configuration files in the /config folder in the repository.

Like in the first example, the UbiOps CLI is used to deploy and the deployment version tag is constructed from the first seven characters of the Commit-SHA. The GitLab and GitHub workflows pass this SHA to the script in the COMMIT_SHA environment variable.

The Token bearer of the UbiOps API token will be added in the deploy.sh script, so for both GitHub and GitLab, add only the token string without the bearer as the UBIOPS_API_TOKEN secret.

UbiOps environment are stored in an image registry on UbiOps for future use. Rebuilding an environment with the exact same libraries and pushing it to UbiOps is only needed when the dependencies have been changed. This script uses git diff logic to check if changes were made to the environment files since the commit the environment was last built from.

When creating the environment and deployment, environment variables are added to the deployment so that they can be used during runtime. After a new deployment version is created, a UbiOps service is created or updated. As a last step, all the old running versions are scaled down.

Place this deploy.sh script in the root of the repository. If you want to store the file in another location, adjust in the gitlab-ci.yml and .github/workflows/deploy.yml accordingly.

#!/usr/bin/env bash
set -euo pipefail
export PS4='+ ${BASH_SOURCE##*/}:${LINENO}: '
set -x

source config.env

## Are the following environment variables set?
: "${UBIOPS_API_TOKEN:?is not set}" "${UBIOPS_API_HOST:?is not set}" \
  "${UBIOPS_PROJECT_NAME:?is not set}" "${UBIOPS_DEPLOYMENT_NAME:?is not set}" \
  "${UBIOPS_ENVIRONMENT_NAME:?is not set}" "${UBIOPS_SERVICE_NAME:?is not set}" \
  "${COMMIT_SHA:?is not set}"

## Name the version after the commit
export VERSION_NAME="v${COMMIT_SHA:0:7}"

## Fill in all the config/*.yaml files with the environment variables
manifests="$(mktemp -d)"
trap 'rm -rf "$manifests"' EXIT
python3 - "$manifests" config/*.yaml <<'PY'
import os, pathlib, string, sys
out, *sources = sys.argv[1:]
for src in map(pathlib.Path, sources):
    (pathlib.Path(out) / src.name).write_text(string.Template(src.read_text()).substitute(os.environ))
PY

## Signin to UbiOps 
ubiops signin --token -p "Token $UBIOPS_API_TOKEN" --api "$UBIOPS_API_HOST"
ubiops current_project set "$UBIOPS_PROJECT_NAME"

## Find the commit the environment was last built from (its newest available tag)
PREV_ENV_TAG=$(ubiops environment_tags list -e "$UBIOPS_ENVIRONMENT_NAME" -fmt json 2>/dev/null | python3 -c '
import json, sys
try:
    tags = [t for t in json.load(sys.stdin) if t["status"] == "available"]
except ValueError:
    tags = []
print(max(tags, key=lambda t: t["creation_date"])["name"] if tags else "")
') || true

## Rebuild if there is no previous build, or the environment files changed since it
ENV_TAG="$PREV_ENV_TAG"
if [ -z "$PREV_ENV_TAG" ] || ! git diff --quiet "$PREV_ENV_TAG" -- environment config/environment.yaml 2>/dev/null; then
  ENV_TAG="$COMMIT_SHA"
  ubiops environments deploy "$UBIOPS_ENVIRONMENT_NAME" -tag "$ENV_TAG" -dir environment \
    -f "$manifests/environment.yaml" --assume_yes
  ubiops environments wait "$UBIOPS_ENVIRONMENT_NAME" -tag "$ENV_TAG" -t 1800 --stream_logs
fi

## Create or update your deployment 
ubiops deployments create "$UBIOPS_DEPLOYMENT_NAME" -f "$manifests/deployment.yaml" --overwrite

## Add the environment variables to the deployment
for n in "${!VAR_@}"; do
  ubiops env create "${n#VAR_}" --value "${!n}" -d "$UBIOPS_DEPLOYMENT_NAME" --overwrite
done
for n in "${!SECRET_@}"; do
  ubiops env create "${n#SECRET_}" --value "${!n}" -d "$UBIOPS_DEPLOYMENT_NAME" --overwrite --secret
done

## Create a new deployment version
ubiops deployments deploy "$UBIOPS_DEPLOYMENT_NAME" -v "$VERSION_NAME" -e "$UBIOPS_ENVIRONMENT_NAME" --tag "$ENV_TAG" \
  -dir deployment -f "$manifests/version.yaml" --overwrite --assume_yes
ubiops deployment_versions wait "$VERSION_NAME" -d "$UBIOPS_DEPLOYMENT_NAME" -t 900 --stream_logs
ubiops deployments update "$UBIOPS_DEPLOYMENT_NAME" --default_version "$VERSION_NAME"

## Create/update a service of the deployment version
ubiops services create "$UBIOPS_SERVICE_NAME" -f "$manifests/service.yaml" --overwrite 

## Scale down all deployment versions
stale="$(ubiops deployment_versions list -d "$UBIOPS_DEPLOYMENT_NAME" -fmt json | python3 -c '
import json, os, sys
keep = os.environ["VERSION_NAME"]
for v in json.load(sys.stdin):
    if v["version"] != keep and (v["minimum_instances"] or v["maximum_instances"]):
        print(v["version"])
')"

for v in $stale; do
  echo "Scaling down $v"
  ubiops deployment_versions update "$v" -d "$UBIOPS_DEPLOYMENT_NAME" -min 0 -max 0 -q
done

Environment variables

Besides the deploy.sh script and a script that initiates the CI workflows on GitLab or GitHub, a separate file for the environment variables is also needed in this example. This allows the user to store and edit all your configurable variables in one place. Secret environment variables do have to be added to GitHub or GitLab nonetheless.

There are three types of environment variables in this example CI workflow:

  • UBIOPS: Environment variables that are used in the CI script to deploy UbiOps objects with the right names and settings, these are not added to the UbiOps deployment runtime but are only used in the GitLab/GitHub runner. These variables start with UBIOPS_ and are declared in the config.env file. Make sure that the environment variables that you pass in the config.env are provided by the

  • VAR: Non-secret environment variables that are added to the UbiOps deployment to be used there in UbiOps runtime. These variables start with VAR_ and are declared in the config.env. These environment variables are added to the deployment to be used in the deployment runtime, by the deployment.py code.

  • SECRET: Secret environment variables that are added to the UbiOps deployment to be used there in UbiOps runtime. These variables start with SECRET_ and are not declared in the config.env but in GitHub and GitLab.

export UBIOPS_API_HOST="${UBIOPS_API_HOST:-https://api.ubiops.com/v2.1/}"
export UBIOPS_PROJECT_NAME="${UBIOPS_PROJECT_NAME:-name}"

export UBIOPS_DEPLOYMENT_NAME="${UBIOPS_DEPLOYMENT_NAME:-name}"
export UBIOPS_ENVIRONMENT_NAME="${UBIOPS_ENVIRONMENT_NAME:-name}"
export UBIOPS_SERVICE_NAME="${UBIOPS_SERVICE_NAME:-name}"

export UBIOPS_BASE_ENVIRONMENT="${UBIOPS_BASE_ENVIRONMENT:-ubiops-ubuntu24-04-python3-13}"
export UBIOPS_INSTANCE_TYPE_GROUP_NAME="${UBIOPS_INSTANCE_TYPE_GROUP_NAME:-1024 MB + 0.25 vCPU}"
export UBIOPS_MINIMUM_INSTANCES="${UBIOPS_MINIMUM_INSTANCES:-0}"
export UBIOPS_MAXIMUM_INSTANCES="${UBIOPS_MAXIMUM_INSTANCES:-1}"
export UBIOPS_REQUEST_RETENTION_MODE="${UBIOPS_REQUEST_RETENTION_MODE:-full}"

export VAR_SERVICE_PORT="${VAR_SERVICE_PORT:-8000}"

## Add these environment variables as secrets / variables on GitHub or GitLab
## UBIOPS_API_TOKEN
## SECRET_HF_TOKEN

The VAR_ and SECRET_ prefixes allow to distinguish between the environment variables that should be added to the UbiOps deployments as secrets or not. For both, the prefixes are dropped when they are added. The environment variable VAR_SERVICE_PORT will be added to the UbiOps ad a non-secret environment variable called SERVICE_PORT. There it can be used in the runtime of the deployment. If SECRET_HF_TOKEN is added to GitLab or GitHub, it will be picked up and added as a secret environment variable to the UbiOps deployment called HF_TOKEN.

UbiOps configuration files

The deploy.sh script fills the manifests needed for each of the UbiOps objects with configurable variables that are sourced from the config.env. These variables largely cover the settings for the objects that are created on UbiOps: the environment, deployment, deployment version and service. Below are the files that are needed in the config/ folder.

Environment

This config file is stored /config/environment.yaml.

environment_name: $UBIOPS_ENVIRONMENT_NAME
environment_supports_request_format: true
base_environment: $UBIOPS_BASE_ENVIRONMENT

Deployment

This config file is stored as /config/deployment.yaml.

deployment_name: $UBIOPS_DEPLOYMENT_NAME
deployment_supports_request_format: true
input_type: plain
output_type: plain

Deployment version

This config file is stored as /config/version.yaml.

deployment_name: $UBIOPS_DEPLOYMENT_NAME
environment: $UBIOPS_ENVIRONMENT_NAME
instance_type_group_name: "$UBIOPS_INSTANCE_TYPE_GROUP_NAME"
minimum_instances: $UBIOPS_MINIMUM_INSTANCES
maximum_instances: $UBIOPS_MAXIMUM_INSTANCES
request_retention_mode: $UBIOPS_REQUEST_RETENTION_MODE

Service

This config file is stored as /config/service.yaml.

service_name: $UBIOPS_SERVICE_NAME
service_deployment: $UBIOPS_DEPLOYMENT_NAME
service_version: $UBIOPS_VERSION_NAME
port: $VAR_SERVICE_PORT

Expand your CI/CD workflow

The provided scripts and workflow serve as baseline examples. You can tailor them to your requirements.

  1. A development/production workflow can be set-up by working on different branches and using conditional statements and different API Tokens to deploy your new versions to different projects or deployments.

  2. You can include more steps into the deployment script, to include unit tests inside or to add environment variables to your deployment in the same process. Check out our CLI documentation all the different commands.

  3. You can add a .ubiops-ignore file in the deployment package directory to exclude files from being pushed to UbiOps together with the deployment.py. It works similar to a .gitignore file. The file can be added into the ubiops deployments deploy and the ubiops environments deploy commands by using the following flag: --ignore_file .ubiops-ignore

  4. Feel free to reach out to UbiOps Support to discuss and optimize your deployment process with our Support team!