Skip to content

Continuous integration

You can use mise in continuous integration (CI) environments to provision the tools your project needs. We recommend pinning tools to specific versions so the environment is reproducible.

Any CI provider

CI pipelines can run arbitrary commands. Use this to install mise and then run mise install to install the tools:

yaml
script: |
  MISE_VERSION=v2026.9.0
  curl https://mise.run | MISE_VERSION="$MISE_VERSION" sh
  mise version | grep -q "^${MISE_VERSION#v} "
  mise install

To ensure you run the tool versions installed by mise, run them through mise x:

yaml
script: |
  mise x -- npm test

Alternatively, you can add the shims directory to your PATH, if the CI provider allows it.

Bootstrapping

An alternative to calling curl https://mise.run | sh is to use mise generate install-script to generate a script that installs and runs mise.

shell
mise generate install-script -l -w

Add .mise/ to your .gitignore and commit the generated ./bin/mise file. You can then use ./bin/mise to install and run mise directly in CI.

yaml
script: |
  ./bin/mise install
  ./bin/mise x -- npm test

By default the generated script installs the version it was generated with, but it honors the same MISE_VERSION and MISE_INSTALL_PATH variables as the install script. An explicit MISE_INSTALL_PATH is always used as-is; otherwise MISE_VERSION also selects the version-keyed default path under the data dir (~/.local/share/mise/bootstrap/), so bumping it in CI installs the requested version instead of reusing the one that was installed first. That describes the non-localized wrapper; a localized one keeps its binary in its localized data directory (.mise/ by default). Wrappers generated by older mise put that binary in the cache dir on Unix, and old Windows launchers put it directly under %LOCALAPPDATA%\mise; the wrapper keeps running an existing one there rather than re-downloading it.

Running against untrusted config (safe mode)

When a job resolves tool versions from configuration it does not control — most commonly a bot that refreshes mise.lock on pull request branches — set MISE_SAFE=1 so that project configuration cannot execute code. In safe mode mise refuses (with an error, never a silent fallback) to run template exec()/read_file(), _.source scripts, hooks, tasks, asdf plugin scripts, or plugin installs, while version resolution over HTTP-based backends continues to work.

yaml
script: |
  MISE_SAFE=1 mise lock --bump --json

See Safe mode for the full list of what is and isn't allowed.

GitHub Actions

If you use GitHub Actions, we provide a mise-action that wraps the installation of mise and the tools. Add the action to your workflow:

yaml
name: test
on:
  pull_request:
    branches:
      - main
  push:
    branches:
      - main
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: jdx/mise-action@v3
        with:
          version: 2024.12.14 # [default: latest] mise version to install
          install: true # [default: true] run `mise install`
          cache: true # [default: true] cache mise using GitHub's cache
          experimental: true # [default: false] enable experimental features
          # automatically write this mise.toml file
          mise_toml: |
            [tools]
            shellcheck = "0.9.0"
          # or, if you prefer .tool-versions:
          tool_versions: |
            shellcheck 0.9.0
      - run: shellcheck scripts/*.sh

GitLab CI

You can use any Docker image with mise installed to run your CI jobs. Here's an example using debian-slim as the base image:

Example Dockerfile
dockerfile
FROM debian:12-slim

RUN apt-get update  \
    && apt-get -y --no-install-recommends install  \
      # install any tools you need
      sudo curl git ca-certificates build-essential \
    && rm -rf /var/lib/apt/lists/*

RUN curl https://mise.run | MISE_VERSION=v... MISE_INSTALL_PATH=/usr/local/bin/mise sh

When configuring your job, you can cache some of the mise directories.

yaml
build-job:
  stage: build
  image: mise-debian-slim # Use the image you created
  variables:
    MISE_DATA_DIR: $CI_PROJECT_DIR/.mise/mise-data
  cache:
    - key:
        prefix: mise-
        files: ["mise.toml", "mise.lock"] # mise.lock is optional, only if using `lockfile = true`
      paths:
        - $MISE_DATA_DIR
  script:
    - mise install
    - mise exec --command 'npm build'

Example with the bootstrap script

An alternative is to use mise generate install-script to bootstrap mise on GitLab CI.

mise generate install-script -l -w

You can then use a generic Docker image such as this one to install and run mise in CI.

Example Dockerfile
dockerfile
FROM debian:12-slim

RUN apt-get update  \
    && apt-get -y --no-install-recommends install sudo curl git ca-certificates build-essential \
    && rm -rf /var/lib/apt/lists/*

Here's an example of a .gitlab-ci.yml file:

yaml
.mise-cache: &mise-cache
  key:
    prefix: mise-
    files: ["mise.toml", "./bin/mise"]
  paths:
    - .mise/installs
    - .mise/mise-2025.1.3

build-job:
  stage: build
  image: my-debian-slim-image # Use the image you created
  cache:
    - <<: *mise-cache
      policy: pull-push
  script:
    - ./bin/mise install
    - ./bin/mise exec --command 'npm build'

Xcode Cloud

If you use Xcode Cloud, you can use a custom ci_post_clone.sh build script to install mise. Here's an example:

bash
#!/bin/sh
curl https://mise.run | sh
export PATH="$HOME/.local/bin:$PATH"

mise install # Installs the tools in mise.toml
eval "$(mise activate bash --shims)" # Adds the activated tools to $PATH

swiftlint {args}
MIT LicenseCopyright © 2026jdx.dev