32blogby StudioMitsu

Dotfiles y Variables de Entorno: Guía Práctica

Aprende a gestionar dotfiles y variables de entorno en Linux y macOS. Inicialización del shell, PATH, XDG Base Directory, GNU Stow, chezmoi y direnv.

17 min read
Contenido

Los dotfiles son los archivos de configuración ocultos que definen cómo se comportan tu shell, editor, Git y docenas de otras herramientas. Las variables de entorno son los valores que esos archivos establecen en tiempo de ejecución — PATH, EDITOR, claves API, configuración de localización. Juntos, forman tu entorno de desarrollo, y gestionarlos bien significa poder recrear tu setup en cualquier máquina en minutos.

En resumen: mete tus dotfiles en un repo Git, usa GNU Stow o chezmoi para crear symlinks, sigue la especificación XDG Base Directory para mantener $HOME limpio, y usa direnv para variables específicas por proyecto. Esta guía te lleva paso a paso con ejemplos reales.

Por qué importa tu configuración del shell

Cada vez que abres una terminal, el shell lee un conjunto de archivos de configuración para preparar tu entorno. Estos archivos controlan todo: desde la apariencia del prompt hasta qué comandos están disponibles. Si nunca los has gestionado intencionalmente, probablemente has acumulado años de adiciones aleatorias — aliases copiados de Stack Overflow, entradas PATH de instaladores, y líneas de export que copiaste sin entender.

El problema aparece cuando:

  • Configuras una máquina nueva — pasas horas reconstruyendo tu entorno de memoria, olvidando la mitad
  • Entras por SSH a un servidor — ninguno de tus aliases ni funciones existe y te sientes perdido
  • Depuras un pipeline de CI — el build falla porque una variable de entorno está configurada diferente que en tu máquina local
  • Colaboras — el shell de tu compañero se comporta completamente diferente, haciendo el debugging en pareja un dolor

Me di cuenta de esto cuando configuré el entorno de desarrollo de 32blog en tres máquinas — mi workstation principal, un portátil y un VPS. No tenía idea de cuál .bashrc era el "real". Algunas máquinas tenían aliases que había olvidado, otras les faltaban herramientas que usaba a diario. Ahí fue cuando empecé a gestionar los dotfiles en serio.

Inicialización del shell: qué archivo se carga y cuándo

Aquí es donde la mayoría se confunde. Bash y Zsh cargan archivos diferentes según cómo se inicia el shell, y equivocarte significa que tus variables de entorno silenciosamente no se aplican.

Orden de carga en Bash

Bash distingue entre shells de login (cuando entras a una máquina vía SSH, TTY, o bash --login) y shells interactivos sin login (cuando abres un emulador de terminal):

bash
# Shell de login lee estos archivos:
# 1. /etc/profile         — configuración global del sistema (siempre se lee)
# Luego busca UNO de estos (se detiene en el primero que encuentra):
# 2. ~/.bash_profile      — configuración de login del usuario
# 3. ~/.bash_login        — alternativa si .bash_profile no existe
# 4. ~/.profile           — alternativa si ninguno de los anteriores existe

# Shell interactivo sin login lee:
# 1. /etc/bash.bashrc     — global del sistema (algunas distros)
# 2. ~/.bashrc            — tu configuración personal

La trampa principal: .bashrc NO se lee en shells de login, y .bash_profile NO se lee en shells sin login. Por eso casi todas las guías de dotfiles recomiendan poner esta línea en .bash_profile:

bash
# ~/.bash_profile
# Source .bashrc para que los shells de login tengan la misma config
[[ -f ~/.bashrc ]] && source ~/.bashrc

Así pones toda tu configuración real en .bashrc y usas .bash_profile solo para hacer source. Un único punto de verdad.

Orden de carga en Zsh

Zsh es un poco más predecible:

bash
# Todas las sesiones Zsh:
# 1. /etc/zshenv      → ~/.zshenv        — siempre se carga, incluso para scripts

