Overview

mkenv is a CLI that builds and runs per-project developer containers using Docker with security-focused defaults, disposable sessions, and caching for language tooling. It exists for developers who would rather delete a container than reimage their laptop.

This documentation covers how to install, run, and configure mkenv. No YAML files required.

Installation

Platform: macOS, Linux, and Windows (via WSL). Bring Docker Desktop or equivalent.

Prerequisites

macOS (Homebrew)

brew tap 0xa1bed0/mkenv
brew install mkenv

Linux / Windows (WSL)

# Download the binary (arm64 or amd64)
curl -L https://github.com/0xa1bed0/mkenv/releases/latest/download/mkenv-linux-arm64 -o mkenv
# or for amd64: curl -L https://github.com/0xa1bed0/mkenv/releases/latest/download/mkenv-linux-amd64 -o mkenv

# Make it executable and move to PATH
chmod +x mkenv
sudo mv mkenv /usr/local/bin/

Verify Installation

mkenv version

Important: Ensure Docker is running before launching containers.

Quickstart

  1. Navigate to your project directory: cd /path/to/your/project
  2. Run: mkenv .
  3. mkenv analyzes your project, builds a Docker image, and drops you into a container shell
  4. You're now inside a sandboxed environment with all detected tools installed

First run: Builds the full image and caches dependencies (slower)

Subsequent runs: Reuse cached layers for fast startup

.mkenv Configuration Files

While mkenv works with zero configuration, you can customize behavior using .mkenv files. These files provide the same options as command-line flags.

Example: Organization-wide Defaults

Create ~/projects/.mkenv to apply settings to all projects in that directory:

{
  "enabled_bricks": ["codex", "nvim", "tmux"]
}

Example: Project-specific Settings

Create .mkenv in your project root:

{
  "enabled_bricks": ["claude-code"],
  "volumes": ["~/datasets:/data"]
}

Example: Install Additional System Packages

Create .mkenv in your project root:

{
  "extra_pkgs": ["git", "curl", "vim", "htop", "jq"]
}

These packages will be installed via the system's package manager (e.g., apt-get on Debian-based systems).

Example: Custom Environment Variables (with secrets)

Inject environment variables into the container. Literal values are passed through; values prefixed with mkenv_value_from:<path> are read from a host file at container start.

{
  "envs": {
    "API_BASE":  "https://api.example.com",
    "API_TOKEN": "mkenv_value_from:~/secrets/api-token",
    "DEPLOY_KEY": "mkenv_value_from:/home/me/.config/myapp/deploy.key"
  }
}

Secret values resolved via mkenv_value_from: are never baked into the docker image and never logged — they are injected at container creation time and live only for the lifetime of the container.

Security guarantees for mkenv_value_from::

Reserved names: the MKENV_ prefix is reserved for mkenv-internal variables, and loader-hijack names (LD_PRELOAD, LD_LIBRARY_PATH, LD_AUDIT, DYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH, DYLD_FALLBACK_LIBRARY_PATH) are blocked.

Note: custom envs are applied at container-run time, not at image-build time. Changing an envs entry does not trigger a rebuild — restart the container to pick up the new value.

How It Works

Available Fields

Field Type Description
enabled_bricks array Tools to install (e.g., ["codex", "claude-code", "nvim", "pulumi"])
disabled_bricks array Tools to exclude from auto-detection
extra_pkgs array Additional system packages to install (e.g., ["git", "curl", "vim"])
volumes array Additional directories to mount (e.g., ["~/data:/data"])
disable_auto boolean Disable automatic language detection (default: false)
envs object (string → string) Environment variables to set in the container. Literal values are passed through; values prefixed with mkenv_value_from:<path> are read from a host file at container start (secrets never enter the image). See the example above for the security model.
mount_gpg boolean Forward host GPG agent into the container — equivalent to passing --mount-gpg on the command line. Auto-enables the gpg-agent brick. See GPG Agent Forwarding.

Use cases: Set organization-wide defaults, team preferences, or project-specific requirements without repeating command flags.

LLM Support

Run AI coding tools safely inside mkenv containers using the --tools flag.

Example: Install Claude Code

mkenv . --tools claude-code

Available LLM Tools

Usage

# Start container with Claude Code
mkenv . --tools claude-code

# Inside the container, run Claude
claude

Security Isolation

AI tools run inside the container with restricted access:

Combining Tools

Install multiple tools at once:

mkenv . --tools claude-code,nvim,tmux

Other available tools: nvim, tmux, pulumi, ansible

GPG Agent Forwarding (YubiKey / Smartcard)

