Skip to content

Server Deployment Guide

How to deploy dotfiles to servers, containers, and CI/CD environments.


Deployment Modes

Mode Tools Installed Packages Use Case
Full All (mise.toml) Yes (run_once scripts) Personal workstations
Server Server-safe (mise-server.toml) Yes (run_once scripts) Dev servers, VMs
Dotfiles Only None (chezmoi only) No Shared servers, containers, CI

Shell Policy

  • Server deployments keep bash as the default stable baseline.
  • zsh remains available as an opt-in interactive shell.
  • Workstation installs can still switch default shell to zsh.

Decision Guide

Do you have root/sudo access?
├── Yes → Do you want desktop/UI tools?
│   ├── Yes → Full Mode
│   └── No  → Server Mode
└── No  → Dotfiles Only Mode

Deploying to Work/Corporate Servers?

For professional environments with security and compliance requirements, see the dedicated Work Environments Guide which covers:

  • Corporate policy considerations
  • Personal vs work separation strategies
  • Git identity management
  • Secrets isolation for work servers

Dotfiles Only (No Root Required)

The lightest deployment - applies config files without installing packages or tools.

Prerequisites

Install chezmoi:

sh -c "$(curl -fsLS get.chezmoi.io)"

Deploy

# Standard deployment
chezmoi init --apply jerdaw/dotfiles --exclude "run_once_*"

# Skip encrypted files (no age key available)
chezmoi init --apply jerdaw/dotfiles --exclude "run_once_*" --exclude encrypted

Minimal Clone (Slow Networks)

For deploying to many servers or slow connections:

git clone --filter=blob:none --sparse https://github.com/jerdaw/dotfiles ~/.local/share/chezmoi
cd ~/.local/share/chezmoi
git sparse-checkout set 'dot_*' '.chezmoi*'
chezmoi apply --exclude "run_once_*"

Server Mode (With Tools)

Installs server-safe tools via mise-server.toml, excluding desktop/UI applications.

What's Excluded

Server mode skips these desktop-only tools:

Excluded Tool Reason
yazi TUI file manager (interactive)
lazygit Git TUI (interactive)
lazydocker Docker TUI (interactive)
atuin Shell history sync daemon
glow Terminal markdown viewer
lolcat Rainbow text output
tinty Theme manager
pnpm, bun Extra JS runtimes
onefetch Git repo summary
lnav Log navigator

Deploy

# Clone the repository
git clone https://github.com/jerdaw/dotfiles.git ~/repos/dotfiles
cd ~/repos/dotfiles

# Run bootstrap with server profile
./scripts/bootstrap --server

# OR with a specific profile
./scripts/bootstrap --profile minimal

Profiles

The bootstrap script supports profiles to customize the installed toolset:

  • default: Installs everything in mise.toml (Workstation).
  • server: Installs tools from mise-server.toml (No GUI tools).
  • minimal: Installs dotfiles only, no tools.

Custom Profiles: You can create custom profiles in mise-profiles/<name>.toml and use them with --profile <name>.

Make Server Mode Permanent

To ensure mise always uses the server configuration:

ln -sf ~/repos/dotfiles/mise-server.toml ~/.config/mise/config.toml
mise install

Docker Deployment

Development Container

Use the provided example Dockerfile:

docker build -t dotfiles -f examples/Dockerfile .
docker run -it dotfiles

Multi-Stage Build

FROM ubuntu:24.04 AS base
RUN apt-get update && apt-get install -y curl git zsh

# Install chezmoi
RUN sh -c "$(curl -fsLS get.chezmoi.io)" -- -b /usr/local/bin

# Apply dotfiles (no packages, no encrypted files)
RUN chezmoi init --apply jerdaw/dotfiles \
    --exclude "run_once_*" \
    --exclude encrypted

CMD ["zsh"]

See examples/Dockerfile for a complete example.


Ansible Deployment

Use the provided playbook for fleet deployment:

ansible-playbook -i inventory examples/ansible-playbook.yml

The playbook:

  1. Installs chezmoi on target hosts
  2. Clones the dotfiles repository
  3. Applies dotfiles (excluding run_once scripts and encrypted files)
  4. Verifies key config files were created

See examples/ansible-playbook.yml for the full playbook.


CI/CD Deployment

Add dotfiles to your CI pipeline for consistent development environments:

# In your GitHub Actions workflow
- name: Install chezmoi
  run: sh -c "$(curl -fsLS get.chezmoi.io)" -- -b /usr/local/bin

- name: Apply dotfiles
  run: chezmoi init --apply jerdaw/dotfiles --exclude "run_once_*" --exclude encrypted

See examples/ci-deploy.yml for a complete reusable workflow.


Environment Variables

Variable Default Description
DOTFILES_DIR ~/repos/dotfiles Repository location
SERVER_MODE unset Set to 1 to skip desktop tool checks in doctor
MISE_CONFIG_FILE mise.toml Override to use mise-server.toml
DOTFILES_SKIP_CHSH unset Set to 1 to skip changing default shell
DOTFILES_SKIP_DOCTOR unset Set to 1 to skip health check after install

Verification

After deployment, verify the installation:

# Check key files exist
[ -f ~/.bashrc ] && echo "bashrc: OK" || echo "bashrc: MISSING"
[ -f ~/.zshrc ] && echo "zshrc: OK (optional)" || echo "zshrc: MISSING (optional)"
[ -f ~/.gitconfig ] && echo "gitconfig: OK" || echo "gitconfig: MISSING"
[ -d ~/.config ] && echo "config dir: OK" || echo "config dir: MISSING"

# If mise is installed, run health check
mise run doctor

# Server-specific health check (skips desktop tools)
SERVER_MODE=1 mise run doctor

Troubleshooting

Template Errors During Apply

Error: template: ...: undefined variable

Fix: Chezmoi templates may reference variables set during chezmoi init. Run with --promptDefaults for non-interactive defaults:

chezmoi init --apply jerdaw/dotfiles --exclude "run_once_*" --promptDefaults

Age Encryption Errors

Error: age: no identity matched any of the recipients

Fix: Skip encrypted files when no age key is available:

chezmoi apply --exclude encrypted

Permission Denied on Shared Servers

If you don't have write access to standard locations:

# Install chezmoi to a user-writable location
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b "$HOME/.local/bin"
export PATH="$HOME/.local/bin:$PATH"

# Apply dotfiles normally
chezmoi init --apply jerdaw/dotfiles --exclude "run_once_*"

Shell Strategy on Servers

By design, server mode keeps bash as default. If you want zsh on a specific host:

# Option 1: Set in SSH config on the client side
# ~/.ssh/config
# Host myserver
#   RequestTTY yes
#   RemoteCommand zsh -l

# Option 2: One-off per session
exec zsh