# Shells de login también cargan:
# 2. /etc/zprofile    → ~/.zprofile       — configuración específica de login
# 3. /etc/zshrc       → ~/.zshrc          — configuración interactiva
# 4. /etc/zlogin      → ~/.zlogin         — después de .zshrc

# Shells interactivos sin login:
# 1. /etc/zshenv      → ~/.zshenv
# 2. /etc/zshrc       → ~/.zshrc

# Al cerrar sesión:
# ~/.zlogout → /etc/zlogout

El dato clave: .zshenv siempre se carga — incluso para ejecución de scripts no interactivos. Esto lo convierte en el lugar correcto para variables de entorno que deben estar siempre disponibles. .zshrc es para configuración interactiva como aliases, prompt y atajos de teclado.

bash
# ~/.zshenv — variables de entorno (siempre se carga)
export EDITOR="nvim"
export LANG="es_ES.UTF-8"

# ~/.zshrc — solo interactivo (prompt, aliases, completions)
autoload -Uz compinit && compinit
alias ll='ls -lah'

Resumen visual

┌──────────────────────────────────────────────────┐
│                  BASH                            │
│                                                  │
│  Login:          /etc/profile → ~/.bash_profile  │
│  Interactivo:    /etc/bash.bashrc → ~/.bashrc    │
│  Script:         (ninguno por defecto)           │
├──────────────────────────────────────────────────┤
│                  ZSH                             │
│                                                  │
│  Siempre:        /etc/zshenv → ~/.zshenv         │
│  Login:          + ~/.zprofile → ~/.zshrc        │
│  Interactivo:    + ~/.zshrc                      │
│  Script:         solo ~/.zshenv                  │
└──────────────────────────────────────────────────┘

Si escribes scripts de shell, mira la guía de shell scripting para más detalles sobre cómo el shell procesa estos archivos al ejecutar scripts.

Variables de entorno en la práctica

Las variables de entorno son pares clave-valor que los procesos hijos heredan. Cuando haces export de una variable en tu shell, cada comando que ejecutes después puede leerla.

Establecer y exportar

bash
# Establecer una variable (local a este shell solamente)
MY_VAR="hello"

# Exportarla (los procesos hijos la heredan)
export MY_VAR="hello"

# Establecer y exportar en una línea (lo más común)
export EDITOR="nvim"
export GOPATH="$HOME/go"

# Ver todas las variables de entorno
env
# o
printenv

# Verificar una variable específica
echo $PATH
printenv PATH

La diferencia entre set y export confunde a mucha gente. Una variable que no ha sido exportada solo existe en el shell actual — no es visible para los comandos que ejecutes ni para los scripts.

PATH: La variable más importante

PATH es una lista de directorios separados por dos puntos donde el shell busca ejecutables. Cuando escribes git, el shell busca en cada directorio de PATH de izquierda a derecha hasta encontrar una coincidencia.

bash
# PATH típico en un sistema Linux
echo $PATH
# /home/furuya/.local/bin:/usr/local/bin:/usr/bin:/bin

# Añadir un directorio a PATH (al principio — mayor prioridad)
export PATH="$HOME/.local/bin:$PATH"

# Añadir al final (menor prioridad)
export PATH="$PATH:$HOME/go/bin"

Un patrón que uso en mi .bashrc:

bash
# ~/.bashrc — construcción de PATH
# Solo añadir directorios que existen
add_to_path() {
  [[ -d "$1" ]] && [[ ":$PATH:" != *":$1:"* ]] && export PATH="$1:$PATH"
}

add_to_path "$HOME/.local/bin"
add_to_path "$HOME/.cargo/bin"
add_to_path "$HOME/go/bin"
add_to_path "$HOME/.npm-global/bin"

Esta función add_to_path evita duplicados y salta directorios que no existen. Después de años modificando .bashrc, la duplicación de PATH es un problema real — una vez tenía el mismo directorio en PATH seis veces porque cada instalador lo añadía sin verificar.