If you use a YubiKey or smartcard with GPG, you can forward your host's GPG agent into the mkenv container. This lets you use YubiKey-backed SSH authentication, GPG signing, and file encryption/decryption inside the sandbox — just like on your host, without exposing your keys.

Usage

mkenv . --mount-gpg

Or set it permanently in your project's .mkenv file:

{
  "mount_gpg": true
}

How It Works

mkenv does three things when you pass --mount-gpg:

  1. Socket forwarding — creates a local proxy for your GPG agent sockets and forwards all GPG and SSH agent traffic to the host agent. Your private keys never leave the host.
  2. Keyring import — exports your public keys and trust database from the host (gpg --export) and imports them into the container. This works across all GPG versions (2.2 with kbx, 2.4+ with keyboxd). Private key stubs (smartcard references, no secret material) are also copied so GPG knows which keys live on your YubiKey.
  3. Pinentry management — when an SSH sign request requires your YubiKey PIN, the proxy automatically pauses the container terminal, lets your pinentry program render cleanly, and resumes after you enter the PIN.

The result: gpg --list-secret-keys, gpg --encrypt, gpg --decrypt, gpg --sign, and SSH all work inside the container exactly as they do on your host.

Prerequisites

PIN Entry Setup

Before launching mkenv, tell the GPG agent which terminal to use for PIN prompts:

# Run this in the same terminal where you'll launch mkenv
gpg-connect-agent updatestartuptty /bye

Tip: Add an alias to your shell config to make this easier:

# In ~/.zshrc or ~/.bashrc
alias gpghere="gpg-connect-agent updatestartuptty /bye"

Then just run gpghere before mkenv.

Supported Pinentry Programs

All pinentry programs work with --mount-gpg:

Example Workflow

# Terminal setup
gpghere

# Launch mkenv with GPG forwarding
mkenv . --mount-gpg

# Inside the container — SSH uses your YubiKey automatically
ssh root@your-server.com
# → PIN prompt appears on the host
# → Enter PIN, press Enter
# → SSH session opens

# Encryption/decryption works too
gpg --encrypt -r your@email.com secrets.env
gpg --decrypt secrets.env.gpg
# → YubiKey touch/PIN prompt on the host

# Signing
gpg --sign --armor message.txt

Combining with Ansible

For infrastructure automation with YubiKey-backed SSH:

mkenv . --mount-gpg --tools ansible

This gives you a sandboxed environment where Ansible can SSH to remote hosts using your YubiKey for authentication. Run playbooks, manage inventory, and use ansible-vault — all isolated from your host.

Ansible with PIN caching (default)

Most GPG agent configurations cache the PIN after the first entry (this is the default). Ansible works seamlessly — the first SSH connection prompts for your PIN, and subsequent parallel connections use the cached PIN without any terminal disruption.

Ansible without PIN caching / with touch-required YubiKey

If you've disabled PIN caching or configured your YubiKey to require a touch for every operation, each SSH connection needs a separate PIN entry or touch. Ansible opens multiple SSH connections in parallel by default, which can cause overlapping PIN prompts.

Use --forks 1 to serialize connections so each gets a clean PIN prompt:

ansible-playbook -i inventory.yml deploy.yml --forks 1

Alternatively, set forks = 1 in your ansible.cfg:

[defaults]
forks = 1

Ansible

Install Ansible inside your mkenv container for infrastructure automation:

mkenv . --tools ansible

This installs Ansible via pip along with its dependencies (python3, sshpass). All standard Ansible CLI tools are available: ansible, ansible-playbook, ansible-galaxy, ansible-vault, ansible-inventory, ansible-doc, etc.

Ansible's pip cache and ~/.ansible directory are persisted across container restarts.

Usage with GPG/YubiKey SSH

# Infrastructure automation with hardware-backed SSH keys
mkenv . --mount-gpg --tools ansible

# Inside the container
ansible-playbook -i inventory.yml deploy.yml

Ansible SSH connections will use your YubiKey for authentication — PIN prompts appear on your host terminal when needed.

Commands

All commands accept -h/--help for usage details.

mkenv / mkenv run

Build (if necessary) and run a dev container for the current project.

mkenv run [PATH] [flags]

Flags

