preparing a production-ready zellij setup: storing configuration, layout, theme, and plugins in a dotfiles repo, an automatic install script, and standardizing team workflows across linux, macos, and windows.

All the configuration you've built over the past 26 episodes — config.kdl, KDL layouts, themes, keybindings, up to plugins — is valuable precisely because it can be carried anywhere. Unfortunately, many people leave that configuration piled on a local machine: not version-controlled, not documented, and not reproducible. Episode 27 changes that: you'll prepare a production-ready, portable, and deterministic Zellij setup.
What does "production-ready" mean in this context? Three things: (1) your configuration lives in a dotfiles repo and can be deployed to a new machine in minutes, (2) that configuration is safe and deterministic — the same result wherever and whenever it's run, and (3) your workflow can be standardized for a team, not just for yourself. This episode covers the portability and reproducibility side that hasn't been touched on in detail before.
This episode's plan: (1) the dotfiles repo structure and what goes into it, (2) an automatic install script that handles bootstrap from scratch, (3) the Linux, macOS, and Windows platform differences and how to tame them, (4) automatic session persistence and safe configuration, and (5) team workflow standardization. By the end of the episode, you'll have a complete blueprint usable on your work machine, personal machine, and production servers.
Zellij's configuration directory, ~/.config/zellij/, is home to almost everything you customize: config.kdl, and the layouts/, themes/, and plugins/ subdirectories. A standard dotfiles repo structure for Zellij looks like this:
dotfiles/
├── zellij/
│ ├── config.kdl
│ ├── layouts/
│ │ ├── dev.kdl
│ │ └── server.kdl
│ ├── themes/
│ │ └── arman-dark.kdl
│ └── plugins/
│ └── status-bar.wasmThe simple rule: never put configuration that only applies to one machine into the repo. Font sizes, truly personal keybindings, and absolute project paths should be stored as variables or per-project layouts, not hardcoded. Conversely, everything generic — themes, team-default keybindings, shared project layouts, commonly used plugins — deserves a place in the repo.
| Goes in the dotfiles repo | Doesn't go in the dotfiles repo |
|---|---|
Customized config.kdl | Secrets and API keys |
| Shared project layouts | Absolute local project paths |
| Team custom themes | Per-machine font sizes |
| Shared WASM plugins | Truly personal keybindings |
| Bootstrap install script | Logs and cache files |
The rule of thumb: if two developers can use it without conflict, it goes in the repo; if it only applies to one machine, leave it out. Separating the two from the start is far cheaper than cleaning up a repo already contaminated with personal values.
A good first step is making sure your configuration is valid and complete before committing:
zellij setup --dump-config > ~/.config/zellij/config.kdl
zellij setup --checkzellij setup --check runs a series of environment and configuration checks — green output means your setup is healthy. This verification is ideal as part of the install script, so every new machine is confirmed healthy before it's used.
Note
Start the dotfiles repo from configuration you actually use, not from the defaults. Dumping zellij setup --dump-config produces a complete file that's useful as a reference, but your repo should contain the subset you've modified. A compact repo is easier to review and less likely to collide with configuration changes in newer Zellij versions.
Dotfiles are only useful if they can be deployed easily. For that, build an install script that: (1) installs the Zellij binary per platform, (2) links or copies the zellij/ directory from the repo to the config location, and (3) runs verification. This script should run on a new machine without excessive interaction. For the installation part, let the script choose the right method per platform:
curl -sSfL https://install.zellij.dev | bash#!/usr/bin/env bash
set -euo pipefail
REPO="$(cd "$(dirname "$0")/.." && pwd)"
CONFIG_DIR="${ZELLIJ_CONFIG_DIR:-$HOME/.config/zellij}"
command -v zellij >/dev/null 2>&1 || curl -sSfL https://install.zellij.dev | bash
mkdir -p "$CONFIG_DIR"
ln -sfn "$REPO/zellij/config.kdl" "$CONFIG_DIR/config.kdl"
ln -sfn "$REPO/zellij/layouts" "$CONFIG_DIR/layouts"
ln -sfn "$REPO/zellij/themes" "$CONFIG_DIR/themes"
ln -sfn "$REPO/zellij/plugins" "$CONFIG_DIR/plugins"
zellij setup --checkThe symlink approach ensures changes in the repo are immediately reflected in $CONFIG_DIR without re-copying. For more structured usage, GNU Stow (stow zellij) can replace the one-by-one ln lines. If you prefer copying over linking, use cp -r — with the trade-off that every repo update requires re-copying.
After bootstrap runs, the last optional step is auto-start: zellij setup --generate-auto-start zsh produces a snippet that starts Zellij automatically on every login shell — exactly the pattern tmux users often use to land in a session. If you choose this pattern, make sure default_mode is set to locked (episode 22) so a non-Zellij login shell doesn't open an unwanted input door.
Why use ZELLIJ_CONFIG_DIR? Because this environment variable is the key to portability. Instead of relying on default locations that differ per platform, you can set one consistent location:
export ZELLIJ_CONFIG_DIR="$HOME/.config/zellij"With this variable set, Zellij looks for configuration in the same location on Linux, macOS, and Windows — and your dotfiles repo stays single, with no branching.
Each platform has different default locations and behaviors. Understanding these differences is half the portability battle:
| Aspect | Linux | macOS | Windows |
|---|---|---|---|
| Default config dir | ~/.config/zellij | ~/Library/Application Support/org.Zellij-Contributors.Zellij | %USERPROFILE%\.config\zellij |
| Data & plugins | ~/.local/share/zellij/plugins | Under Application Support | %APPDATA% (Roaming) |
| Installation | Installer/apt/dnf/Cargo | brew install zellij | winget install zellij.zellij |
| Copy command | wl-copy/xclip | pbcopy | PowerShell Set-Clipboard |
It's these default-location differences that make ZELLIJ_CONFIG_DIR so important. By setting it in your shell profile, the three platforms share one config path and one dotfiles repo. For the remaining differences, copy_command is a perfect example: you can tailor it per platform without changing the repo structure — for example copy_command "wl-copy" on Wayland, "xclip -selection clipboard" on X11, "pbcopy" on macOS, and Set-Clipboard on Windows.
Linux is the most flexible place, since Zellij is also available through distribution package managers and Cargo. macOS is convenient with Homebrew. Windows, since 0.44, runs natively without WSL — though some tooling like copy_command needs to be adapted to the PowerShell world. The key to all of it: don't hardcode platform assumptions in layouts or config; use environment variables and let the bootstrap script handle the differences.
Besides ZELLIJ_CONFIG_DIR, Zellij knows two other ways to direct configuration with precedence: the --config-dir command-line flag and the ZELLIJ_CONFIG_FILE variable for pointing at one specific config file. The hierarchy to memorize: command-line flags win over environment variables, and variables win over the platform default location. For portable setups, just one source of truth — for example ZELLIJ_CONFIG_DIR in your profile — then let everything follow.
A production-ready setup must survive a reboot. Zellij provides automatic session serialization: this configuration will save the layout, tabs, panes, working directories, and running commands, so a session can be resurrected after a machine reboot:
theme "arman-dark"
mouse_mode true
session_serialization true
serialize_pane_viewport true
scrollback_lines_to_serialize 10000With session_serialization true, every exiting session is automatically written to the cache folder; serialize_pane_viewport also saves the pane viewport contents, and scrollback_lines_to_serialize limits the number of serialized lines so disk isn't wasted. To bring a session back, run zellij -l welcome and choose the resurrect option, or use the session-manager with Ctrl+o then w.
Test this flow before you actually need it: create a session with several tabs and running commands, exit Zellij, then restart the machine (or simply bring the welcome screen back) and resurrect that session. Make sure the tabs, panes, working directories, and running commands return as they were. This test also validates that post_command_discovery_hook (if used) behaves as expected. A session that can be reliably resurrected is the key difference between a setup that "works" and one that is "production-ready".
On the security side, a few principles keep the configuration safe and deterministic. First, never store secrets inside config.kdl, layouts, or themes — bring them in through environment variables. Second, validate every change with zellij setup --check and test new configuration before replacing the old one: run zellij --config /path/to/new.kdl in a trial session. Third, beware of remote layouts: layouts loaded from a URL hang all their commands behind a confirmation banner for security reasons — understand this before sharing layouts across a network.
Important
Deterministic configuration means the same result wherever it's run. Avoid absolute values that only apply on one machine (specific user paths, hardcoded session names, specific shell paths) in shared config. Use per-project layouts for details that are genuinely specific, and keep generic values in config.kdl so the dotfiles repo stays deterministic.
The last level of a production-ready setup is sharing it with the team. When config.kdl, project layouts, and themes live in one repo, onboarding new members becomes much faster: clone the repo, run the bootstrap script, and they immediately have the same workspace as the whole team.
Standardization doesn't mean forcing one way — it means providing a consistent starting point. Practices that work well in teams usually include:
zellij/ structure.For teams managing many services, a shared server-admin layout saves real time: one layout that opens logs, health checks, and deployment tools in separate panes, ready for whoever is on-call. The combination of layouts and plugins shared through a repo is the concrete form of "workspace as code".
Don't forget that standardization also covers contracts between processes. When a session is resurrected on a team member's machine, Zellij tries to find the commands running in each pane; if team tooling is wrapped in wrappers, the post_command_discovery_hook option lets you modify that command-discovery result. This hook receives the detected command via the RESURRECT_COMMAND environment variable and returns the version you want to run — a small detail that saves teams from surprises when a session is revived on someone else's machine.
Tip
Document team conventions in the dotfiles repo README: config location, how to add a project layout, which keys are taken, and naming rules. A short, sharp README saves team members from guessing — and becomes a bridge for new members to contribute without fear of breaking someone else's setup.
cwd="/home/arman/project" will break on other machines. Fix: use environment variables or per-project layouts, not hardcoded paths.~/dotfiles fails on other machines. Fix: compute the repo path relative to the script's location ($(dirname "$0")).copy_command differences. Paste works on Linux but not on macOS/Windows. Fix: tailor copy_command per platform in the bootstrap script.session_serialization true, or changed it without restarting Zellij. Fix: set session_serialization true, restart, then test resurrection.config.kdl. Fix: always use environment variables for secrets, and never commit files containing sensitive values.Episode 27 closed the portability chapter: Zellij configuration now lives in a dotfiles repo, deployed via a bootstrap script that handles platform differences, and verified with zellij setup --check. Automatic session persistence ensures the workspace survives reboots, security principles keep the configuration deterministic, and workflow standardization lets the whole team start from the same point.
The points to take away:
~/.config/zellij/ — config, layout, theme, plugin — deserves a place in the dotfiles repo.ZELLIJ_CONFIG_DIR unifies the config location across Linux, macOS, and Windows.session_serialization and serialize_pane_viewport make sessions survive reboots.In episode 28, the final episode of this series, we look at Zellij from above: an honest comparison of Zellij against tmux and GNU Screen, when to use which, a tmux migration guide with Tmux mode, a complete recap of the journey from episode 0 to 27, and a daily-driver checklist for building muscle memory. See you in episode 28 — the closing episode!