Variables de entorno comunes

VariablePropósitoEjemplo
PATHRuta de búsqueda de ejecutables/usr/local/bin:/usr/bin
HOMEDirectorio home del usuario/home/furuya
EDITOREditor de texto por defectonvim
VISUALEditor visual (con GUI)code --wait
SHELLShell de login del usuario/bin/zsh
LANGConfiguración de localizaciónes_ES.UTF-8
TERMTipo de terminalxterm-256color
XDG_CONFIG_HOMEDirectorio de configuración~/.config
PAGERPaginador para man, etc.less
GPG_TTYTTY para firma GPG$(tty)

Secretos y archivos .env

Nunca pongas secretos en tus archivos de configuración del shell. .bashrc y .zshrc a menudo acaban en repos Git (tus dotfiles), y secretos en control de versiones es un desastre esperando a ocurrir.

Para secretos del proyecto, usa archivos .env:

bash
# .env (NO se commitea a Git)
DATABASE_URL="postgres://user:pass@localhost:5432/32blog_dev"
STRIPE_SECRET_KEY="sk_test_abc123"
NEXTAUTH_SECRET="random-secret-here"

Asegúrate de que .env esté en tu .gitignore, y crea un .env.example con valores de ejemplo:

bash
# .env.example (se commitea a Git — sin valores reales)
DATABASE_URL="postgres://user:pass@localhost:5432/mydb"
STRIPE_SECRET_KEY="sk_test_..."
NEXTAUTH_SECRET="generate-with-openssl-rand"

Para secretos personales que necesitas en tu entorno de shell (como tokens API para herramientas CLI), considera usar el llavero del SO o una herramienta como pass (el gestor de contraseñas estándar de Unix):

bash
# Guardar un secreto
pass insert dev/github-token

# Usarlo en tu configuración del shell
export GITHUB_TOKEN=$(pass show dev/github-token)

XDG Base Directory: ordenar $HOME

Ejecuta ls -la ~ y probablemente verás un desastre de dotfiles. La especificación XDG Base Directory resuelve esto definiendo ubicaciones estándar para diferentes tipos de archivos:

VariablePor defectoPropósito
XDG_CONFIG_HOME~/.configArchivos de configuración
XDG_DATA_HOME~/.local/shareDatos de aplicaciones
XDG_STATE_HOME~/.local/stateDatos de estado (logs, historial)
XDG_CACHE_HOME~/.cacheDatos en caché (se pueden borrar)
XDG_RUNTIME_DIR/run/user/$UIDArchivos de runtime

Muchas herramientas modernas ya respetan XDG. Por ejemplo:

~/.config/git/config       # en lugar de ~/.gitconfig
~/.config/htop/htoprc      # configuración de htop
~/.config/btop/btop.conf   # configuración de btop
~/.config/nvim/init.lua    # configuración de Neovim

Establece estas variables en tu .zshenv o .bash_profile:

bash
export XDG_CONFIG_HOME="$HOME/.config"
export XDG_DATA_HOME="$HOME/.local/share"
export XDG_STATE_HOME="$HOME/.local/state"
export XDG_CACHE_HOME="$HOME/.cache"

Para herramientas que no soportan XDG nativamente, a menudo puedes redirigirlas con variables de entorno:

bash
# Forzar herramientas a usar directorios XDG
export HISTFILE="$XDG_STATE_HOME/bash/history"
export LESSHISTFILE="$XDG_STATE_HOME/less/history"
export NPM_CONFIG_USERCONFIG="$XDG_CONFIG_HOME/npm/npmrc"
export DOCKER_CONFIG="$XDG_CONFIG_HOME/docker"
export CARGO_HOME="$XDG_DATA_HOME/cargo"
export GOPATH="$XDG_DATA_HOME/go"
export RUSTUP_HOME="$XDG_DATA_HOME/rustup"

La página XDG del Arch Wiki mantiene una lista completa de qué herramientas soportan XDG y cómo configurar las que no. La consulto cada vez que instalo una herramienta CLI nueva.

