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/Dockerfile dotfiles/shell/install.sh
ββ Base image (typescript-node) ββ Homebrew installation
ββ System packages (tmux, nvim, ...) ββ fzf, hugo, babashka
ββ Zsh plugins
.devcontainer/docker-compose.yml ββ VS Code settings symlinks
ββ Workspace + dotfiles mounts ββ Shell configuration
ββ tmpfs /tmp, hostname, network
ββ Example extra services (profiles) dotfiles/shell/init.sh
ββ Runtime shell behavior
.devcontainer/devcontainer.json ββ PATHs, aliases, functions
ββ Features, extensions ββ Sourced on every shell start
ββ Host env forwarding (localEnv)
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)
Dotfiles are bind-mounted from the host instead of cloned inside the container:
# .devcontainer/docker-compose.yml (dev service; <project> is filled in by
# the installer from your project folder name)
volumes:
- ..:/workspaces/<project>
- ${HOME}${USERPROFILE}/.config/dotfiles:/home/node/.config/dotfilesOptionally, the commented postCreateCommand in devcontainer.json
(curl -fsSL https://devmagic.run/setup | bash) installs extras (oh-my-zsh,
fzf) and links the mounted dotfiles' shell/init.sh into the container's
.bashrc/.zshrc.
This means:
- β Your machine: One dotfiles folder shared by the host and every container, always in sync
- β Someone else using DevMagic: Gets a working container (no dotfiles folder β Docker creates an empty one; nothing breaks)
- β 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 the templates (templates/devcontainer/), fills in
the project folder name (sanitized), and writes ready-to-use files into
.devcontainer/ in the current dir:
ββ devcontainer.json
ββ docker-compose.yml
ββ Dockerfile
(every shared value β mount path, Compose project name, hostname, image
tag β is baked in consistently; no .env, no placeholders left)
β
βΌ
User opens in VS Code and chooses "Reopen in Container"
β
βΌ
Container starts:
ββ Docker Compose builds the image from the Dockerfile
ββ devcontainer.json forwards host TZ/locale via ${localEnv:*}
ββ ~/.config/dotfiles is mounted from the host (optional)
ββ Optional postCreateCommand: curl -fsSL https://devmagic.run/setup | bash
β
βΌ
devcontainer-setup.sh (opt-in, commented out by default):
ββ Extra system packages (jq, ripgrep, fd, ...)
ββ oh-my-zsh and fzf
ββ Links dotfiles' shell/init.sh into .bashrc/.zshrc
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 templates from<br/>templates/devcontainer/"]
C --> C2["Fills in the project folder name and writes<br/>devcontainer.json, docker-compose.yml,<br/>Dockerfile into .devcontainer/"]
C2 --> D["User opens in VS Code<br/>and reopens in container"]
D --> E["Docker Compose builds<br/>the image from the Dockerfile"]
E --> F["devcontainer.json forwards host<br/>TZ/locale via localEnv"]
F --> G["~/.config/dotfiles mounted<br/>from the host (optional)"]
G --> H["Optional postCreateCommand:<br/>curl devmagic.run/setup | bash"]
H --> I["Extra system packages"]
H --> J["oh-my-zsh and fzf"]
H --> K["Links dotfiles shell/init.sh<br/>into .bashrc/.zshrc"]
style A fill:#e1f5ff
style E fill:#fff4e1
style H 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: ~/.config/dotfiles/apps/vscode/User/
ββ settings.json
ββ keybindings.json
β
βΌ (symlinked by install.sh)
Container: ~/.vscode-server/data/User/
ββ settings.json β ~/.config/dotfiles/apps/vscode/User/settings.json
ββ keybindings.json β ~/.config/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 the templates from
templates/devcontainer/, fills in the project folder name (sanitized to Compose naming rules), and writes ready-to-use files into the user's.devcontainer/ - Every value shared between devcontainer.json and docker-compose.yml is baked in consistently β no
.envfiles or placeholders left behind (see ADR 0005)
- Optional
postCreateCommand(ships commented out in devcontainer.json) - Installs extra packages, oh-my-zsh and fzf
- Links the mounted dotfiles'
shell/init.shinto.bashrc/.zshrcif present
- 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/
βββ templates/
β βββ devcontainer/ # The source of truth ({{PROJECT_NAME}} placeholder)
β βββ devcontainer.json
β βββ docker-compose.yml
β βββ Dockerfile
βββ .devcontainer/ # Filled copy for this repo (project name: devmagic),
β β # so standalone/maintainer clones work out of the box.
β β # Regenerate with ./setup/generate.sh after editing
β β # the templates.
β βββ devcontainer.json # Dev Container definition (Compose based)
β βββ docker-compose.yml # dev service + commented example service
β βββ Dockerfile # Dev image (typescript-node + CLI tools)
βββ setup/
β βββ devmagic.sh # Installer: downloads templates, fills project name
β βββ generate.sh # Fills templates locally (repo checkouts)
β βββ devcontainer-setup.sh # Optional container extras (opt-in postCreate)
βββ 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
dotfiles/ (separate repo, mounted at ~/.config/dotfiles)
βββ 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