Containerized development environments for macOS using Apple's container CLI. Each container is an isolated Fedora 44 environment with SSH access and your repo mounted at /workspace.
- Multiple AI coding tools: Claude Code, Opencode, Pi (with Claude or local llama.cpp backends)
- Transient by default: Drop-in/drop-out workspace switching with auto-cleanup
- Persistent opt-in: Long-lived containers for important projects
- Machine-level auth: Configure Claude authentication once per machine
- Simple command interface:
container-dev start/stop/list/persist
cd ~/path/to/container-dev
./install.shThis creates a symlink at ~/.local/bin/container-dev. Make sure ~/.local/bin is in your PATH.
# Start a transient container (auto-replaced when switching workspaces)
cd ~/my-project
container-dev start claude
ssh claude-transient
# Work in another project (auto-replaces the transient container)
cd ~/another-project
container-dev start claude
ssh claude-transient # Same SSH hostname, different workspace
# Create a persistent container for an important project
cd ~/work/critical-project
container-dev start claude --persistent
ssh claude-criticalproject # Dedicated container, never auto-replaced| Profile | Tool | Backend | Use Case |
|---|---|---|---|
claude |
Claude Code | Claude API | Main AI coding assistant (auth auto-detected) |
opencode |
Opencode | Claude API | Alternative tool with Claude backend |
opencode-local |
Opencode | llama.cpp | Offline/privacy-focused with local model |
pi |
Pi | Claude API | Pi coding assistant with Claude |
pi-local |
Pi | llama.cpp | Pi with local model |
Best for: Quick experiments, switching between many repos
- One per profile:
claude-transient,opencode-transient - Auto-replaced: When you switch workspaces, the old container is stopped and recreated
- SSH hostname:
ssh claude-transient
cd ~/experiments/test-1
container-dev start claude
ssh claude-transient
cd ~/experiments/test-2
container-dev start claude # Replaces test-1 container
ssh claude-transient # Same hostname, new workspaceBest for: Long-lived projects you return to frequently
- One per workspace:
claude-importantproject,claude-clientwork - Never auto-replaced: Dedicated container stays running until you explicitly stop it
- SSH hostname:
ssh claude-importantproject
cd ~/work/important-project
container-dev start claude --persistent
ssh claude-importantproject
cd ~/work/another-project
container-dev start claude --persistent
ssh claude-anotherproject
# Both containers stay running simultaneously
container-dev listEnvironment variables can be passed to containers at three levels:
Location: ~/.config/container-dev/env
Create this file to pass environment variables to all containers across all profiles.
Option A: Reference host environment (recommended for secrets)
List variable names only - values are read from your shell environment:
# ~/.config/container-dev/env
JIRA_TOKEN
JIRA_EMAIL
GITHUB_TOKEN
OPENAI_API_KEYThese variables must be set in your shell (e.g., in ~/.bashrc or ~/.zshrc). The container will receive their current values when started.
Option B: Direct values
Specify values directly in the file:
# ~/.config/container-dev/env
GITHUB_TOKEN=ghp_your_token_here
JIRA_TOKEN=your_jira_token
EDITOR=vim
DEBUG=1You can mix both approaches: Variables with = use the specified value, variables without = are expanded from your environment.
Security Note: This file stays on your machine and is never committed to git.
Location: profiles/<profile>/.env
Create a .env file in a profile directory for variables specific to that profile:
# profiles/claude/.env
ANTHROPIC_API_KEY=sk-ant-your-key-here
EDITOR=vimThese are only loaded when starting that specific profile.
Note: Profile .env files can be committed to git for shared defaults, but avoid committing secrets.
Pass environment variables for a single container start:
# Set a variable just for this container
SPECIAL_TOKEN=secret123 container-dev start claude --persistent
# Multiple variables
DEBUG=1 LOG_LEVEL=verbose container-dev start opencodeVariables are loaded in this order (later overrides earlier):
- User-level env file (
~/.config/container-dev/env) - Profile-level env file (
profiles/<profile>/.env) - One-time overrides (command-line)
Authentication is machine-level: configure once per machine, and container-dev auto-detects it.
# On your work laptop with gcloud
gcloud auth application-default login
# Start container (auto-detects Vertex)
container-dev start claudeThe unified claude profile detects the gcloud ADC file and uses Vertex AI automatically.
Set your API key in the user-level env file:
# ~/.config/container-dev/env
ANTHROPIC_API_KEY=sk-ant-your-key-hereOr in the profile-level env file:
# profiles/claude/.env
ANTHROPIC_API_KEY=sk-ant-your-key-herecontainer-dev start claude will auto-detect the API key.
If no gcloud ADC or API key is found, Claude Code will use browser OAuth on first launch.
Override detection:
Edit ~/.config/container-dev/config:
FORCE_CLAUDE_AUTH=vertex # or: api, webStart a container for the current workspace.
Options:
--persistent/-p- Create dedicated container (never auto-replaced)--size small|medium|large- Resource preset (default: medium)--cpus <n>- CPU cores--mem <size>- Memory limit (e.g.,4g)
Examples:
# Transient (default)
container-dev start claude
# Persistent
container-dev start claude --persistent
# With custom resources
container-dev start opencode-local --size large
container-dev start pi --cpus 6 --mem 8gStop and remove a container. Warns before stopping persistent containers.
container-dev stop claude-transient
container-dev stop claude-importantprojectShow all running containers with their type (transient vs persistent), workspace, and SSH hostname.
$ container-dev list
π¦ Transient Containers (auto-replaced on workspace change)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ssh claude-transient
Profile: claude
Workspace: /Users/you/experiments/test
Port: 2222
π Persistent Containers (dedicated, never auto-replaced)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ssh claude-bigproject
Profile: claude
Workspace: /Users/you/work/bigproject
Port: 2223
ssh opencode-local-research
Profile: opencode-local
Workspace: /Users/you/research/ml
Port: 2231Convert the current workspace's transient container to persistent.
cd ~/work/project
container-dev start claude # Transient
# ...work for a while, decide to keep it...
container-dev persist # Now persistentNote: Full implementation pending - currently guides manual recreation.
Profiles ending in -local (e.g., opencode-local, pi-local) use llama.cpp for inference.
Models are stored in ~/.config/container-dev/models/ and shared across all local profiles.
mkdir -p ~/.config/container-dev/models
cd ~/.config/container-dev/models
# Example: CodeLlama 7B (4.8 GB)
wget https://huggingface.co/TheBloke/CodeLlama-7B-GGUF/resolve/main/codellama-7b.Q4_K_M.gguf
# Example: DeepSeek Coder 6.7B (4.1 GB)
wget https://huggingface.co/TheBloke/deepseek-coder-6.7b-instruct-GGUF/resolve/main/deepseek-coder-6.7b-instruct.Q4_K_M.ggufCreate profiles/opencode-local/.env:
LLAMA_MODEL_PATH=/root/.cache/models/codellama-7b.Q4_K_M.gguf
LLAMA_CONTEXT_SIZE=8192
LLAMA_THREADS=4For persistent containers, the workspace directory name becomes part of the SSH hostname:
cd ~/work/important-project
container-dev start claude --persistent
# Creates: claude-importantproject
# SSH: ssh claude-importantprojectTip: Use clear, descriptive directory names for workspaces you plan to make persistent.
# Start container
cd ~/my-project
container-dev start claude --persistent
# Connect VS Code
code --remote ssh-remote+claude-myproject /workspace
# Or use VS Code's "Remote-SSH: Connect to Host" command
# and select "claude-myproject" from the listSafety: Persistent containers stay connected even when you're working elsewhere. Forgotten VS Code windows can't accidentally reconnect to the wrong workspace.
If you were using claude-vertex, claude-pro-api, or claude-pro-web:
- Use the unified
claudeprofile instead - Auth is auto-detected (or override in
~/.config/container-dev/config) - Old profiles still work (deprecated) but will eventually be removed
# Old way
./bin/start.sh claude-pro-api ~/my-project
# New way
cd ~/my-project
container-dev start claudeAdd ~/.local/bin to your PATH:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrcOverride in ~/.config/container-dev/config:
FORCE_CLAUDE_AUTH=apicontainer-dev auto-assigns the next available port. Check with:
container-dev listCheck the state file:
cat ~/.config/container-dev/stateIf stale, manually stop the container:
container-dev stop claude-transient- Runtime: Apple
containerCLI (not Docker/Podman) - Base image: Fedora 44
- SSH: Dedicated ed25519 keypair at
~/.config/container-dev/keys/ - State tracking:
~/.config/container-dev/state - Config:
~/.config/container-dev/config
See CLAUDE.md for implementation details.
See CLAUDE.md for instructions on adding Opencode and Pi profiles.