Gestionar dotfiles con Git y GNU Stow

Una vez que tus dotfiles están organizados, necesitas una forma de rastrear cambios, compartirlos entre máquinas y desplegarlos con un solo comando. Hay tres enfoques populares.

Enfoque 1: GNU Stow (recomendado por simplicidad)

GNU Stow es un gestor de symlinks. Organizas los dotfiles en un directorio que refleja la estructura de tu home, y Stow crea los symlinks por ti.

bash
# Instalar GNU Stow (2.4.1 a fecha de 2024)
sudo apt install stow     # Debian/Ubuntu
sudo pacman -S stow        # Arch
brew install stow          # macOS

Configurar la estructura de directorios:

bash
mkdir -p ~/dotfiles
cd ~/dotfiles
git init

# Crear directorios que reflejan dónde deben vivir los archivos
mkdir -p bash/.config
mkdir -p git/.config/git
mkdir -p nvim/.config/nvim
mkdir -p tmux/.config/tmux

Mover tus archivos de configuración al directorio de Stow:

bash
# Mover .bashrc al paquete bash
mv ~/.bashrc ~/dotfiles/bash/

# Mover configuración de Git
mv ~/.config/git/config ~/dotfiles/git/.config/git/config

# Mover configuración de Neovim
mv ~/.config/nvim/init.lua ~/dotfiles/nvim/.config/nvim/init.lua

Desplegar con Stow:

bash
cd ~/dotfiles

# Stow cada paquete — crea symlinks en $HOME
stow bash       # ~/.bashrc → ~/dotfiles/bash/.bashrc
stow git        # ~/.config/git/config → ~/dotfiles/git/.config/git/config
stow nvim       # ~/.config/nvim/init.lua → ~/dotfiles/nvim/.config/nvim/init.lua
stow tmux

# Stow todo a la vez
stow */

# Eliminar symlinks de un paquete
stow -D bash

# Re-stow (eliminar y recrear — útil al añadir archivos)
stow -R bash

La belleza de Stow es que es transparente — tus herramientas no saben que están leyendo archivos con symlinks. Y como el directorio dotfiles es un repo Git normal, tienes historial completo de cambios.

bash
cd ~/dotfiles
git add -A
git commit -m "feat: add initial dotfiles"
git remote add origin git@github.com:tu-usuario/dotfiles.git
git push -u origin main

Configurar una máquina nueva se reduce a:

bash
git clone git@github.com:tu-usuario/dotfiles.git ~/dotfiles
cd ~/dotfiles
stow */

Enfoque 2: chezmoi (para setups complejos)

chezmoi (v2.70.0) es un gestor de dotfiles escrito en Go con plantillas integradas, gestión de secretos y soporte multi-máquina. Es más complejo que Stow pero maneja escenarios que Stow no puede:

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

# Inicializar desde un repo existente de dotfiles
chezmoi init --apply tu-usuario

# Añadir un archivo a la gestión de chezmoi
chezmoi add ~/.bashrc

# Editar un archivo gestionado
chezmoi edit ~/.bashrc

# Ver qué cambiaría
chezmoi diff

# Aplicar cambios
chezmoi apply

La función estrella de chezmoi son las plantillas. Puedes tener una plantilla de .bashrc que produzca salidas diferentes en cada máquina:

bash
# ~/.local/share/chezmoi/dot_bashrc.tmpl
export EDITOR="nvim"

{{ if eq .chezmoi.hostname "work-laptop" }}
export HTTP_PROXY="http://proxy.corp.example.com:8080"
{{ end }}

{{ if eq .chezmoi.os "darwin" }}
eval "$(/opt/homebrew/bin/brew shellenv)"
{{ end }}

Enfoque 3: Repositorio Git bare

El enfoque bare repo usa Git directamente sin herramientas adicionales:

bash
# Inicializar
git init --bare $HOME/.dotfiles

