Skip to main content
Chezmoi is the orchestration layer that manages your dotfiles across machines. It handles file templating, encryption, and coordinates with Bitwarden and Ansible.

What is Chezmoi?

Chezmoi is a dotfiles manager that:
  • Maintains your dotfiles in a Git repository
  • Applies them to your home directory
  • Supports templating for per-machine customization
  • Integrates with password managers and encryption tools
  • Runs scripts to automate additional setup
Think of chezmoi as a “desired state engine” for your home directory, similar to how Kubernetes manages container deployments.

Configuration File

The .chezmoi.toml.tmpl file configures chezmoi’s behavior and stores machine-specific data.

Interactive Prompts

On first run, chezmoi asks questions to customize your environment:
.chezmoi.toml.tmpl
{{- $name := promptStringOnce . "name" "Your Name" "Julio Lira" -}}
{{- $machine_type := promptStringOnce . "machine_type" "Machine type (personal/work/hybrid)" "hybrid" -}}
{{- $os := promptStringOnce . "os" "Operating System" "linux" -}}
{{- $editor := promptStringOnce . "editor" "Default Editor" "code" -}}

{{- $personal_email := "" -}}
{{- if or (eq $machine_type "personal") (eq $machine_type "hybrid") -}}
{{-   $personal_email = promptStringOnce . "personal_email" "Personal Email" "" -}}
{{- end -}}

{{- $work_email := "" -}}
{{- if or (eq $machine_type "work") (eq $machine_type "hybrid") -}}
{{-   $work_email = promptStringOnce . "work_email" "Work Email" "" -}}
{{- end -}}
promptStringOnce caches answers in ~/.config/chezmoi/chezmoi.toml so you’re only asked once per machine.

Data Section

Prompted values are stored in the [data] section and become available to all templates:
[data]
    name = "Julio Lira"
    machine_type = "hybrid"
    os = "linux"
    editor = "code"
    personal_email = "[email protected]"
    work_email = "[email protected]"
You can access these in templates as:
echo "Hello {{ .name }}!"  # Output: Hello Julio Lira!

Editor Integration

Chezmoi configures VS Code for editing, diffing, and merging:
.chezmoi.toml.tmpl
[edit]
    command = "code"
    args = ["--wait"]

[diff]
    command = "code"
    args = ["--wait", "--diff"]

[merge]
    command = "bash"
    args = [
        "-c",
        "cp {{ .Target }} {{ .Target }}.base && code --new-window --wait --merge {{ .Destination }} {{ .Target }} {{ .Target }}.base {{ .Source }}",
    ]
This enables:
  • chezmoi edit ~/.bashrc - Opens in VS Code
  • chezmoi diff - Shows changes in VS Code diff view
  • chezmoi merge ~/.gitconfig - Three-way merge conflicts
Replace "code" with "nano", "vim", or your preferred editor.

Bitwarden Integration

Configuration

.chezmoi.toml.tmpl
[bitwarden]
    command = "bw"
    unlock = "auto"
This tells chezmoi to use the bw CLI and automatically prompt for unlock if the vault is locked.

Template Functions

Chezmoi provides two Bitwarden template functions:
Retrieves the entire item as JSON:
{{- $sshKey := bitwarden "item" "ssh-private-key" -}}
{{ $sshKey.notes }}
Use .notes for Secure Note contents or .login.password for passwords.
Bitwarden must be unlocked before running chezmoi apply. The bootstrap script handles initial authentication.

Age Encryption

Configuration

.chezmoi.toml.tmpl
encryption = "age"

[age]
    identity = "~/.config/chezmoi/key.txt"
    recipient = {{ output "age-keygen" "-y" (joinPath .chezmoi.homeDir ".config/chezmoi/key.txt") | trim | quote }}
  • identity: Your private key for decryption
  • recipient: Your public key for encryption (derived from the private key)

How It Works

1

Add Encrypted File

chezmoi add --encrypt ~/.ssh/config
Creates private_dot_ssh/config.age in the source directory.
2

Edit Encrypted File

chezmoi edit ~/.ssh/config
Decrypts, opens in editor, re-encrypts on save.
3

Apply to System

chezmoi apply
Decrypts .age files and writes plaintext to ~/.
The .age file is safe to commit to Git. Only someone with your private key at ~/.config/chezmoi/key.txt can decrypt it.

File Naming Conventions

