I have a blog post about this project here.
β οΈ NOTE: It's outdated as previously everything including dotfiles was setup using Ansible. Now, we have a mix of Stow + Ansible.
Three layers, clear boundaries: Stow for dotfiles, Ansible for system setup, mise for developer tools.
- Stow manages all configuration files (dotfiles). Adding a new tool config
is as simple as creating a directory and running
stow. - Ansible handles system-level setup: base packages, services, Docker, fonts, terminal emulator, shell defaults - anything that is OS-specific or needs root.
- mise manages developer tools and language runtimes (Go, Node, Python, Rust, Neovim, ripgrep, fzf, lazygit, etc.). One config file, works the same on Arch, Fedora, and Ubuntu - no more per-distro task files for dev tools.
New machine setup (one command):
bash -c "$(curl -fsSL https://raw.githubusercontent.com/shricodev/dotfiles/main/bin/dotfiles)"This auto-detects your OS, installs Ansible, runs the playbook (which installs all packages and deploys dotfiles via stow).
Day-to-day dotfiles (no ansible needed):
# edit configs in place (already symlinked)
vim dots/nvim/.config/nvim/init.lua
# add a new tool
mkdir -p dots/wezterm/.config/wezterm
# add your config files...
stow -d dots -t ~ weztermYou can find the demo video setting up this dotfiles configuration on multiple ubuntu homelabs here: Link
- Git
- Supported OS: Arch Linux, Ubuntu, Fedora
- (Optional) Ansible for system setup (auto-installed by the bootstrap script)
- (Optional) Docker for testing in a container
Compatible with Ansible 2.20+. All system facts are accessed via
ansible_facts["distribution"], ansible_facts["user_dir"], etc.
.
βββ dots/ # Stow packages (dotfiles)
β βββ mise/ # ~/.config/mise/config.toml (dev tool list)
β βββ nvim/ # ~/.config/nvim/
β βββ tmux/ # ~/.config/tmux/
β βββ fish/ # ~/.config/fish/
β βββ kitty/ # ~/.config/kitty/
β βββ ghostty/ # ~/.config/ghostty/
β βββ alacritty/ # ~/.config/alacritty/
β βββ lazygit/ # ~/.config/lazygit/
β βββ starship/ # ~/.config/starship.toml
β βββ bat/ # ~/.config/bat/
β βββ zed/ # ~/.config/zed/
β βββ git/ # ~/.gitconfig, ~/.gitignore_global
β βββ gnupg/ # ~/.gnupg/
β βββ scripts/ # ~/.local/bin/
β βββ ... # (i3, picom, polybar, gtk, etc.)
βββ ansible/ # System automation
β βββ roles/ # Modular setup roles
β β βββ common/ # Base system packages
β β βββ docker/ # Docker engine (distro-specific)
β β βββ flatpak/ # Flatpak runtime
β β βββ fonts/ # System fonts
β β βββ ghostty/ # Terminal emulator (distro-specific)
β β βββ fish/ # Fish shell + set as default
β β βββ tmux/ # Tmux + plugin manager
β β βββ mise/ # Install mise + run mise install
β β βββ stow/ # Deploy dotfiles via stow
β β βββ cleanup/ # Remove orphaned packages
β βββ pre_tasks/ # Pre-setup tasks
β βββ group_vars/ # Variable configurations
β βββ inventory/ # Host inventory
β βββ configure_system.yml # System setup playbook
β βββ clean_up_system.yml # Cleanup playbook
β βββ main.yml # Master playbook
β βββ ansible.cfg # Ansible config
βββ bin/
β βββ dotfiles # Bootstrap script
βββ stow.sh # Helper to stow/unstow all packages
βββ Taskfile.yml # Task runner
βββ Dockerfile # Test environment
Each stow package mirrors the home directory structure. For example:
dots/nvim/.config/nvim/init.lua --> ~/.config/nvim/init.lua
dots/git/.gitconfig --> ~/.gitconfig
dots/mise/.config/mise/config.toml --> ~/.config/mise/config.toml
dots/scripts/.local/bin/foo --> ~/.local/bin/foo
Any user-facing config file: shell, editor, git, tmux, terminal, mise tool
list. If it lives under ~/.config/ or ~/, it belongs in dots/.
Anything that needs sudo, is OS-specific, or involves system services:
- Base system packages (git, curl, python, htop, etc.)
- Docker installation and service enablement
- Flatpak runtime
- System fonts
- Terminal emulators with distro-specific install flows (Ghostty)
- Setting the default shell (Fish)
- Tmux package + plugin manager bootstrap
- Installing the mise binary itself
Anything that is part of the development environment and should behave the same on Arch, Fedora, and Ubuntu:
- Language runtimes: Go, Node, Python, Rust, Bun
- CLI tools: neovim, ripgrep, fd, bat, fzf, eza, lazygit, starship, zoxide
- Go-based tools: air, goose, sesh, cobra-cli, delve
- Linters: golangci-lint, sqlc
- Infrastructure: terraform, uv
Decision rule: If it is part of the machine or OS β Ansible. If it is part of your development environment β mise.
# 1. Add to mise config
vim dots/mise/.config/mise/config.toml
# Add: "aqua:owner/repo" = "latest"
# 2. Install it
mise install
# 3. (Optional) If it has a config file, create a stow package
mkdir -p dots/toolname/.config/toolname
# Add config files, then: stow -d dots -t ~ toolnameAdd it to ansible/roles/common/vars/<distro>.yml or create a new Ansible
role if it needs distro-specific setup (repos, services, etc.).
# deploy all dotfiles
./stow.sh
# deploy a single package
stow -d dots -t ~ nvim
# remove symlinks
./stow.sh unstow
# re-deploy (unstow + stow)
./stow.sh restow# run locally (via Taskfile)
task local
# run on remote homelabs
task ubuntu_homelabsUpdate ansible/inventory/hosts.yml with your hosts and
ansible/group_vars/ with your configuration.
---
all:
children:
ubuntu_homelabs:
hosts:
# <place_your_hosts_here>
local:
hosts:
localhost:
ansible_connection: localFor remote hosts, set up SSH keys and update ansible/ansible.cfg:
[defaults]
inventory = inventory/hosts.yml
private_key_file = <place_ssh_key_path_here>
roles_path = roles| Command | Description |
|---|---|
task stow |
Deploy all dotfiles via stow |
task unstow |
Remove all dotfile symlinks |
task restow |
Re-deploy all dotfiles |
task mise |
Install all developer tools via mise |
task local |
Run Ansible locally (your machine) |
task ubuntu_homelabs |
Run Ansible on Ubuntu homelab machines |
task docker_build |
Build the dotfiles Docker image |
task docker_run |
Run the dotfiles Docker container |
task docker_build_and_run |
Build and run the Docker container |
Note
To test without installing anything on your system, run task docker_build_and_run.
bash dotfilesThis will install Ansible, clone the repo, and run the full playbook.
Logs are stored in ~/.dotfiles.log.
On first run, ~/.dotfiles_first_run_check is created. The script will
recommend a reboot to apply all changes.