# Crear un alias
alias dotfiles='git --git-dir=$HOME/.dotfiles --work-tree=$HOME'

# Ignorar archivos no rastreados
dotfiles config --local status.showUntrackedFiles no

# Usarlo como git normal
dotfiles add ~/.bashrc
dotfiles commit -m "add bashrc"
dotfiles push

Es el enfoque más ligero — sin dependencias más allá de Git. Pero se vuelve incómodo cuando necesitas excluir archivos o gestionar múltiples máquinas.

¿Cuál elegir?

CaracterísticaGNU StowchezmoiBare repo
DependenciasPerl (preinstalado)Binario GoSolo Git
PlantillasNoSí (Go templates)No
Gestión de secretosNoSí (age, gpg, keyring)No
Multi-máquinaManualIntegradoManual
Curva de aprendizajeBajaMediaBaja
RollbackHistorial GitDiff/apply integradoHistorial Git

Mi recomendación: empieza con GNU Stow. Hace una cosa bien (symlinks), y siempre puedes migrar a chezmoi después si necesitas plantillas o gestión de secretos. Llevo dos años usando Stow para mis dotfiles personales y no he necesitado nada más.

direnv: variables de entorno por proyecto

direnv (v2.37.1) carga y descarga automáticamente variables de entorno cuando haces cd a un directorio de proyecto. En lugar de esparcir sentencias export por tu configuración del shell, cada proyecto tiene su propio archivo .envrc.

Instalación y configuración

bash
# Instalar direnv
sudo apt install direnv     # Debian/Ubuntu
sudo pacman -S direnv       # Arch
brew install direnv          # macOS

# Engancharlo a tu shell (añadir a .bashrc o .zshrc)
eval "$(direnv hook bash)"    # para Bash
eval "$(direnv hook zsh)"     # para Zsh

Uso básico

bash
# Crear un .envrc en tu proyecto
cd ~/projects/32blog
echo 'export NODE_ENV="development"' > .envrc

# direnv lo bloquea hasta que lo apruebes (función de seguridad)
direnv: error /home/furuya/projects/32blog/.envrc is blocked.
Run `direnv allow` to approve its content.

# Aprobarlo
direnv allow

# Ahora NODE_ENV está establecido dentro de este directorio
echo $NODE_ENV
# development

# Sales del directorio — la variable se descarga automáticamente
cd ~
echo $NODE_ENV
# (vacío)

Patrones .envrc del mundo real

bash
# ~/projects/32blog/.envrc

# Cargar archivo .env (patrón común)
dotenv

# Añadir binarios locales del proyecto al PATH
PATH_add node_modules/.bin
PATH_add .bin

# Establecer versiones de herramientas específicas del proyecto
export NODE_VERSION="22.14.0"
use node

# Source de una configuración compartida del equipo
source_env ../.shared-env

# Perfil AWS para este proyecto
export AWS_PROFILE="32blog-prod"

El comando dotenv carga un archivo .env automáticamente. Esto significa que tu archivo .env (con secretos, no commiteado) y tu archivo .envrc (sin secretos, commiteado) trabajan juntos:

bash
# .envrc (commiteado a Git)
dotenv
PATH_add node_modules/.bin
export NODE_ENV="development"

# .env (NO commiteado — en .gitignore)
DATABASE_URL="postgres://..."
API_SECRET="..."

direnv con proyectos Node.js

Cuando trabajo en 32blog (un proyecto Next.js), mi .envrc es así:

bash
# ~/projects/32blog/.envrc
dotenv                         # cargar .env
PATH_add node_modules/.bin     # usar eslint, prettier locales, etc.
export NEXT_TELEMETRY_DISABLED=1

Esto significa que puedo ejecutar next dev o eslint . sin npx — el node_modules/.bin local está en mi PATH solo cuando estoy dentro del directorio del proyecto.

FAQ

¿Cuál es la diferencia entre .bashrc y .bash_profile?