Chezmoi uses special prefixes to control file handling:
PrefixEffectExample
dot_Becomes .dot_bashrc~/.bashrc
private_Permissions 0600private_dot_ssh/~/.ssh/ (mode 600)
executable_Permissions 0755executable_script.sh~/script.sh (executable)
.tmplTemplate processingdot_gitconfig.tmpl~/.gitconfig (rendered)
.ageAge encryptionconfig.ageconfig (decrypted on apply)

Combined Examples

private_dot_ssh/config.age           → ~/.ssh/config (mode 600, decrypted)
dot_bashrc.tmpl                      → ~/.bashrc (templated)
run_once_after_ansible.sh.tmpl      → Runs once after other scripts
Use chezmoi add --autotemplate <file> to automatically detect template variables in existing files.

Template Syntax

Chezmoi uses Go’s text/template engine:

Variable Substitution

dot_bashrc.tmpl
# User: {{ .name }}
export EDITOR={{ .editor }}
Rendered output:
# User: Julio Lira
export EDITOR=code

Conditionals

dot_gitconfig.tmpl
[user]
    name = {{ .name }}
{{- if .personal_email }}
    email = {{ .personal_email }}
{{- else if .work_email }}
    email = {{ .work_email }}
{{- end }}

Functions

# Execute commands
RECIPIENT={{ output "age-keygen" "-y" "~/.config/chezmoi/key.txt" | trim }}

# File operations
{{ include "scripts/helper.sh" }}

# Bitwarden
{{ (bitwarden "item" "ssh-key").notes }}
  • .name - User’s full name
  • .machine_type - personal, work, or hybrid
  • .os - Operating system
  • .editor - Default editor
  • .personal_email - Personal email address
  • .work_email - Work email address
  • .chezmoi.homeDir - Home directory path
  • .chezmoi.sourceDir - Chezmoi source directory
  • .chezmoi.username - Current username
  • .chezmoi.hostname - Machine hostname

Scripts

Chezmoi can run scripts at various lifecycle stages:

Script Types

Script NameWhen It Runs
run_once_*.shOnly once ever (tracked in state)
run_before_*.shBefore applying files
run_after_*.shAfter applying files
run_onchange_*.shWhen the script content changes

Example: Running Ansible

run_once_after_ansible.sh.tmpl
#!/bin/bash

echo "Running Ansible playbooks..."
cd {{ .chezmoi.sourceDir }}/ansible
ansible-playbook site.yml --ask-become-pass
This script:
  1. Uses the .tmpl extension for template processing
  2. Uses run_once_after to run once after files are applied
  3. Changes to the Ansible directory using {{ .chezmoi.sourceDir }}
  4. Executes the playbook
Scripts marked run_once_* create a state file at ~/.local/share/chezmoi/.chezmoistate.boltdb. If you need to re-run, use chezmoi state delete-bucket --bucket=entryState or rename the script.

Common Commands

# Clone and apply dotfiles in one command
chezmoi init --apply yurgenlira

# Or in two steps
chezmoi init yurgenlira
chezmoi apply

Directory Structure

~/.local/share/chezmoi/          # Source directory (Git repo)
├── .chezmoi.toml.tmpl           # Configuration
├── dot_bashrc.tmpl              # → ~/.bashrc
├── dot_gitconfig.tmpl           # → ~/.gitconfig
├── private_dot_ssh/
│   ├── config.age               # → ~/.ssh/config (encrypted)
│   └── id_ed25519.tmpl          # → ~/.ssh/id_ed25519 (from Bitwarden)
├── run_once_after_ansible.sh.tmpl
└── ansible/                     # Embedded Ansible configs

~/.config/chezmoi/
├── chezmoi.toml                 # Generated from .chezmoi.toml.tmpl
└── key.txt                      # Age private key (NOT in Git)

Best Practices

  • Never commit .config/chezmoi/key.txt
  • Use Bitwarden for credentials, not plaintext templates
  • Mark sensitive files with private_ prefix
  • Use age encryption for configs containing secrets
  • Use {{ .machine_type }} conditionals for hybrid setups
  • Keep complex logic in separate script files
  • Use include for shared template snippets
  • Comment template sections for clarity
  • Always run chezmoi diff before apply
  • Use --dry-run to preview script execution
  • Test in a VM before applying to production machines
  • Commit and push changes regularly

Next Steps

Ansible Integration

Learn how Ansible automates system configuration

Secrets Management

Deep dive into Bitwarden and age encryption

Customization Guide

Add your own packages and configurations

Official Docs

Chezmoi reference documentation

Build docs developers (and LLMs) love