| name | mac-mini-llm-lab |
| description | Configure a Mac mini as a reliable local LLM server with remote access, observability, and power-safe operation. Use when building an always-on private AI inference server on Apple Silicon. |
| license | MIT |
| metadata | {"author":"devops-skills","version":"1.0"} |
Mac mini LLM Lab
Turn a Mac mini into a low-noise, always-on local AI appliance.
When to Use This Skill
Use this skill when:
- Setting up a dedicated local LLM inference server
- Building a private AI development environment
- Need always-on model serving without cloud costs
- Running models that require Apple Silicon unified memory (32-192GB)
- Creating a home lab AI server for a small team
Prerequisites
- Mac mini with Apple Silicon (M2/M3/M4, 16GB+ unified memory recommended)
- macOS Sonoma 14+ or Sequoia 15+
- Ethernet connection (recommended over Wi-Fi)
- UPS for power protection (optional but recommended)
Initial System Setup
softwareupdate --install --all
xcode-select --install
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install tmux htop btop wget jq git neovim
brew install python@3.12 uv
brew install prometheus node_exporter
Ollama Setup
brew install ollama
ollama pull llama3.1:8b
ollama pull nomic-embed-text
ollama pull codellama:7b
ollama pull llama3.1:8b
ollama pull qwen2.5:14b
ollama pull deepseek-coder-v2:16b
ollama pull nomic-embed-text
ollama pull llama3.1:70b
ollama pull qwen2.5:32b
ollama pull codellama:34b
ollama run llama3.1:8b --verbose
MLX Framework (Apple Silicon Native)
MLX runs models natively on Apple Silicon with excellent performance:
uv pip install mlx mlx-lm
python3 -c "
from mlx_lm import load, generate
model, tokenizer = load('mlx-community/Llama-3.1-8B-Instruct-4bit')
response = generate(model, tokenizer, prompt='Explain Docker in 3 sentences', max_tokens=200)
print(response)
"
uv pip install mlx-lm[server]
mlx_lm.server --model mlx-community/Llama-3.1-8B-Instruct-4bit --port 8080
Auto-Start with launchd
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.ollama.serve</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/ollama</string>
<string>serve</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>OLLAMA_HOST</key>
<string>0.0.0.0</string>
<key>OLLAMA_NUM_PARALLEL</key>
<string>4
OLLAMA_MAX_LOADED_MODELS
2
OLLAMA_FLASH_ATTENTION
1
RunAtLoad
KeepAlive
StandardOutPath
/tmp/ollama.log
StandardErrorPath
/tmp/ollama.err
launchctl load ~/Library/LaunchAgents/com.ollama.serve.plist
launchctl list | grep ollama
launchctl unload ~/Library/LaunchAgents/com.ollama.serve.plist
Power & Reliability
sudo pmset -a disablesleep 1
sudo pmset -a sleep 0
sudo pmset -a autorestart 1
sudo pmset repeat shutdown MTWRFSU 03:55:00
sudo pmset repeat poweron MTWRFSU 04:00:00
pmset -g
Remote Access
Tailscale (Recommended)
brew install --cask tailscale
SSH Hardening
sudo systemsetup -setremotelogin on
sudo nano /etc/ssh/sshd_config
sudo launchctl unload /System/Library/LaunchDaemons/ssh.plist
sudo launchctl load /System/Library/LaunchDaemons/ssh.plist
Reverse Proxy with Caddy
brew install caddy
cat > /opt/homebrew/etc/Caddyfile << 'EOF'
llm.local:443 {
tls internal
reverse_proxy localhost:11434
@api path /v1/*
handle @api {
reverse_proxy localhost:11434
}
}
webui.local:443 {
tls internal
reverse_proxy localhost:3000
}
EOF
brew services start caddy
Open WebUI Setup
docker run -d \
--name open-webui \
-p 3000:8080 \
-e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
-e WEBUI_AUTH=true \
-v open-webui:/app/backend/data \
--restart unless-stopped \
ghcr.io/open-webui/open-webui:main
brew install --cask docker
Monitoring
cat > ~/scripts/llm-health.sh << 'SCRIPT'
if curl -sf http://localhost:11434/api/tags > /dev/null; then
echo "$(date): Ollama OK"
curl -s http://localhost:11434/api/ps | python3 -m json.tool
else
echo "$(date): Ollama DOWN"
launchctl kickstart -k gui/$(id -u)/com.ollama.serve
fi
echo "CPU: $(top -l 1 -n 0 | grep 'CPU usage')"
echo "Memory: $(vm_stat | head -5)"
echo "Disk: $(df -h / | tail -1)"
echo "Thermal: $(sudo powermetrics --samplers smc -n 1 2>/dev/null | grep 'CPU die' || echo 'N/A')"
SCRIPT
chmod +x ~/scripts/llm-health.sh
Memory Usage by Model
| Model | RAM Required | Tokens/sec (M2) | Tokens/sec (M4) |
|---|
| llama3.1:8b (Q4) | ~5 GB | ~25 t/s | ~45 t/s |
| qwen2.5:14b (Q4) | ~9 GB | ~15 t/s | ~30 t/s |
| llama3.1:70b (Q4) | ~40 GB | ~5 t/s | ~10 t/s |
| nomic-embed-text | ~300 MB | N/A | N/A |
| codellama:13b | ~8 GB | ~18 t/s | ~35 t/s |
Security Checklist
sudo fdesetup enable
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode on
sudo launchctl disable system/com.apple.screensharing
sudo launchctl disable system/com.apple.AirPlayXPCHelper
Performance Tuning
sudo launchctl limit maxfiles 65536 200000
memory_pressure
sudo powermetrics --samplers gpu_power -n 1
mdutil -i off ~/.ollama
Troubleshooting
| Issue | Solution |
|---|
| Model loading slow | First load caches to memory; subsequent loads are fast |
| Out of memory | Use smaller quantization (Q4_K_M), reduce OLLAMA_MAX_LOADED_MODELS |
| Mac sleeping | Run sudo pmset -a disablesleep 1 |
| Ollama not starting | Check `launchctl list |
| Slow over Wi-Fi | Use Ethernet; Wi-Fi adds latency to streaming responses |
| Thermal throttling | Ensure adequate ventilation, check powermetrics |
Related Skills