Loading
Reza Chegini

Junior DevOps Engineer

Junior Cloud Engineer

Junior Site Reliability Engineer

Software Engineer

Backend Developer

Reza Chegini

Junior DevOps Engineer

Junior Cloud Engineer

Junior Site Reliability Engineer

Software Engineer

Backend Developer

Blog Post

Mise and Dev Containers – Simple Setup Guide

Mise and Dev Containers – Simple Setup Guide

This guide shows how to use mise inside a Dev Container (works with DevPod and Visual Studio Code). It explains how to build a Dockerfile, write a setup script, connect chezmoi, and prepare Zsh.

The goal is to create a portable and ready-to-use development environment for your projects.


What Is Mise?

Mise is a simple tool manager that helps developers control versions of programming languages and tools across different projects.

It works like asdf or rtx. You can use it to install and switch between different versions of tools such as Node.js, Python, Go, Terraform, or smaller utilities like bat, fzf, or ripgrep.

Mise is very helpful when you work on several projects that need different versions of the same language or tool.

For example:

  • Project A uses Python 3.10
  • Project B needs Python 3.12

Instead of changing your system manually, mise automatically loads the correct version for each project using a file called mise.toml.


Why Is Mise Useful?

  • It makes your development environment consistent on all machines.
  • You can define tool versions inside mise.toml and share it with your team.
  • It saves time when setting up new environments or containers.
  • It avoids “it works on my computer” problems because everyone uses the same setup.
  • It works perfectly with Dev Containers, chezmoi, and Zsh.

Goal of This Setup

  • Build a Dev Container image that already includes mise
  • Automatically install tools listed in your mise.toml file
  • (Optional) Manage the mise binary with chezmoi for your local machine
  • (Optional) Set up Zsh for a clean and friendly shell prompt

Project Structure (what each file is for)

project-root/
├─ .devcontainer/
│  ├─ devcontainer.json   ← rules for building and starting your dev container
│  └─ Dockerfile          ← how to build the container image (base OS, copy mise, etc.)
├─ scripts/
│  └─ setup               ← runs after the container is created; installs tools with mise
├─ mise.toml              ← list of tools and versions for this project
└─ (your source code)     ← your app or library

Step 1: devcontainer.json (explain each field)

File: .devcontainer/devcontainer.json

{
  "build": {
    "context": "..",
    "dockerfile": "Dockerfile"
  },
  "postCreateCommand": "scripts/setup"
}

What it means:

  • "build": tells Dev Containers to build a custom image.
    • "context": ".." means the build process can see the project root.
    • "dockerfile": "Dockerfile" means use the Dockerfile in this folder.
  • "postCreateCommand": "scripts/setup" runs once after the container is created. It will trust mise.toml and install tools.

Why we build an image:
Having mise preinstalled makes the container start faster and work the same every time.


Step 2: Dockerfile (explain each line)

File: .devcontainer/Dockerfile

FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04
  • Use a clean base image with Ubuntu 24.04 and default Dev Container tools.
COPY --from=jdxcode/mise /usr/local/bin/mise /usr/local/bin/
  • Copy the mise binary from the official mise image into our container.
  • Now mise is available at /usr/local/bin/mise.
RUN echo 'eval "$(mise activate bash)"' >> /home/vscode/.bashrc \
 && echo 'eval "$(mise activate zsh)"'  >> /home/vscode/.zshrc
  • Automatically activate mise for both bash and zsh when the shell opens.
  • If your user is not vscode, update the home path.

Why this helps:
When you open a new terminal, mise loads the correct tool versions from mise.toml.


Step 3: scripts/setup (line-by-line explanation)

File: scripts/setup
Make it executable:

chmod +x scripts/setup

Script:

#!/usr/bin/env bash
set -euo pipefail

# Fail early if mise is missing
if ! command -v /usr/local/bin/mise >/dev/null 2>&1; then
  echo "ERROR: mise not found at /usr/local/bin/mise" >&2
  exit 1
fi

# Resolve workspace path (Dev Containers mount your repo under /workspaces/<name>)
WORKSPACE_DIR="${WORKSPACE_DIR:-${PWD}}"

# Point to your repo's mise.toml (adjust if stored elsewhere)
MISE_FILE="$WORKSPACE_DIR/mise.toml"

if [[ -f "$MISE_FILE" ]]; then
  /usr/local/bin/mise trust "$MISE_FILE"
  /usr/local/bin/mise install
else
  echo "WARN: $MISE_FILE not found. Skipping mise install."
fi

# --- Optional: set zsh as default shell if available ---
if command -v zsh >/dev/null 2>&1; then
  sudo chsh -s "$(command -v zsh)" "$USER" || true
fi

Explanation line by line:

  1. #!/usr/bin/env bash – Runs the script using bash.
  2. set -euo pipefail – Makes the script stop when an error occurs or when a variable is missing.
  3. if ! command -v /usr/local/bin/mise >/dev/null 2>&1; then – Checks if mise is installed.
  4. echo "ERROR..." – If not, prints an error and stops the script.
  5. WORKSPACE_DIR="${WORKSPACE_DIR:-${PWD}}" – Sets the workspace path. If not defined, use the current folder.
  6. MISE_FILE="$WORKSPACE_DIR/mise.toml" – Points to the mise.toml file.
  7. if [[ -f "$MISE_FILE" ]]; then – Checks if the file exists.
  8. /usr/local/bin/mise trust "$MISE_FILE" – Tells mise to trust the config file (no prompt).
  9. /usr/local/bin/mise install – Installs all the tools listed in mise.toml.
  10. else … – Shows a warning if mise.toml is missing.
  11. if command -v zsh >/dev/null 2>&1; then – Checks if zsh exists.
  12. sudo chsh -s "$(command -v zsh)" "$USER" || true – Sets zsh as the default shell if possible.

Why this script matters:
It prepares your container by installing tools automatically from mise.toml. You only need to run the container; everything else happens by itself.


Step 4: mise.toml Example

[tools]
bat = "latest"
chezmoi = "latest"
fzf = "latest"
lsd = "latest"
ripgrep = "latest"

You can also add programming languages:

node = "20"
python = "3.12"
go = "1.22"
terraform = "1.8.5"

When you run mise install, all tools are downloaded and ready to use.


Step 5: Optional chezmoi Integration

chezmoi manages configuration files on your local system.
You can use it to install mise automatically.

.chezmoiexternals/mise.toml

[".local/bin/mise"]
type = "file"
executable = true
url = "https://mise.jdx.dev/mise-latest-{{.chezmoi.os}}-{{.chezmoi.arch}}"

For macOS:

brew install mise

Step 6: Optional Zsh and Pure Prompt

Install pure prompt:

mkdir -p "$HOME/.zsh"
git clone https://github.com/sindresorhus/pure.git "$HOME/.zsh/pure"

Add this to .zshrc:

autoload -U promptinit; promptinit
prompt pure
eval "$(/usr/local/bin/mise activate zsh)"

This gives you a clean prompt and auto-loads mise every time you open the terminal.


Step 7: Quick Start

  1. Start the container devpod up . or in VS Code → Reopen in Container
  2. Check mise mise --version mise which bat
  3. Add or update tools mise install

Troubleshooting

  • mise: command not found – Rebuild the container or open a new shell.
  • Permission denied for setup script – Run chmod +x scripts/setup.
  • mise.toml missing warning – Add it at the project root.
  • Wrong home path – Change /home/vscode in Dockerfile if your username differs.

Tags:
Write a comment