Skip to main content

Lightweight process containerization for macOS Apple Silicon with Metal/MLX support

Project description

MetalBox

Lightweight process containerization for macOS Apple Silicon. Run ML workloads with Metal/MLX acceleration and Docker-like resource limits — without a Linux VM.

The problem

Every container runtime on macOS (Docker, Podman, OrbStack, Lima) runs a Linux VM. Linux doesn't have Metal. So you can't use MLX, MPS, or any Metal-accelerated framework inside a container. You're stuck choosing between:

  • Docker — real resource limits, but no GPU, 3x slower for ML inference
  • Native — full Metal speed, but no resource limits, no lifecycle management, dangerous on a shared machine

MetalBox gives you both: native Metal performance with container-like resource management.

How it works

MetalBox is a Go server that runs your workloads as native macOS processes with enforced resource limits, health checks, and a web dashboard for monitoring.

┌──────────────────────────────────────┐
│  metalbox-dashboard (Go binary)      │
│                                      │
│  ┌────────────┐  ┌────────────────┐  │
│  │ your app   │  │ resource guard │  │
│  │ (native    │◄─│ • RSS watchdog │  │
│  │  macOS     │  │ • Metal mem cap│  │
│  │  process)  │  │ • CPU policy   │  │
│  └────────────┘  └────────────────┘  │
│        │              │              │
│   Metal / MLX / MPS   │ health checks│
│   (direct GPU access) │ log capture  │
│                       │ auto-restart │
│  ┌─────────────────────────────────┐ │
│  │  web dashboard (localhost:9090) │ │
│  │  start/stop/restart • logs      │ │
│  │  RSS/CPU graphs • events        │ │
│  └─────────────────────────────────┘ │
└──────────────────────────────────────┘

Quick start

# Build the dashboard server
cd dashboard
go build -o metalbox-dashboard .

# Create a metalbox.yml (see Config below)
# Start the dashboard
./metalbox-dashboard

# Open http://localhost:9090

Web dashboard

The dashboard runs on localhost:9090 and provides:

  • Service overview — status, PID, RSS, CPU%, memory usage bars
  • Start / Stop / Restart buttons per service
  • Log viewer with auto-refresh
  • Event timeline — starts, stops, OOM kills, health check failures, restarts
  • Auto-refresh every 3 seconds

CLI

A thin Python CLI is also available, talking to the dashboard API:

pip install -e .

metalbox ps                  # show services + resource usage
metalbox start myapp         # start a service
metalbox stop myapp          # stop a service
metalbox restart myapp       # restart a service
metalbox logs myapp          # view logs

Config

metalbox.yml in your project directory:

services:
  inference:
    command: python -m uvicorn app:app --host 0.0.0.0 --port 8080
    workdir: /path/to/your/project
    env:
      MODEL_CACHE: /tmp/models
    resources:
      memory: 2.5g          # hard RSS limit — process killed + restarted if exceeded
      metal_memory: 2g      # Metal heap cap (mx.metal.set_memory_limit)
      metal_cache: 512m     # Metal cache cap (mx.metal.set_cache_limit)
      cpus: background      # "background" = E-cores only, "default" = all cores
    restart: unless-stopped  # always | unless-stopped | on-failure | no
    healthcheck:
      url: http://127.0.0.1:8080/healthz
      interval: 30
      timeout: 10
      retries: 3
      start_period: 120

  proxy:
    command: caddy reverse-proxy --from :443 --to :8080
    ports: [443]                # fail-fast if port already in use
    resources:
      memory: 128m
    restart: always
    depends_on:
      - inference
    sandbox:                    # filesystem + network isolation (macOS sandbox-exec)
      allow_net: true           # allow network access (default: false)
      read_only:                # can read but not write these paths
        - /etc/config
      read_write:               # additional writable paths (workdir + /tmp always allowed)
        - /var/data

Environment isolation

By default, services get a clean environment — only essential system vars (PATH, HOME, USER, SHELL, LANG, TERM, TMPDIR) plus your declared env: vars. No inherited AWS_*, GITHUB_*, SSH_*, or other secrets leak in.