.bash_profile lo lee el shell de login (sesiones SSH, login TTY, bash --login). .bashrc lo lee el shell interactivo sin login (al abrir un emulador de terminal). La práctica estándar es hacer source de .bashrc desde .bash_profile para mantener un solo archivo.

¿Debería usar Bash o Zsh?

Cualquiera funciona. Zsh tiene mejor autocompletado, globbing y soporte de plugins de serie. macOS trae Zsh por defecto desde Catalina (2019). Bash es más portable y es el shell estándar en la mayoría de distribuciones Linux. Si escribes scripts para otros, apunta a Bash. Para tu shell interactivo personal, usa el que te resulte más cómodo. Mira la guía de shell scripting para más sobre portabilidad de scripts.

¿Cómo comparto dotfiles entre máquinas con diferentes SO?

Usa las plantillas de chezmoi para incluir configuración específica del SO condicionalmente. O con GNU Stow, crea paquetes separados (bash-linux, bash-macos) y haz stow solo del relevante. Otro enfoque: usa sentencias if en tu .bashrc:

bash
if [[ "$(uname)" == "Darwin" ]]; then
  eval "$(/opt/homebrew/bin/brew shellenv)"
fi

¿Puedo usar direnv con Docker?

direnv se ejecuta en tu shell del host, no dentro de contenedores. Pero puedes usar el archivo .env que carga el comando dotenv de direnv con la directiva env_file de Docker Compose — usan el mismo formato:

yaml
# docker-compose.yml
services:
  app:
    env_file: .env

¿Dónde debo poner las adiciones a PATH?

En .zshenv si usas Zsh (se carga para todos los tipos de shell incluidos scripts). En .bashrc si usas Bash (asegurándote de que .bash_profile haga source de .bashrc). Evita establecer PATH en múltiples archivos — causa duplicados y dificulta la depuración.

¿Cómo mantengo los secretos fuera de mi repo de dotfiles?

Usa .gitignore para excluir .env y otros archivos con secretos. Para secretos que necesitas en tu entorno de shell, usa un gestor de contraseñas como pass o el llavero del SO. chezmoi tiene soporte integrado para cifrado age, 1Password, Bitwarden y otros gestores de secretos.

¿Qué es XDG y lo necesito?

XDG Base Directory es una especificación de freedesktop.org que define dónde deben vivir los archivos de configuración, datos, caché y estado. No es estrictamente necesario, pero seguirla mantiene tu directorio $HOME limpio y facilita la gestión de dotfiles. Empieza estableciendo las variables XDG y migra herramientas poco a poco.

¿Cómo gestiono configuraciones específicas como .gitconfig o .vimrc?

Muévelas a tu repo de dotfiles bajo el paquete Stow apropiado. Para Git específicamente, la ruta compatible con XDG es ~/.config/git/config. Para tmux, es ~/.config/tmux/tmux.conf (tmux 3.2+). Consulta la documentación de cada herramienta para soporte XDG — la página XDG del Arch Wiki es la mejor referencia.

Conclusión

Los dotfiles y las variables de entorno son la base de tu entorno de desarrollo. Organizarlos bien te recompensa cada vez que configuras una máquina nueva, depuras un pipeline de CI, o incorporas a alguien a tu proyecto.

Empieza simple: mete tu .bashrc o .zshrc y .gitconfig en un repo Git, usa GNU Stow para crear symlinks, y añade direnv para variables específicas por proyecto. Con eso tienes el 90% del valor con el 10% del esfuerzo. Pasa a chezmoi cuando necesites plantillas o soporte multi-SO.

El orden de inicialización del shell vale la pena memorizarlo — explica la mayoría de misterios de "¿por qué no se establece mi variable?". Y vale la pena adoptar XDG Base Directory gradualmente, aunque solo sea por la satisfacción de ver un ls ~ limpio.

Para temas relacionados, mira la guía de aliases para organizar aliases del shell, la guía de tmux para configuración del multiplexor de terminal, y la guía de shell scripting para escribir scripts portables que respeten estas convenciones.