Skip to content

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.

The reproducibility loop
Rendering diagram…

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.

.github/workflows/ci.yml
name: CI
on: [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 env

If 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 runs

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.

matrix build with per-variant cache
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 ci

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.

.gitlab-ci.yml
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 test

Two rules account for almost every broken mise Dockerfile:

  1. Never mise activate in a container. Activation is for interactive shells. Containers are non-interactive — put the shims on PATH instead, or invoke tools through mise exec --.
  2. mise trust -a before mise install. This is the #1 mistake. The build’s working dir isn’t trusted yet, so a config with env templates, hooks, or _.source is ignored — tools silently don’t install. Trust first.
single stage — the shape that works
FROM debian:bookworm-slim
ENV 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 /app
COPY mise.toml mise.lock ./
RUN mise trust -a && mise install --locked # trust BEFORE install

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.

Dockerfile
# syntax=docker/dockerfile:1
# ---- builder ----
FROM debian:bookworm-slim AS builder
ENV 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 /app
COPY mise.toml mise.lock ./
# trust before install; BuildKit cache mount keeps the cache out of the layer
RUN --mount=type=cache,target=/mise/cache \
mise trust -a && mise install --locked
COPY . .
RUN mise run build
# ---- runtime ----
FROM debian:bookworm-slim
ENV MISE_DATA_DIR=/mise PATH="/mise/shims:$PATH"
COPY --from=builder /usr/local/bin/mise /usr/local/bin/mise
COPY --from=builder /mise /mise # installed tools + shims
COPY --from=builder /app /app
WORKDIR /app
CMD ["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.

  1. Make sure shims exist (they do once any tool is installed). Point the IDE at:

    ~/.local/share/mise/shims
  2. Install the editor plugin so paths, env, and tasks wire up automatically:

  3. After a tool adds a new binary (e.g. corepack enable exposes pnpm), 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.