To opt into full parent env inheritance, set env_inherit: true on a service.

How resource limits work

Resource Mechanism Hard?
Memory (RSS) Go watchdog goroutine polls ps every 5s — kills process group if RSS exceeds limit, auto-restarts per policy Yes (kill + restart)
Metal memory Wrapper injection: auto-generates a Python script calling mx.metal.set_memory_limit() before your app imports anything Yes (Metal API)
Metal cache Same wrapper calls mx.metal.set_cache_limit() Yes (Metal API)
CPU taskpolicy -b for background QoS (E-cores only), or default (all cores) Structural (not %)
Health checks HTTP GET / TCP connect / shell command — restart on consecutive failures Configurable retries

macOS has no cgroups. RSS limits (ulimit -m) are silently ignored by the kernel. MetalBox enforces memory limits by monitoring RSS and killing the process if it exceeds the cap — then auto-restarting per the configured restart policy. This is the only reliable approach on macOS.

Metal memory injection

For Python/MLX workloads, MetalBox auto-generates a wrapper script that calls mx.metal.set_memory_limit() and mx.metal.set_cache_limit() before your app loads any models. The wrapper is transparent — it detects python -m module and python script.py patterns (including uv run python prefix) and rewrites the command to go through the wrapper first.

Architecture

metalbox/
├── dashboard/
│   ├── main.go             # Go server — process supervisor, RSS guard,
│   │                       #   health checks, Metal wrapper, REST API
│   └── static/
│       └── index.html      # embedded web dashboard (single file)
├── metalbox/
│   ├── __init__.py
│   └── cli.py              # thin Python CLI (talks to Go server API)
├── pyproject.toml           # Python CLI package config
└── metalbox.yml             # your service config (gitignored)

The Go binary is the runtime — it handles everything:

  • Process lifecycle (start, stop, restart, PID files)
  • RSS memory watchdog (kill + restart on exceed)
  • Metal/MLX memory limit injection (auto-generated Python wrapper)
  • CPU policy via taskpolicy
  • Health checks (HTTP, TCP, command)
  • Log capture to ~/.metalbox/logs/
  • Web dashboard + REST API
  • Event tracking (OOM kills, health failures, restarts)

The Python CLI is optional — a thin client that calls the REST API for terminal use.

Comparison

Docker MetalBox Native script
Metal / MLX / MPS No Yes Yes
Memory limits Hard (cgroups) Hard (watchdog + kill) None
CPU limits Hard (cgroups) Structural (E-cores) None
Health checks Yes Yes (HTTP/TCP/CMD) None
Web dashboard Docker Desktop Yes (localhost:9090) None
Lifecycle management Full Yes Manual
Filesystem isolation Full (namespaces) Write restrictions (sandbox-exec) None
Network isolation Full (namespaces) Yes (sandbox-exec) None
Environment isolation Yes Yes (clean env by default) None
Port conflict detection Yes Yes (fail-fast) None
Works on Linux Yes No (macOS only) No

Requirements

  • macOS 14+ on Apple Silicon
  • Go 1.21+ (to build the dashboard)
  • Python 3.10+ (optional, for CLI only)

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

metalbox-0.1.0-py3-none-macosx_14_0_arm64.whl (2.7 MB view details)

Uploaded Python 3macOS 14.0+ ARM64

File details

Details for the file metalbox-0.1.0-py3-none-macosx_14_0_arm64.whl.

File metadata

  • Download URL: metalbox-0.1.0-py3-none-macosx_14_0_arm64.whl
  • Upload date:
  • Size: 2.7 MB
  • Tags: Python 3, macOS 14.0+ ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for metalbox-0.1.0-py3-none-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 0dcabff7ac07ae2debb912c211151711b7a644ca6abf9f100f1bab4cdd1b8150
MD5 47aea6e6c2befb4cc7611ed7f620705f
BLAKE2b-256 f36cbd7c51d20ee123de7ce9d600a3b17d2ed6b3ba7fe880e94d67d99c1a2790

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page