Flag Type Description Default
--tools comma list Extra tools to preinstall (e.g. codex,claude,nvim,tmux,pulumi,ansible). none
--langs comma list Hint runtimes to prioritize (node,go,python,rust). auto-detect
--volume comma list Mount additional host directories inside the container. Supports relative paths (e.g. "~/foo:~/foo") project dir only
--rebuild bool Ignore cache and rebuild the image from scratch. false
--shell string Preferred shell to launch inside the container (bash, zsh, fish). zsh
--entrypoint string Preferred entrypoint (e.g. "tmux") none
--mount-gpg bool Forward host GPG agent into the container. Enables SSH, signing, and encryption/decryption via YubiKey/smartcard. Imports your public keyring automatically. false
--command / -c string Run a single command inside the environment and exit (non-interactive). Stdout/stderr are streamed; the host process exits with the command's exit code. none

One-off command execution (-c)

Run a single command inside the mkenv environment without opening an interactive shell. Useful for CI pipelines, git pre-commit hooks, and one-off builds where the runtimes are not installed on the host.

# Run a single command and exit
mkenv . -c "make precommit"

# Use in a git pre-commit hook
mkenv . -c "npm test"

# Exit code from the command propagates to the host
mkenv . -c "exit 42"; echo $?   # prints 42

mkenv attach

Open an additional shell in an already-running container for the same project.

mkenv attach [PATH]

mkenv list

List all mkenv containers and their status.

mkenv list [--verbose]

mkenv clean

Remove containers and caches for a project.

mkenv clean [PATH] [--all]

mkenv completion

Generate shell completion scripts.

mkenv completion [bash|zsh|fish]

Add the output to your shell config for tab completion.

mkenv help

Show help for commands.

mkenv help [command]

Configuration & Behavior

Automatic Language Detection

mkenv automatically detects your project's languages and tools by scanning for:

Based on what it finds, mkenv installs the appropriate language runtimes, package managers, and language servers.

Security Defaults

Caching

mkenv caches dependencies using Docker volumes. First run builds everything, subsequent runs are fast. Rebuilds only happen when dependency files change.

Security Warnings

mkenv scans your project directory for sensitive files (SSH keys, tokens, credential files) before starting. If it detects anything suspicious, it will warn you and ask for confirmation.

Best practice: Never mount sensitive credentials into the container. Keep secrets on your host machine only. Remember that the container could be compromised by a supply chain attack.

Accessing Your Dev Server

Run your dev server normally inside the container:

npm run dev

mkenv automatically routes localhost:3000 (or any port) from your host machine to the container. Access it from your browser, Postman, or any other tool as usual.

How Port Forwarding Works

Unlike vanilla Docker or Devcontainers, you can't dynamically expose ports from inside a Docker environment — you need to think of exposed ports in advance. mkenv solves this with a bidirectional reverse proxy.

Architecture


┌──────────────────┐
│ Browser          │
│                  │
│ connects to      │
│  localhost:3000  │
└────┬─────────────┘
     │
     │           ┌──────────────────────┐
     │           │  mkenv host process  │    ┌──────────┐
     └────────────►                     │    │ Postgres │
                 │  Binds :3000, :5432  │────► :5432    │
                 │  on Mac on demand    │    └──────────┘
                 └───────────▲──────────┘
                             │
                             │
                     :45454 (fixed port)
           ┌─────────────────┼────────────────────┐
           │    Docker       │                    │
           │                 │                    │
           │    ┌────────────▼──────────────┐     │
           │    │    mkenv Container        │     │
           │    │                           │     │
           │    │  ┌──────────────────────┐ │     │
           │    │  │  Proxy Daemon        │ │     │
           │    │  │  :45454         :5432│ │     │
           │    │  └─────────┬──────────▲─┘ │     │
           │    │            │          │   │     │
           │    │            │          │   │     │
           │    │  ┌─────────▼──────────┼─┐ │     │
           │    │  │  npm run dev :3000 │ │ │     │
           │    │  │                    │ │ │     │
           │    │  │  connects to       │ │ │     │
           │    │  │  localhost:5432 ───┘ │ │     │
           │    │  └──────────────────────┘ │     │
           │    └───────────────────────────┘     │
           │                                      │
           └──────────────────────────────────────┘

The mkenv host process acts as a bidirectional traffic hub between your host and the container, using a fixed port (:45454) to avoid Docker's dynamic port limitation.

How Traffic Flows

Key Points

Policies & Guardrails

mkenv includes a comprehensive policy engine that enforces security guardrails at multiple levels. Policies can be configured globally or per-project using a policy.json file.

Security Scanning

Secret Detection:

Restricted Directories:

Policy Configuration

Create a policy.json file in your mkenv config directory (typically ~/.mkenv/policy.json) to enforce organizational policies:

{
  "disabled_bricks": ["codex"],
  "enabled_bricks": ["nvim", "tmux"],
  "allowed_mount_paths": ["/home/user/projects", "/data"],
  "allowed_project_path": "/home/user/projects",
  "ignore_preferences": false,
  "reverse_proxy": {
    "denied_ports": [5432, 3306],
    "allowed_ports": []
  }
}

