This document describes the architecture, design decisions, and separation of concerns in DevMagic.
DevMagic provides portable development environments using VS Code Dev Containers. The goal is to enable developers to go from "fresh OS" to "coding" in minutes with zero host installation (except container runtime + VS Code).
- Zero friction - Minimize steps from "fresh OS" to "coding"
- Consistency - Same environment on every machine
- Modularity - Start minimal, add services as needed
- Transparency - Open source, well-documented, no magic
- Portability - Works on Windows, Linux, macOS identically
- Separation of concerns - Container infrastructure vs personal preferences
DevMagic deliberately separates container infrastructure from personal environment preferences:
Container Concerns (DevMagic) βοΈ User Concerns (Dotfiles)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
devcontainer-setup.sh dotfiles/shell/install.sh
ββ SSH keys setup ββ Homebrew installation
ββ AI CLI tools ββ fzf, hugo, babashka
ββ Container-specific config ββ Zsh plugins
ββ Calls dotfiles/shell/install.sh ββ VS Code settings symlinks
ββ Shell configuration
dotfiles/shell/init.sh
ββ Runtime shell behavior
ββ PATHs, aliases, functions
ββ Sourced on every shell start
DevMagic stays focused on container infrastructure:
- Works for anyone using DevMagic
- No personal preferences baked in
- Minimal and maintainable
Dotfiles handle personal environment:
- Your settings follow you everywhere (not just containers)
- Editor-agnostic (VS Code, Neovim, Cursor, etc.)
- Full version control of your preferences
- Works on any machine, not just dev containers
Q: Should install.sh be in DevMagic or dotfiles?
A: It should stay in your dotfiles repo.
The current design is correct:
- DevMagic = portable dev container infrastructure (works for anyone)
- Dotfiles = your personal preferences (only applies to you)
The devcontainer-setup.sh automatically clones your dotfiles repository during container creation:
# Clone dotfiles if directory doesn't exist
if [ ! -d "$dotfiles_dir" ]; then
git clone --depth=1 --branch "$dotfiles_branch" "$dotfiles_repo" "$dotfiles_dir"
fi
# Run install script if it exists
if [ -f "$dotfiles_dir/shell/install.sh" ]; then
bash "$dotfiles_dir/shell/install.sh"
fiYou can configure which repository to clone via host environment variables (no need to edit devcontainer.json):
# Add to your ~/.bashrc or ~/.zshrc
export DEVMAGIC_DOTFILES_REPO="https://github.com/yourusername/dotfiles.git"
export DEVMAGIC_DOTFILES_BRANCH="main" # optionalDEVMAGIC_DOTFILES_REPO: Repository URL (default: marcelocra's dotfiles)DEVMAGIC_DOTFILES_BRANCH: Branch to clone (default:main)
Host environment variables are passed to the container via ${localEnv:VAR} syntax. Default values are handled in the setup script (not in devcontainer.json) due to a spec limitation with colons in URLs.
This means:
- β Your machine: Set host env vars once, works for all DevMagic containers
- β
Someone else using DevMagic: Gets a working container (can skip dotfiles by setting
DEVMAGIC_DOTFILES_REPO="") - β No coupling: DevMagic works without dotfiles; dotfiles are optional enhancement
User runs: curl -fsSL https://devmagic.run/install | bash
β
βΌ
/install endpoint β fetches setup/devmagic.sh from GitHub
β
βΌ
devmagic.sh downloads .devcontainer/ files to user's project
β
βΌ
User opens in VS Code Dev Container
β
βΌ
postCreateCommand: curl -fsSL https://devmagic.run/setup | bash
β
βΌ
/setup endpoint β fetches setup/devcontainer-setup.sh from GitHub
β
βΌ
devcontainer-setup.sh:
ββ SSH keys setup (from mounted ~/.ssh-from-host)
ββ AI CLI tools (aider, claude, gemini, copilot)
ββ Dotfiles setup:
ββ Clone repo if ~/prj/dotfiles doesn't exist
ββ Run ~/prj/dotfiles/shell/install.sh
β
βΌ
install.sh (from dotfiles):
ββ Homebrew installation
ββ fzf (from custom fork for security)
ββ Brew packages (hugo, babashka, bat, ripgrep, etc.)
ββ Zsh plugins (from custom forks)
ββ Shell config symlinks (.zshrc, .bashrc)
ββ VS Code config symlinks (settings.json, keybindings.json)
View as Mermaid diagram
flowchart TD
A["User runs: curl devmagic.run/install | bash"] --> B["/install endpoint<br/>fetches setup/devmagic.sh"]
B --> C["devmagic.sh downloads<br/>.devcontainer/ files"]
C --> D["User opens in<br/>VS Code Dev Container"]
D --> E["postCreateCommand:<br/>curl devmagic.run/setup | bash"]
E --> F["/setup endpoint<br/>fetches devcontainer-setup.sh"]
F --> G["devcontainer-setup.sh"]
G --> H["SSH keys setup"]
G --> I["AI CLI tools"]
G --> J["Dotfiles setup"]
J --> K["Clone repo if missing<br/>~/prj/dotfiles"]
K --> L["Run install.sh"]
L --> M["Homebrew installation"]
L --> N["fzf from custom fork"]
L --> O["Brew packages"]
L --> P["Zsh plugins"]
L --> Q["Shell config symlinks"]
L --> R["VS Code config symlinks"]
style A fill:#e1f5ff
style G fill:#fff4e1
style L fill:#f0f0f0
For CLI tools like fzf, babashka, and hugo, Homebrew is recommended over Conda:
| Aspect | Homebrew β | Conda |
|---|---|---|
| Purpose | CLI tools and system packages | Python-centric ecosystem |
| Package availability | Wide (fzf, babashka, hugo) | Limited for non-Python tools |
| Licensing | Free and open | Commercial license concerns |
| Conflicts | N/A | Known conflicts with Homebrew |
Security-critical tools are installed from custom forks for auditability:
- fzf:
marcelocra/fzfβ Fuzzy finder - zsh-autosuggestions:
marcelocra/zsh-autosuggestions - zsh-syntax-highlighting:
marcelocra/zsh-syntax-highlighting
This allows:
- Code review before updates
- Version pinning for stability
- No supply chain attacks from upstream
VS Code settings and keybindings are stored in the dotfiles repo and symlinked:
Dotfiles: ~/prj/dotfiles/apps/vscode/User/
ββ settings.json
ββ keybindings.json
β
βΌ (symlinked by install.sh)
Container: ~/.vscode-server/data/User/
ββ settings.json β ~/prj/dotfiles/apps/vscode/User/settings.json
ββ keybindings.json β ~/prj/dotfiles/apps/vscode/User/keybindings.json
The install.sh script detects the VS Code environment and symlinks accordingly:
- Remote container:
~/.vscode-server/data/User/ - Native Linux:
~/.config/Code/User/ - Native macOS:
~/Library/Application Support/Code/User/
- Entry point for
curl https://devmagic.run/install | bash - Downloads
.devcontainer/files to user's project - Creates the initial dev container configuration
- Runs as
postCreateCommandwhen container starts - Handles container-specific setup (SSH, AI tools)
- Calls user's dotfiles install script if available
- One-time setup for personal tools and preferences
- Installs Homebrew, CLI tools, zsh plugins
- Creates shell config symlinks
- Symlinks VS Code settings/keybindings
- Idempotent (safe to run multiple times)
- Environment-aware (detects container vs native)
- Runtime shell configuration
- Sourced on every shell start (must be fast!)
- Sets up PATHs, aliases, functions
- No installation logic (that's in install.sh)
The dotfiles install.sh supports feature flags for customization:
# Skip specific components
DOTFILES_SKIP_HOMEBREW=true ./install.sh
DOTFILES_SKIP_CLI_TOOLS=true ./install.sh
DOTFILES_SKIP_ZSH_PLUGINS=true ./install.sh
DOTFILES_SKIP_VSCODE=true ./install.sh
# Enable debug logging
DOTFILES_DEBUG=1 ./install.shdevmagic/
βββ .devcontainer/ # Dev container config (for DevMagic itself)
βββ setup/
β βββ devmagic.sh # Installation script (adds DevMagic to projects)
β βββ devcontainer-setup.sh # Container setup (runs on container create)
βββ www/ # Website source (devmagic.run)
β βββ app/
β β βββ install/route.ts # Serves devmagic.sh
β β βββ setup/route.ts # Serves devcontainer-setup.sh
β βββ ...
βββ docs/ # Documentation
β βββ ARCHITECTURE.md # This file
βββ docker-compose.yml # Auxiliary services
dotfiles/ (separate repo)
βββ shell/
β βββ init.sh # Runtime configuration (sourced)
β βββ install.sh # One-time setup (run once)
βββ apps/
βββ vscode/
βββ User/
βββ settings.json
βββ keybindings.json
- README.md - Getting started and usage
- CONTRIBUTING.md - Development guidelines
- CHANGELOG.md - Version history