Table of Contents
Most terminal setups begin as a few helpful aliases and end up scattered across a shell profile, a terminal-emulator menu, a font download, and a handful of plugins cloned years ago. That works until you set up a new machine, connect over SSH, or need to find out why a terminal takes three seconds to open.
The answer is not a single perfect terminal or shell. A maintainable command-line workspace is a set of small, replaceable layers with clear ownership. You should be able to change your shell without losing your SSH configuration, replace a terminal emulator without rewriting aliases, and bootstrap a new machine without copying your entire home directory.
This is a reference architecture for that approach. It works whether your interactive shell is Bash, Zsh, Fish, or Nushell.
Treat the workspace as layers
The terminal window is only one part of the environment. Separating the layers makes decisions smaller and failures easier to diagnose.
| Layer | Responsibility | Examples |
|---|---|---|
| Terminal emulator | Local window, font, colors, clipboard | Konsole, GNOME Terminal, Kitty, WezTerm |
| Shell | Commands, environment, startup files | Bash, Zsh, Fish, Nushell |
| Prompt and completions | Interactive feedback and discovery | Starship, Powerlevel10k, shell-native completion |
| Multiplexer | Persistent sessions and panes | tmux, Zellij |
| Command-line tools | Search, navigation, history, aliases | rg, fzf, bat, eza, git |
| Remote access | Hosts, keys, forwarding, sessions | OpenSSH, mosh |
| Editor terminal | Project-local command entry point | VS Code, Cursor |
Do not make the choices in one row depend unnecessarily on another. For example, a good ~/.ssh/config works from every shell and terminal. A portable PATH setup does not need to know which prompt you use.
Start with a portable baseline
Before adding plugins, make the basics work in a plain terminal with a standard shell:
git,ssh,curl, and your package manager are available.~/.local/binis onPATH.- Git has your name, email, and preferred signing policy.
- SSH host aliases live in
~/.ssh/config, while private keys and tokens never enter a dotfiles repository. - Your editor can be launched with a command such as
codeorcursor, but remote sessions do not depend on it.
Use Bash as the compatibility floor even if you do not use it interactively. It is the shell you are most likely to find on a server, in a container, or behind an automation hook. Put portable environment variables and scripts in POSIX shell or Bash where practical; keep interactive shell features in shell-specific files.
For example, ~/.profile is a better home for a graphical login’s PATH than a Zsh-only startup file:
| |
That one decision prevents a common failure mode: a desktop launcher or non-Zsh process cannot find a command that worked in your terminal.
Put configuration in version control
A dotfiles repository should describe the configuration you want to reproduce, not capture every mutable file from your home directory. Keep the structure obvious:
| |
The installer can be deliberately boring: create directories, back up an existing file, and create symlinks. Avoid a bootstrap script that silently installs dozens of tools or downloads code you have not reviewed.
| |
Review every target before running a script like this on an existing machine. A symlink is easy to inspect and reverse; a script that overwrites a configuration without a backup is not.
Commit templates and examples, not secrets. Never commit private SSH keys, API tokens, editor credentials, machine-specific hostnames, or a real ~/.ssh/config that exposes internal infrastructure. Use .example files and document the required local values instead.
Pick a terminal emulator for your workflow
Terminal emulators are not interchangeable if your workflow needs graphics, panes, GPU rendering, a dropdown window, or a particular desktop integration. They are interchangeable enough that no application should require one.
- Konsole and GNOME Terminal integrate naturally with KDE and GNOME.
- Kitty and WezTerm are good when configuration-as-code and more advanced terminal features matter.
- Alacritty keeps the surface area small and is popular with users who prefer a minimal configuration.
- Yakuake suits a drop-down-terminal workflow on KDE.
Choose one for your desktop and record only its configuration in your dotfiles. Use a font with the glyphs your prompt needs, but keep a readable fallback such as monospace. A Nerd Font is useful for icon-heavy prompts; it is not a requirement for a productive terminal.
Avoid copying a large theme configuration before you know what it does. Start with readable text, sensible contrast, and a font size that works on every screen you use. Appearance is part of ergonomics, not a substitute for it.
Choose a shell without making it the architecture
Each shell makes a different trade-off:
- Bash is the most portable choice and a strong baseline for scripts.
- Zsh is a flexible interactive shell with a large plugin ecosystem.
- Fish prioritizes discoverability and interactive defaults, but uses its own scripting syntax.
- Nushell works with structured data instead of only text streams, which can be attractive for data-oriented workflows.
Use the shell that makes your day-to-day work pleasant, but retain a small, documented baseline outside it. If you choose Zsh, the Oh My Zsh, Powerlevel10k, and editor setup guide covers the concrete installation and update workflow.
Frameworks and plugin managers belong only in the interactive layer. Oh My Zsh provides a framework and bundled plugins; Antidote and Zinit can manage external plugins declaratively. They solve different parts of the same problem, so use one only when it makes your configuration easier to understand and update. A short startup file with no manager is often the best choice.
Add a multiplexer when sessions need to survive
A multiplexer is useful when you work over SSH, keep long-running tasks open, or want a consistent layout across terminal emulators. It is optional for short local commands.
tmux is established, widely available on servers, and a safe default for remote work. Zellij offers a more modern user interface and layout model. Either should own sessions, windows, and panes; the terminal emulator should own local rendering and keyboard integration.
Keep the first configuration small:
- enable mouse support only if it helps your workflow;
- define a clear prefix or leader key;
- add only the panes and status information you actively use;
- do not depend on a plugin manager to restore a session you have not tested manually.
Test the remote path explicitly: connect to a host, start a session, detach, reconnect, and confirm the session remains usable. That workflow matters more than elaborate local pane layouts.
Build a small, composable CLI toolkit
Prefer tools that improve a specific friction point instead of installing a “modern CLI” bundle all at once:
rgfor fast recursive searching;fzffor interactive selection;batfor readable file previews;ezafor directory listings, if its output helps you;fdfor simple filename searches;direnvfor project-local environment variables;atuinor shell-native history for searchable command history.
Use aliases sparingly and make them transparent. An alias that hides a destructive command or changes the meaning of a standard command makes shared troubleshooting harder. Prefer shell functions for commands that need arguments, error handling, or documentation.
Keep project-specific environment variables close to the project. direnv can load a reviewed .envrc; CI secrets and production tokens should come from the relevant secret-management system, never from a globally sourced shell file.
Make remote access intentional
OpenSSH configuration is one of the highest-leverage parts of a command-line workspace. Give regularly used hosts stable aliases and define their user, key, and options once:
Host staging
HostName staging.example.net
User deploy
IdentityFile ~/.ssh/id_ed25519_staging
IdentitiesOnly yes
Keep the real file private and version an ssh/config.example without hostnames or key paths. Use distinct keys for distinct trust boundaries. Enable agent forwarding only when it is genuinely required, because it lets a remote host use your local agent while the connection is active.
For unreliable connections, evaluate mosh where your environment supports it. For long-lived work on a server, a multiplexer is usually the more important reliability layer.
Integrate the editor without locking in the environment
VS Code and Cursor provide convenient project terminals, but they should consume your workspace rather than define it. Configure their integrated terminals to launch the shell you chose, then validate that the same project commands work in an external terminal and over SSH.
The Cursor on Manjaro guide explains an AppImage-based installation that keeps the executable under ~/.local/opt and exposes a small command in ~/.local/bin. The Zsh guide covers the editor’s terminal.integrated.* settings. Keep those editor-specific settings out of generic shell startup files wherever possible.
A useful rule is: use the editor terminal for commands tied to the open project, and an external terminal plus tmux or Zellij for persistent sessions, administration, and remote work.
Update deliberately and keep startup fast
Every layer has a different update mechanism:
| What you installed | Update it with |
|---|---|
| Distribution packages | Your distribution package manager |
| AppImages and standalone binaries | Their documented updater or a reviewed local script |
| Shell framework | Its native update command |
| Git-cloned themes and plugins | A fast-forward-only git pull loop |
| Fonts | Package manager or the source used to install them |
The Oh My Zsh guide includes a safe loop for the custom themes and plugins it installs. Do not assume a framework’s updater also updates every repository underneath its custom directory.
Measure startup before optimizing it. Zsh users can use zprof; Bash users can inspect startup files or time an interactive shell. Remove plugins and commands you do not use before adding deferred loading or another manager. Fast startup is a result of a small, understandable configuration.
Adopt the setup in stages
You do not need to rebuild your environment in a weekend:
- Establish the portable baseline:
PATH, Git, SSH, and a package list. - Create a small dotfiles repository with examples and a reversible bootstrap script.
- Choose one terminal emulator and one interactive shell.
- Add a multiplexer only when you need persistent sessions.
- Add a few CLI tools that remove real friction.
- Connect your editor terminal to the same environment.
- Review updates, startup time, and secrets periodically.
The goal is not to assemble the most impressive prompt. It is to make a command-line workspace that remains understandable after a year, portable to the next machine, and useful even when your preferred terminal, shell, or plugin manager is unavailable.