Running Mistral’s Vibe CLI in an sbx sandbox

sbx + mistral vibe

Mistral just released Vibe, its open-source coding agent CLI (powered by Mistral models). And as usual, rather than installing it directly on my machine, I prefer to run it inside a secure sandbox with sbx.

The good news is that Mistral is already one of the services natively supported by sbx: https://docs.docker.com/ai/sandboxes/security/credentials/#built-in-services, so there’s no need to use “custom secrets”: https://docs.docker.com/ai/sandboxes/security/credentials/#custom-secrets.


Three quick reminders before we begin:

  • sbx is a Docker CLI that creates and manages microVM-based sandboxes, to run AI agents safely (network isolation, secret management, workspace mounting).
  • a template is simply a base Docker image for a sandbox (for example docker/sandbox-templates:shell). It’s the “ready-to-use” starting point.
  • a kit is a spec.yaml file that describes the sandbox: which image to use, what to launch at startup, which environment variables, which network domains to allow, etc.

The Mistral API key: already supported by sbx

sbx maintains a list of “built-in services”: providers whose name is already known to the CLI. Mistral is one of them (alongside anthropic, openai, google, github, groq, nebius, openrouter, etc).

Watch out for what “built-in” means (and doesn’t mean): sbx knows the name of the mistral service — so we can store the key under that name (sbx secret set -g mistral …) and sbx knows how to resolve it. However, the wiring (which domain, which header, where the key comes from) still has to be declared in the kit. “Built-in” ≠ “automatically injected without writing anything”.

What this gives us, concretely, is the proxy-managed mechanism: the MISTRAL_API_KEY variable only holds a dummy value (a sentinel) inside the container, and it’s sbx’s proxy that replaces it with the real key, only for requests to api.mistral.ai.

📝 Why does this matter?: the real key is never written into the container. Vibe does see a non-empty MISTRAL_API_KEY variable (so it doesn’t prompt for configuration at startup), but the actual secret only travels through the proxy. If the agent tries to exfiltrate the variable, all it gets is the sentinel.


So all you need to do is provide the key to sbx on the host side, once:

export MISTRAL_API_KEY="…"   # or: sbx secret set -g mistral -t "$MISTRAL_API_KEY"

Personally, I use 1Password to store my keys: load secrets into the environment


The template: shell + vibe

Vibe is a Python application installed with uv. Rather than installing it on every startup, I prefer to prepare an image once and for all, based on the official shell sbx template (which already contains uv, git, ripgrep and Python):

# syntax=docker/dockerfile:1
ARG BASE_IMAGE=docker/sandbox-templates:shell
FROM ${BASE_IMAGE}

# The startup wrapper (see below)
USER root
COPY --chmod=0755 start.sh /usr/local/bin/vibe-sandbox

# Installing the Vibe CLI (as the agent user)
USER agent
RUN uv tool install mistral-vibe \
    && vibe --version

CMD ["vibe-sandbox"]

Then we build and publish the image:

DOCKER_HANDLE=k33g
NAME=sbx-mistral-vibe
TAG=0.0.0
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t ${DOCKER_HANDLE}/${NAME}:${TAG} --push .

The kit: spec.yaml

This is where everything gets wired together. Here’s the spec.yaml:

schemaVersion: "1"
kind: agent
name: mistral-vibe
displayName: Mistral Vibe (prebuilt image)

agent:
  image: docker.io/k33g/sbx-mistral-vibe:0.0.0
  aiFilename: AGENTS.md
  entrypoint:
    run: [vibe-sandbox, "--agent", "auto-approve"]

network:
  serviceDomains:
    api.mistral.ai: mistral
  serviceAuth:
    mistral:
      headerName: Authorization
      valueFormat: "Bearer %s"

credentials:
  sources:
    mistral:
      env:
        - MISTRAL_API_KEY

environment:
  proxyManaged:
    - MISTRAL_API_KEY

Explanations

Field Why
kind: agent We’re declaring a standalone agent: a complete image + its launch configuration.
name: mistral-vibe The kit’s machine identifier, reused in the sbx run command.
agent.image Our image prepared just before (with vibe already installed).
agent.aiFilename AGENTS.md — the instructions file Vibe reads in the project.
agent.entrypoint.run The program launched when the sandbox starts (see below). --agent auto-approve starts Vibe in yolo mode: it automatically approves every tool execution.
network.serviceDomains Maps the api.mistral.ai domain to the mistral service. Required: this is what tells the proxy “for this domain, use the mistral service’s key”.
network.serviceAuth The header injection format: Authorization: Bearer <key>. Required (unless you use the egress: sugar, which provides defaults for known services).
credentials.sources Required. Indicates where the key comes from: the host variable MISTRAL_API_KEY (or the credential store). Without this block, the proxy has no key to inject → unauthenticated calls to api.mistral.ai (401).
environment.proxyManaged Places a sentinel MISTRAL_API_KEY inside the container. Needed so that Vibe sees a non-empty key and doesn’t show its configuration screen at startup.

Why entrypoint.run: [vibe-sandbox] and not [vibe]?

This is the only real “gotcha”. Vibe is a Python application that uses the httpx HTTP library. Now, sbx injects into the sandbox a NO_PROXY variable that contains an IPv6 entry in brackets, [::1], with no port:

NO_PROXY=localhost,127.0.0.1,::1,[::1],gateway.docker.internal

httpx can’t parse that [::1] entry and crashes at startup:

Background initialization failed: Invalid port: ':1]'

Since we can’t dynamically transform a variable in the spec.yaml, I created a small wrapper (start.sh, installed in the image under the name vibe-sandbox) that cleans up NO_PROXY right before launching the real vibe:

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

# Remove only the "[::1]" token, keep everything else
_np="${NO_PROXY:-${no_proxy:-}}"
_np="$(printf '%s' "$_np" | sed 's/\[::1\]//g; s/,,*/,/g; s/^,//; s/,$//')"
export NO_PROXY="$_np" no_proxy="$_np"

exec vibe "$@"

📝 The exec matters: the wrapper replaces itself with vibe (same PID), and "$@" forwards to vibe any arguments sbx might add (for example in --task mode). The day sbx fixes this NO_PROXY on the runtime side, we’ll be able to go back to run: [vibe] and remove the wrapper.


Launching the sandbox

Once the key is provided on the host side, all that’s left is a single command:

sbx run --kit ./mistral-vibe mistral-vibe .
  • --kit ./mistral-vibe: the folder that contains our spec.yaml.
  • mistral-vibe: the agent’s name (the one defined in spec.yaml).
  • .: the project directory to mount in the sandbox.

And there you go: Vibe starts in an isolated microVM, talks to the Mistral API through the proxy, and the real key never touched the container.

sbx + mistral vibe

Conclusion

In a few lines of spec.yaml, we turned Mistral’s Vibe CLI into a clean, isolated and reusable sbx agent. Most of the work is already done for us: since Mistral is a built-in service, API key management is automatic. The only adjustment to know about is the little wrapper that works around the NO_PROXY issue with httpx.

Official documentation:

The source code is available at https://codeberg.org/sbx-pod/sbx-mistral-vibe.

© 2026 k33g Project | Built with Gu10berg

Subscribe: 📡 RSS | ⚛️ Atom