Policy Fields

Field Type Description
disabled_bricks array Block specific tools from being installed (e.g., ["codex", "claude-code"])
enabled_bricks array Force-enable specific tools regardless of project detection
allowed_mount_paths array Whitelist of directories that can be mounted (empty = allow all except blocked)
allowed_project_path string Restrict where mkenv can run (empty = allow all)
ignore_preferences boolean Override user preferences with policy settings
reverse_proxy object Control which host ports containers can access (see Reverse Proxy Security below)

Policy File Security

Important: Policy files must have 0444 permissions (read-only). mkenv will refuse to start if the policy file has incorrect permissions. This prevents unauthorized modification of security policies.
chmod 444 ~/.mkenv/policy.json

Reverse Proxy Security

mkenv allows containers to access host services (like databases) via a reverse proxy. The policy engine strictly controls which ports can be accessed.

Hardcoded Denied Ports (Always Blocked)

The following ports are permanently blocked and cannot be overridden by policy configuration:

Custom Port Policies

{
  "reverse_proxy": {
    "denied_ports": [5432, 3306],
    "allowed_ports": [8000, 8080]
  }
}

Policy Enforcement

Tool Control (Bricks)

Policies can control which tools (called "bricks") are available:

{
  "disabled_bricks": ["claude-code", "codex"],
  "enabled_bricks": ["nvim", "tmux", "pulumi"]
}

Use Cases:

Volume Mount Restrictions

{
  "allowed_mount_paths": [
    "/home/user/projects",
    "/data/shared"
  ]
}

Project Path Restrictions

{
  "allowed_project_path": "/home/user/approved-projects"
}

Example: Enterprise Policy

{
  "disabled_bricks": ["codex"],
  "enabled_bricks": ["nvim"],
  "allowed_project_path": "/work/projects",
  "allowed_mount_paths": ["/work"],
  "ignore_preferences": true,
  "reverse_proxy": {
    "denied_ports": [5432, 3306, 6379, 27017],
    "allowed_ports": []
  }
}

This policy:

Example: Developer-Friendly Policy

{
  "disabled_bricks": [],
  "enabled_bricks": ["claude-code", "nvim", "tmux"],
  "reverse_proxy": {
    "denied_ports": [],
    "allowed_ports": []
  }
}

This policy:

FAQ

Can I use this on Linux or Windows?

Yes. Linux is fully supported (arm64 and amd64). Windows works via WSL2 using the Linux binary.

Does mkenv replace Docker Desktop?

No, it depends on Docker for virtualization. Think of mkenv as the paranoid orchestrator on top.

Where is the cache stored?

In Docker volumes managed by mkenv. Use mkenv list --verbose to inspect cache details.

What happens if the container dies?

Run mkenv . again. You'll either reattach or get a fresh container using existing caches.

Is this meant for production workloads?

No. It's a dev tool for local workstations, not a deployment platform. If you push it to prod, that's on you.

Can I keep my editor outside the container?

Yes. You can connect VS Code or Cursor to the mkenv container using their remote development features. This keeps the editor UI on your host while running all commands inside the container.

Is there telemetry?

No analytics pings. If something breaks, file an issue rather than letting logs spy on you.

How do I git push?

Git operations (push, pull, clone) happen on your host machine where your credentials live. Everything else happens in the sandbox. This separates concerns: work runs isolated with no credentials, git operations run on host with credentials and no bloat.

How do I install packages that need sudo?

Use mkenv sandbox install <pkg> from inside the container. This is a controlled, audited way to install system packages. You can also specify packages in your .mkenv file with extra_pkgs.

What if auto-detection gets my project wrong?

Guide it with CLI options (--langs, --tools), a .mkenv file, or adjust ad-hoc with mkenv sandbox install.

IDE integration?

VS Code and Cursor can connect to mkenv containers using their remote development features. Dedicated plugins coming soon.

Can I use my YubiKey / smartcard inside the container?

Yes. Use --mount-gpg to forward your host's GPG agent into the container. SSH, GPG signing, encryption, and decryption all work — your public keyring is imported automatically and all crypto operations are forwarded to the host agent. Run gpg-connect-agent updatestartuptty /bye before launching mkenv so the agent knows where to show PIN prompts. See GPG Agent Forwarding for full details.

Can I use Ansible inside the container?

Yes. Use --tools ansible to install Ansible. Combine with --mount-gpg for YubiKey-backed SSH to remote hosts. See Ansible for details.