dotm
A dotfile manager with composable roles, Tera templates, and host-specific overrides.
dotm organizes config files into packages (directories mirroring your home directory structure), groups them into roles (e.g. "desktop", "dev", "gaming"), and assigns roles to hosts. Deployment creates symlinks for plain files and copies for overrides/templates, so your dotfiles repo stays the single source of truth.
Installation
Or to install from the latest source:
Quick Start
# Initialize a dotm project
&&
# Create the root config
# Create a package
# Create a role
# Create a host config
# Deploy (dry run first)
Core Concepts
Packages
A package is a directory under packages/ that mirrors the target directory structure (usually ~). Files inside are symlinked to their corresponding locations during deployment.
packages/
├── shell/
│ ├── .bashrc
│ └── .bash_profile
└── editor/
└── .config/
└── nvim/
└── init.lua
Packages can declare dependencies and suggestions in dotm.toml:
[]
= "Editor configuration"
= ["shell"] # always pulled in
= ["theme"] # informational only
= "/" # override deploy target (default: ~)
Roles
A role groups packages together and can define variables for template rendering. Role configs live in roles/<name>.toml:
# roles/desktop.toml
= ["shell", "editor", "kde"]
[]
= "fancy"
= "3840x2160"
Hosts
A host config selects which roles to apply and can override variables. Host configs live in hosts/<hostname>.toml:
# hosts/workstation.toml
= "workstation"
= ["desktop", "gaming", "dev"]
[]
= "3840x2160"
= "amd"
Variable precedence: host vars > role vars (last role listed wins among roles).
Directory Structure
~/dotfiles/
├── dotm.toml # root config: package declarations
├── hosts/
│ ├── workstation.toml # workstation
│ └── dev-server.toml # server
├── roles/
│ ├── desktop.toml
│ ├── dev.toml
│ └── gaming.toml
└── packages/
├── shell/
│ ├── .bashrc # plain file → symlinked
│ ├── .bashrc##host.dev-server # host override → copied
│ └── .bashrc##role.dev # role override → copied
├── editor/
│ └── .config/nvim/
│ └── init.lua
└── kde/
└── .config/
├── rc.conf
└── rc.conf.tera # template → rendered & copied
File Overrides
Override files sit next to the base file with a ## suffix:
| Pattern | Priority | Description |
|---|---|---|
file##host.<hostname> |
1 (highest) | Used only on the named host |
file##role.<rolename> |
2 | Used when the role is active |
file.tera |
3 | Tera template, rendered with vars |
file |
4 (lowest) | Base file, symlinked |
- Override and template files are copied, not symlinked
- Only the highest-priority matching variant is deployed
- Non-matching overrides are ignored entirely
Templates
Files ending in .tera are rendered using Tera (a Jinja2-like template engine). Variables come from role and host configs:
# .config/app.conf.tera
resolution={{ display.resolution }}
{% if gpu.vendor == "amd" %}
driver=amdgpu
{% else %}
driver=modesetting
{% endif %}
The .tera extension is stripped from the deployed filename.
CLI Reference
dotm [OPTIONS] <COMMAND>
Options:
-d, --dir <DIR> Path to dotfiles directory [default: .]
-V, --version Print version
Commands:
deploy Deploy configs for the current host
undeploy Remove all managed symlinks and copies
status Show deployment status
check Validate configuration
init Initialize a new package
deploy
undeploy
status
check
init
Comparison
| Feature | dotm | GNU stow | yadm | dotter |
|---|---|---|---|---|
| Symlink-based | Yes | Yes | Yes | Yes |
| Role/profile system | Yes | No | No | Yes |
| Host-specific overrides | Yes | No | Alt files | Yes |
| Template rendering | Tera | No | Jinja2* | Handlebars |
| Dependency resolution | Yes | No | No | No |
| Per-package target dirs | Yes | Yes | No | No |
*yadm templates require a separate yadm alt step.
Disclaimer
Claude Code (Opus 4.6) was used for parts of the development of this tool, including some implementation, testing and documentation.
License
GNU AGPLv3