CI, Docker & IDEs
You designed the team layout in Designing Your Workflow:
mise.toml and mise.lock are committed, *.local.toml is gitignored. This page
takes that exact same config and runs it three places your interactive shell never
reaches — CI runners, Docker builds, and your IDE — without mise activate.
The thread tying all three together is the lockfile: the versions and checksums your laptop resolved are the versions CI installs and Docker bakes in. That’s the whole reproducibility story.
flowchart LR dev["dev laptop<br/>mise use / mise install"] -->|writes exact versions + checksums| lock["mise.lock"] lock -->|git commit| repo["repo<br/>mise.toml + mise.lock"] repo --> ci["CI<br/>mise install --locked"] repo --> docker["Docker build<br/>mise install --locked"] ci -->|identical toolchain| build["green build"] docker -->|identical toolchain| image["slim image"]
GitHub Actions
Section titled “GitHub Actions”Use jdx/mise-action@v4. It installs mise,
installs your tools, and caches them. There’s no “run a task” input — run tasks as
ordinary steps with mise run / mise exec.
name: CIon: [push, pull_request]jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: jdx/mise-action@v4 # installs mise + tools, caches them - run: mise run lint - run: mise run build - run: mise exec -- npm test # mise exec runs one-off in the resolved envIf a mise.lock is present, the action automatically appends --locked to the
install, so CI fails loudly when a tool can’t be resolved from the lockfile rather
than silently pulling a different version. Pin it explicitly if you want it visible:
- uses: jdx/mise-action@v4 with: version: 2026.6.4 # pin mise itself install_args: "--locked" # strict install from mise.lock cache: true # default; tools cached between runsCaching across a matrix
Section titled “Caching across a matrix”Caching is on by default with cache_key_prefix: mise-v1. The trap: a build matrix
shares one cache key, so different variants stomp on each other’s cache. Give each
matrix leg a distinct prefix.
jobs: test: strategy: matrix: os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: jdx/mise-action@v4 with: cache_key_prefix: mise-${{ matrix.os }} # distinct per variant - run: mise run ciGitLab CI & a generic runner
Section titled “GitLab CI & a generic runner”No dedicated action outside GitHub — just install mise and put its shims on PATH.
This curl | sh shape works on any CI, cron box, or bare runner.
default: before_script: - curl https://mise.run | sh - export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:$PATH" - mise install --locked # strict install from mise.lock
test: script: - mise run build - mise exec -- npm testcurl https://mise.run | shexport PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:$PATH"mise install --locked # NOTE: --locked, not --frozen (no such flag)mise run ciDocker
Section titled “Docker”Two rules account for almost every broken mise Dockerfile:
- Never
mise activatein a container. Activation is for interactive shells. Containers are non-interactive — put the shims onPATHinstead, or invoke tools throughmise exec --. mise trust -abeforemise install. This is the #1 mistake. The build’s working dir isn’t trusted yet, so a config with env templates, hooks, or_.sourceis ignored — tools silently don’t install. Trust first.
FROM debian:bookworm-slimENV MISE_VERSION=2026.6.4 \ MISE_DATA_DIR=/mise \ MISE_CONFIG_DIR=/mise \ MISE_CACHE_DIR=/mise/cache \ PATH="/mise/shims:$PATH" # shims on PATH — NOT `mise activate`RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \ && curl https://mise.run | sh && mv ~/.local/bin/mise /usr/local/bin/mise
WORKDIR /appCOPY mise.toml mise.lock ./RUN mise trust -a && mise install --locked # trust BEFORE installA worked multi-stage slim image
Section titled “A worked multi-stage slim image”Build with the full toolchain, then copy only the binaries + installed tools into a
slim runtime. Mount a BuildKit cache on MISE_CACHE_DIR so re-installs are fast.
# syntax=docker/dockerfile:1# ---- builder ----FROM debian:bookworm-slim AS builderENV MISE_VERSION=2026.6.4 \ MISE_DATA_DIR=/mise \ MISE_CONFIG_DIR=/mise \ MISE_CACHE_DIR=/mise/cache \ PATH="/mise/shims:$PATH"RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates git \ && curl https://mise.run | sh && mv ~/.local/bin/mise /usr/local/bin/mise
WORKDIR /appCOPY mise.toml mise.lock ./# trust before install; BuildKit cache mount keeps the cache out of the layerRUN --mount=type=cache,target=/mise/cache \ mise trust -a && mise install --locked
COPY . .RUN mise run build
# ---- runtime ----FROM debian:bookworm-slimENV MISE_DATA_DIR=/mise PATH="/mise/shims:$PATH"COPY --from=builder /usr/local/bin/mise /usr/local/bin/miseCOPY --from=builder /mise /mise # installed tools + shimsCOPY --from=builder /app /appWORKDIR /appCMD ["mise", "exec", "--", "node", "server.js"]Your editor doesn’t source ~/.bashrc, so it never runs mise activate — it can’t
see PATH-mode tools. Shims are the answer. They’re static wrappers in
~/.local/share/mise/shims that resolve the right version with no shell involved,
which is exactly what an IDE (and CI, and cron) needs.
-
Make sure shims exist (they do once any tool is installed). Point the IDE at:
~/.local/share/mise/shims -
Install the editor plugin so paths, env, and tasks wire up automatically:
- VS Code —
mise-vscode(hverlin) - JetBrains (IntelliJ, PyCharm, GoLand, …) —
intellij-mise
- VS Code —
-
After a tool adds a new binary (e.g.
corepack enableexposespnpm), refresh the shims so the IDE can find it:Terminal window mise reshim
That’s the same mise.toml + mise.lock running on your laptop, in CI, in a
container, and in your editor — one config, four environments, identical tools. Now
migrate your existing projects onto it, then raid the
Cookbook for the sharp tricks.