This step-by-step tutorial will guide you through setting up plowman and deploying your first dotfiles.
- Python 3.10 or higher installed
- Basic familiarity with command-line tools
- Some configuration files you want to manage
Install plowman using uv (recommended):
$ uv tool install plowmanVerify the installation:
$ plm --version
plowman 0.3.1If you don't have uv, see the Installation Guide for alternative methods.
Create a directory for your dotfiles:
$ mkdir -p ~/dotfiles/{bash,nvim,git}This creates a structure like:
~/dotfiles/
├── bash/
├── nvim/
└── git/
Each subdirectory (called a "granary") will contain related configuration files.
Let's start with a simple .bashrc file. Create it in the bash granary:
$ cat > ~/dotfiles/bash/.bashrc << 'EOF'
# My bash configuration
# Aliases
alias ll='ls -la'
alias gs='git status'
# Environment variables
export EDITOR=nvim
export HISTSIZE=10000
EOFAdd a simple git config:
$ cat > ~/dotfiles/git/.gitconfig << 'EOF'
[user]
name = Your Name
email = your.email@example.com
[core]
editor = nvim
[alias]
co = checkout
br = branch
ci = commit
st = status
EOFCreate the plowman configuration directory and file:
$ mkdir -p ~/.config/plowmanCreate ~/.config/plowman/config.yaml:
estates:
~/dotfiles:
granaries:
- bash
- gitThis tells plowman:
- Look in
~/dotfilesfor your dotfile repositories - Deploy files from the
bashandgitsubdirectories - No template variables needed yet (we'll add those later)
Before making any changes, use dry-run mode to see what would happen:
$ plm sow --dry-run
☑️ Would copy /home/user/dotfiles/bash/.bashrc to /home/user/.bashrc
☑️ Would copy /home/user/dotfiles/git/.gitconfig to /home/user/.gitconfigThis shows:
- Which files would be copied
- Source and destination paths
- No actual changes are made
Dry-run is always recommended before your first deployment!
Now deploy for real:
$ plm sowYou should see output like:
☑️ Copying /home/user/dotfiles/bash/.bashrc to /home/user/.bashrc
☑️ Copying /home/user/dotfiles/git/.gitconfig to /home/user/.gitconfig
Verify the files were deployed:
$ ls -la ~/.bashrc ~/.gitconfig
-rw-r--r-- 1 user user 123 Jun 24 10:00 /home/user/.bashrc
-rw-r--r-- 1 user user 234 Jun 24 10:00 /home/user/.gitconfigCheck the content:
$ cat ~/.bashrc
# My bash configuration
# Aliases
alias ll='ls -la'
alias gs='git status'
# Environment variables
export EDITOR=nvim
export HISTSIZE=10000plowman created an estate file to track deployed files:
$ cat ~/dotfiles/.plowman/estate.yml
files:
- .bashrc
- .gitconfigThis file:
- Tracks which files plowman manages
- Enables automatic cleanup of orphaned files
- Is automatically updated on each run
Now let's make our configuration dynamic using Jinja2 templates.
First, update your config to include variables:
# ~/.config/plowman/config.yaml
estates:
~/dotfiles:
granaries:
- bash
- git
variables:
username: alice
email: alice@example.com
editor: nvimCreate a template file. Rename .gitconfig to .gitconfig.j2 and use variables:
$ mv ~/dotfiles/git/.gitconfig ~/dotfiles/git/.gitconfig.j2Edit the template:
# ~/dotfiles/git/.gitconfig.j2
[user]
name = {{ username }}
email = {{ email }}
[core]
editor = {{ editor }}
[alias]
co = checkout
br = branch
ci = commit
st = statusMark it as a template by creating .plowman/plowman.yml:
$ mkdir -p ~/dotfiles/.plowman# ~/dotfiles/.plowman/plowman.yml
git:
templates:
- .gitconfig.j2This tells plowman that .gitconfig.j2 should be processed as a Jinja2 template.
Preview the changes:
$ plm sow --dry-run -v
☑️ Would copy /home/user/dotfiles/bash/.bashrc to /home/user/.bashrc
☑️ Would copy /home/user/dotfiles/git/.gitconfig.j2 to /home/user/.gitconfigNote that .gitconfig.j2 will be deployed as .gitconfig (the .j2 extension is removed).
Deploy for real:
$ plm sow
☑️ Copying /home/user/dotfiles/bash/.bashrc to /home/user/.bashrc
☑️ Copying /home/user/dotfiles/git/.gitconfig.j2 to /home/user/.gitconfigVerify the template was rendered:
$ cat ~/.gitconfig
[user]
name = alice
email = alice@example.com
[core]
editor = nvim
[alias]
co = checkout
br = branch
ci = commit
st = statusThe variables have been substituted!
See exactly what changed with verbose mode:
$ plm sow -vv
☑️ Copying /home/user/dotfiles/bash/.bashrc to /home/user/.bashrc
@@ -1,5 +1,5 @@
# My bash configuration
# Aliases
-alias ll='ls -l'
+alias ll='ls -la'
alias gs='git status'
# Environment variables
☑️ Copying /home/user/dotfiles/git/.gitconfig.j2 to /home/user/.gitconfigThe diff shows:
- Lines starting with
-are being removed - Lines starting with
+are being added - Context lines show surrounding content
Update your .bashrc in the granary:
$ cat >> ~/dotfiles/bash/.bashrc << 'EOF'
# Additional aliases
alias gp='git push'
alias gl='git pull'
EOFRe-deploy:
$ plm sow -v
☑️ Copying /home/user/dotfiles/bash/.bashrc to /home/user/.bashrcNotice that only the changed file was deployed. plowman uses SHA256 hashing to skip unchanged files, making re-deployment fast.
Remove a file from your granary:
$ rm ~/dotfiles/git/.gitconfig.j2Update the per-path config to remove the template reference:
# ~/dotfiles/.plowman/plowman.yml
git:
templates: [] # No templates nowRun plowman:
$ plm sow -v
🧹 Deleting /home/user/.gitconfigplowman detected that .gitconfig is no longer in your configuration and automatically removed it. This keeps your home directory clean and synchronized with your granaries.
Add a neovim configuration:
$ mkdir -p ~/dotfiles/nvim
$ cat > ~/dotfiles/nvim/init.vim << 'EOF'
" Neovim configuration
set number
set relativenumber
set tabstop=4
set shiftwidth=4
set expandtab
EOFUpdate your main config:
# ~/.config/plowman/config.yaml
estates:
~/dotfiles:
granaries:
- bash
- git
- nvim # Added!
variables:
username: alice
email: alice@example.com
editor: nvimDeploy:
$ plm sow -v
☑️ Copying /home/user/dotfiles/nvim/init.vim to /home/user/.config/nvim/init.vimNotice that plowman automatically created the ~/.config/nvim/ directory structure.
Create a more complex template with conditionals:
# ~/dotfiles/bash/.bash_profile.j2
# Bash profile for {{ username }}
# Platform-specific settings
{% if os == "darwin" %}
# macOS specific
export PATH="/usr/local/bin:$PATH"
export HOMEBREW_PREFIX="/usr/local"
{% elif os == "linux" %}
# Linux specific
export PATH="$HOME/.local/bin:$PATH"
{% endif %}
# User settings
export USER="{{ username }}"
export EMAIL="{{ email }}"
export EDITOR="{{ editor }}"
# Load .bashrc if it exists
if [ -f ~/.bashrc ]; then
source ~/.bashrc
fiUpdate config with OS variable:
estates:
~/dotfiles:
granaries:
- bash
- git
- nvim
variables:
username: alice
email: alice@example.com
editor: nvim
os: darwin # Change to "linux" on Linux systemsMark it as a template:
# ~/dotfiles/.plowman/plowman.yml
bash:
templates:
- .bash_profile.j2Deploy:
$ plm sow -v
☑️ Copying /home/user/dotfiles/bash/.bash_profile.j2 to /home/user/.bash_profileCheck the rendered result:
$ cat ~/.bash_profile
# Bash profile for alice
# Platform-specific settings
# macOS specific
export PATH="/usr/local/bin:$PATH"
export HOMEBREW_PREFIX="/usr/local"
# User settings
export USER="alice"
export EMAIL="alice@example.com"
export EDITOR="nvim"
# Load .bashrc if it exists
if [ -f ~/.bashrc ]; then
source ~/.bashrc
fiCongratulations! You've successfully:
- ✅ Installed plowman
- ✅ Created a dotfile repository structure
- ✅ Configured granaries and variables
- ✅ Deployed plain files and templates
- ✅ Used dry-run and verbose modes
- ✅ Seen automatic cleanup in action
- ✅ Created advanced templates with conditionals
Now that you know how to deploy configs with sow, learn how to collect changes back with harvest:
The harvest command does the opposite of sow - it collects changed files from your home directory back into your granaries.
When to use harvest:
- You manually edited a config file in HOME (not through your dotfiles repo)
- You want to sync changes made on one machine back to your central dotfiles repo
- You're setting up a new machine and need to collect existing configs
To use the -a/--add-to-estate feature, add names to your granaries:
# ~/dotfiles/.plowman/plowman.yml
bash:
name: myshell # Add this line
templates:
- .bashrc.j2
git:
name: mygit # Add this line
templates:
- .gitconfig.j2Let's say you edited .bashrc directly instead of through your dotfiles repo:
$ echo "# New alias" >> ~/.bashrc
$ echo "alias docker='sudo docker'" >> ~/.bashrcSee what would be harvested:
$ plm harvest --dry-run -v
☑️ Would harvest /home/user/.bashrc to /home/user/dotfiles/bash/.bashrcSee detailed diffs:
$ plm harvest --dry-run -vv
☑️ Would harvest /home/user/.bashrc to /home/user/dotfiles/bash/.bashrc
@@ -8,3 +8,5 @@
export HISTSIZE=10000
+
+# New alias
+alias docker='sudo docker'Collect the changes back to your granary:
$ plm harvest -v
☑️ Harvesting /home/user/.bashrc to /home/user/dotfiles/bash/.bashrcVerify the granary was updated:
$ tail -3 ~/dotfiles/bash/.bashrc
# New alias
alias docker='sudo docker'Now commit the harvested changes:
$ cd ~/dotfiles
$ git status
$ git add bash/.bashrc
$ git commit -m "Add docker alias from manual edit"
$ git pushSuppose you created a new config file manually:
$ cat > ~/.tmux.conf << 'EOF'
# TMUX configuration
set -g mouse on
set -g status-bg blue
EOFCopy it into the granary:
$ plm harvest -a myshell::/home/user/.tmux.conf -v
☑️ Harvesting /home/user/.tmux.conf to /home/user/dotfiles/bash/.tmux.confThis:
- Copies
/home/user/.tmux.confto~/dotfiles/bash/.tmux.conf - Leaves the estate unchanged until the next
sow, which discovers and tracks the new granary file
Verify:
$ ls ~/dotfiles/bash/.tmux.conf
/home/user/dotfiles/bash/.tmux.conf
$ plm sowHarvest makes it easy to sync configs between machines:
On Machine A (where you made changes):
# Harvest all manual changes
$ plm harvest -v
# Commit and push
$ cd ~/dotfiles
$ git add .
$ git commit -m "Sync from Machine A"
$ git pushOn Machine B:
# Pull latest changes
$ cd ~/dotfiles
$ git pull
# Deploy to Machine B
$ plm sow -vNow both machines have the same configuration!
Harvest handles template files automatically:
# HOME has: ~/.gitconfig (rendered, no .j2)
# Granary has: ~/dotfiles/git/.gitconfig.j2 (template source)
# Edit the rendered file
$ echo "[diff]\n tool = meld" >> ~/.gitconfig
# Harvest will update the .j2 template
$ plm harvest -v
☑️ Harvesting /home/user/.gitconfig to /home/user/dotfiles/git/.gitconfig.j2.j2 file directly in your granary instead.
Continue learning:
- Read the complete Usage Guide for all features
- Explore the Configuration Reference for advanced options
- Check the FAQ for common questions
- See Troubleshooting for help with issues
Store your granaries in Git:
$ cd ~/dotfiles
$ git init
$ git add .
$ git commit -m "Initial dotfiles setup"Add estate files to .gitignore:
# ~/dotfiles/.gitignore
.plowman/estate.ymlSync your dotfiles across machines:
- Push to GitHub/GitLab
- Clone on new machine
- Install plowman
- Run
plm sow
$ cp ~/.bashrc ~/.bashrc.backup
$ plm sowOr just use dry-run first:
$ plm sow --dry-run -vvKeep related configs together:
dotfiles/
├── shell/ # Shell configs
├── editor/ # Editor configs
├── git/ # Git configs
├── ssh/ # SSH configs
└── tools/ # Other tools
Happy dotfile farming! 🌾