i3 Dev Mode with tmuxp
This workflow creates a manual dev-mode command for an i3 setup. The command loads tmuxp sessions, starts the main Docker Compose stack, starts the two worktree Compose stacks, keeps the screen awake, sets performance mode, opens the main development tmux session in workspace 1, opens the browser in workspace 2, opens VS Code in workspace 3, and keeps the setup reusable through named tmuxp files.
Overview
Section titled “Overview”- Use tmuxp for repeatable terminal sessions, windows, and pane layouts.
- Use tmux-run as the central loader for Docker Compose services and tmuxp sessions.
- Use dev-mode as the i3 launcher for workspaces and GUI apps.
- Use Alacritty for the main tmux workspace so the terminal uses the same font, opacity, and color palette as the base i3 setup.
- Use screen-on inside dev-mode so the display does not blank or enter DPMS power saving during development.
- Use powerprofilesctl inside dev-mode so the laptop switches to performance mode for development work.
- Use home as the local shell session.
- Use io_main as the main mlnotebooks development session.
- Use io_ec2 as the EC2 or remote shell session.
- Use hermes as the Hermes Docker agent session.
- Use tree_1 and tree_2 as separate worktree sessions.
- Use Docker-managed Python dependencies inside backend containers instead of activating a project .venv manually.
- Run dev-mode once after reboot. The loader is intentionally simple and does not protect already-running sessions.
Install required tools
Section titled “Install required tools”Install the basic tools needed for this workflow. tmux is the terminal multiplexer that runs the sessions, windows, and panes. tmuxp reads YAML files and creates repeatable tmux layouts. pipx is used to install tmuxp as an isolated command-line app.
sudo pacman -S tmux python-pipx xorg-xsetpipx install tmuxpConfirm the commands are available before creating the configuration files.
tmux -Vtmuxp --versioni3-msg -vxset qpowerprofilesctl getCreate config folders
Section titled “Create config folders”tmuxp reads YAML session definitions from a normal config folder. This workflow keeps one file for each named tmux session and keeps helper scripts in ~/.local/bin.
mkdir -p ~/.config/tmuxpmkdir -p ~/.local/binSession layout
Section titled “Session layout”| tmux session | Purpose |
|---|---|
| home | Local shell session |
| io_main | Main mlnotebooks development session |
| io_ec2 | EC2 or remote shell session |
| hermes | Hermes Docker agent session |
| tree_1 | First worktree development session |
| tree_2 | Second worktree development session |
| tmux session | tmux window | Purpose |
|---|---|---|
| home | home | Local shell |
| io_main | main | Frontend dev server, backend Uvicorn server, and free shell |
| io_main | celery | Celery worker and beat logs |
| io_main | claude | Claude Code through claude-box |
| io_main | codex | Codex with workspace-write sandbox |
| io_ec2 | io_ec2 | EC2 or remote shell |
| hermes | hermes | Hermes Docker agent |
| tree_1 | main | Worktree 1 frontend, backend, and free shell |
| tree_1 | claude | Claude Code through claude-box for worktree 1 |
| tree_2 | main | Worktree 2 frontend, backend, and free shell |
| tree_2 | claude | Claude Code through claude-box for worktree 2 |
Create home.yaml
Section titled “Create home.yaml”home replaces the old local session name.
nano ~/.config/tmuxp/home.yamlsession_name: homestart_directory: "${HOME}"
windows: - window_name: home panes: - shell_command: - cd "${HOME}" - bashCreate io_main.yaml
Section titled “Create io_main.yaml”io_main replaces the old dev session name. The main window runs the frontend, backend, and a free shell. The celery window follows worker and beat logs. The claude window starts claude-box inside the main project. The codex window starts Codex in workspace-write mode.
nano ~/.config/tmuxp/io_main.yamlsession_name: io_mainstart_directory: "${HOME}/projects/mlnotebooks"
windows: - window_name: main layout: tiled panes: - shell_command: - cd "${HOME}/projects/mlnotebooks" - docker compose exec frontend bash -lc "npm run dev" - shell_command: - cd "${HOME}/projects/mlnotebooks" - docker compose exec backend bash -lc "uvicorn main:app --host 0.0.0.0 --port 8000 --reload" - shell_command: - cd "${HOME}/projects/mlnotebooks" - bash
- window_name: celery panes: - shell_command: - cd "${HOME}/projects/mlnotebooks" - docker compose logs --tail=0 -f celery_worker celery_beat
- window_name: claude panes: - shell_command: - cd "${HOME}/projects/mlnotebooks" - > docker run -it --rm --cap-drop=ALL --security-opt no-new-privileges --user "$(id -u):$(id -g)" -v "$(pwd)":/workspace -v "${HOME}/.config/claude-box/state:/home/claude/.claude" -v "${HOME}/.config/claude-box/state/.claude.json:/home/claude/.claude.json" claude-box
- window_name: codex panes: - shell_command: - cd "${HOME}/projects/mlnotebooks" - codex --sandbox workspace-write --ask-for-approval on-requestCreate io_ec2.yaml
Section titled “Create io_ec2.yaml”io_ec2 replaces the old ec2 session name.
nano ~/.config/tmuxp/io_ec2.yamlsession_name: io_ec2start_directory: "${HOME}"
windows: - window_name: io_ec2 panes: - shell_command: - cd "${HOME}" - bashCreate hermes.yaml
Section titled “Create hermes.yaml”The hermes session runs a single tmux window and a single pane. The Hermes Docker container mounts persistent Hermes data from ~/.hermes and the working directory from ~/hermes-workspace.
nano ~/.config/tmuxp/hermes.yamlsession_name: hermesstart_directory: "${HOME}/hermes-workspace"
windows: - window_name: hermes panes: - shell_command: - cd "${HOME}/hermes-workspace" - > docker run -it --rm -e HERMES_UID="$(id -u)" -e HERMES_GID="$(id -g)" -v "${HOME}/.hermes:/opt/data" -v "${HOME}/hermes-workspace:/workspace" -w /workspace nousresearch/hermes-agentCreate tree_1.yaml
Section titled “Create tree_1.yaml”tree_1 is the first worktree development session. It uses the mlnotebooks-wt1 project path and the wt1 container names.
The backend command still runs Uvicorn on port 8000 inside the container. The external host binding is handled by compose.wt1.yaml.
nano ~/.config/tmuxp/tree_1.yamlsession_name: tree_1start_directory: "${HOME}/projects/mlnotebooks-wt1"
windows: - window_name: main layout: tiled panes: - shell_command: - cd "${HOME}/projects/mlnotebooks-wt1" - docker exec -it mlnotebooks-wt1-frontend bash -lc "npm run dev" - shell_command: - cd "${HOME}/projects/mlnotebooks-wt1" - docker exec -it mlnotebooks-wt1-backend bash -lc "uvicorn main:app --host 0.0.0.0 --port 8000 --reload" - shell_command: - cd "${HOME}/projects/mlnotebooks-wt1" - bash
- window_name: claude panes: - shell_command: - cd "${HOME}/projects/mlnotebooks-wt1" - > docker run -it --rm --cap-drop=ALL --security-opt no-new-privileges --user "$(id -u):$(id -g)" -v "$(pwd)":/workspace -v "${HOME}/.config/claude-box/state:/home/claude/.claude" -v "${HOME}/.config/claude-box/state/.claude.json:/home/claude/.claude.json" claude-boxCreate tree_2.yaml
Section titled “Create tree_2.yaml”tree_2 is the second worktree development session. It uses the mlnotebooks-wt2 project path and the wt2 container names.
The backend command still runs Uvicorn on port 8000 inside the container. The external host binding is handled by compose.wt2.yaml.
nano ~/.config/tmuxp/tree_2.yamlsession_name: tree_2start_directory: "${HOME}/projects/mlnotebooks-wt2"
windows: - window_name: main layout: tiled panes: - shell_command: - cd "${HOME}/projects/mlnotebooks-wt2" - docker exec -it mlnotebooks-wt2-frontend bash -lc "npm run dev" - shell_command: - cd "${HOME}/projects/mlnotebooks-wt2" - docker exec -it mlnotebooks-wt2-backend bash -lc "uvicorn main:app --host 0.0.0.0 --port 8000 --reload" - shell_command: - cd "${HOME}/projects/mlnotebooks-wt2" - bash
- window_name: claude panes: - shell_command: - cd "${HOME}/projects/mlnotebooks-wt2" - > docker run -it --rm --cap-drop=ALL --security-opt no-new-privileges --user "$(id -u):$(id -g)" -v "$(pwd)":/workspace -v "${HOME}/.config/claude-box/state:/home/claude/.claude" -v "${HOME}/.config/claude-box/state/.claude.json:/home/claude/.claude.json" claude-boxCreate tmux-run
Section titled “Create tmux-run”tmux-run is the central loader. This version is intentionally simple because dev-mode is run once after reboot. It starts the main Compose stack, recreates the two worktree backend and frontend services, then loads all tmuxp sessions directly.
If this script is run again while the same tmux sessions already exist, tmuxp can fail because the session names are already active. For normal use, run dev-mode once after reboot.
nano ~/.local/bin/tmux-run#!/usr/bin/env bash
set -e
MAIN_DIR="${HOME}/projects/mlnotebooks"WT1_DIR="${HOME}/projects/mlnotebooks-wt1"WT2_DIR="${HOME}/projects/mlnotebooks-wt2"
cd "${MAIN_DIR}" || exit 1docker compose up -d
cd "${WT1_DIR}" || exit 1docker compose -f compose.wt1.yaml up -d --force-recreate backend frontend
cd "${WT2_DIR}" || exit 1docker compose -f compose.wt2.yaml up -d --force-recreate backend frontend
tmuxp load -d "${HOME}/.config/tmuxp/home.yaml"tmuxp load -d "${HOME}/.config/tmuxp/io_main.yaml"tmuxp load -d "${HOME}/.config/tmuxp/io_ec2.yaml"tmuxp load -d "${HOME}/.config/tmuxp/hermes.yaml"tmuxp load -d "${HOME}/.config/tmuxp/tree_1.yaml"tmuxp load -d "${HOME}/.config/tmuxp/tree_2.yaml"chmod +x ~/.local/bin/tmux-runCreate screen-on
Section titled “Create screen-on”screen-on disables the X screensaver timer and disables DPMS monitor power saving so the display stays awake during development work.
nano ~/.local/bin/screen-on#!/usr/bin/env bash
xset s offxset -dpmschmod +x ~/.local/bin/screen-onUse xset q to confirm the screen saver timeout is 0 and DPMS is disabled.
screen-onxset qCreate dev-mode
Section titled “Create dev-mode”dev-mode is the i3 launcher. It loads the tmux sessions, keeps the screen awake, sets performance mode, opens the main tmux session in workspace 1, opens Firefox in workspace 2, and opens VS Code in workspace 3.
The tmux terminal uses Alacritty, not Konsole. This is important because the base i3 setup uses Alacritty for the configured font, background, foreground, opacity, and overall terminal styling.
nano ~/.local/bin/dev-mode#!/usr/bin/env bash
~/.local/bin/tmux-run
screen-onpowerprofilesctl set performance
i3-msg 'workspace "1"'i3-msg 'exec alacritty -e tmux attach -t io_main'
sleep 1
i3-msg 'workspace "2"'i3-msg 'exec firefox'
sleep 1
i3-msg 'workspace "3"'i3-msg 'exec code --new-window ~/projects/mlnotebooks'chmod +x ~/.local/bin/dev-modeRun dev mode
Section titled “Run dev mode”Run dev-mode once after reboot.
dev-modeIf the starter terminal should close after the setup starts, run this command instead.
dev-mode && exit- Workspace 1 opens Alacritty attached to the io_main tmux session.
- Workspace 2 opens Firefox.
- Workspace 3 opens VS Code in ~/projects/mlnotebooks.
- The screen stays awake.
- The active power profile is set to performance.
- The visible workspace at the end is workspace 3.
Check active sessions
Section titled “Check active sessions”tmux lsExpected sessions:
homehermesio_ec2io_maintree_1tree_2tmux attach -t hometmux attach -t io_maintmux attach -t io_ec2tmux attach -t hermestmux attach -t tree_1tmux attach -t tree_2tmux navigation
Section titled “tmux navigation”Inside io_main:
Ctrl+b 0 mainCtrl+b 1 celeryCtrl+b 2 claudeCtrl+b 3 codexInside tree_1 or tree_2:
Ctrl+b 0 mainCtrl+b 1 claudeCommon tmux shortcuts:
Ctrl+b n Next windowCtrl+b p Previous windowCtrl+b s Session switcherCtrl+b c Create a new windowCtrl+b , Rename current windowexit Close the current pane or shellRetest without reboot
Section titled “Retest without reboot”Use this only when testing the setup without rebooting first.
tmux kill-session -t home 2>/dev/null || truetmux kill-session -t hermes 2>/dev/null || truetmux kill-session -t io_ec2 2>/dev/null || truetmux kill-session -t io_main 2>/dev/null || truetmux kill-session -t tree_1 2>/dev/null || truetmux kill-session -t tree_2 2>/dev/null || true
dev-modeRefresh a changed tmuxp layout
Section titled “Refresh a changed tmuxp layout”Because tmux-run is intentionally simple, it does not reload existing sessions. If a tmuxp YAML file changes, kill the related session and run dev-mode again or load that YAML directly.
tmux kill-session -t io_maintmuxp load -d ~/.config/tmuxp/io_main.yamltmux attach -t io_maintmux kill-session -t tree_1cd ~/projects/mlnotebooks-wt1docker compose -f compose.wt1.yaml up -d --force-recreate backend frontendtmuxp load -d ~/.config/tmuxp/tree_1.yamltmux attach -t tree_1tmux kill-session -t tree_2cd ~/projects/mlnotebooks-wt2docker compose -f compose.wt2.yaml up -d --force-recreate backend frontendtmuxp load -d ~/.config/tmuxp/tree_2.yamltmux attach -t tree_2tmux kill-session -t hermestmuxp load -d ~/.config/tmuxp/hermes.yamltmux attach -t hermesCheck command locations
Section titled “Check command locations”Use these commands when the shell can run dev-mode but the file location is not clear.
type dev-modecommand -v dev-modewhich dev-mode
type tmux-runcommand -v tmux-runwhich tmux-run
type screen-oncommand -v screen-onwhich screen-onInspect the scripts after finding their paths.
cat "$(command -v dev-mode)"cat "$(command -v tmux-run)"cat "$(command -v screen-on)"Check i3 integration
Section titled “Check i3 integration”dev-mode does not have to be added to the i3 config. It can stay as a manual command.
If it is added to i3 startup, it will run every time i3 starts, which is usually not ideal for this development layout.
grep -n "dev-mode\|dev.*mode\|\.sh" ~/.config/i3/config- No output means i3 is not starting dev-mode automatically.
- Manual mode is safer because the development layout opens only when requested.
Workspace behavior
Section titled “Workspace behavior”Workspace 1 is used for the main tmux terminal. Workspace 2 is used for the browser. Workspace 3 is used for VS Code. The script finishes on workspace 3 after launching the layout.
| Workspace | Purpose |
|---|---|
| 1 | Alacritty attached to io_main |
| 2 | Firefox |
| 3 | VS Code editor |
| Other workspaces | Left untouched by dev-mode |
If a GUI app opens on the wrong workspace, keep the short sleep delays in dev-mode. Add delayed move rules only if the simple workspace-first launch flow is not reliable on the machine.
Optional delayed move rules
Section titled “Optional delayed move rules”The current dev-mode script does not use delayed move rules. It first switches to the target workspace, then opens the app there. This matches normal i3 usage and keeps the script simple.
If Firefox or VS Code opens slowly and lands on the wrong workspace, add a short delayed correction at the end of dev-mode.
sleep 4
i3-msg '[class="firefox"] move to workspace number 2'i3-msg '[class="Code"] move to workspace number 3'Find window classes
Section titled “Find window classes”If an optional delayed move rule does not work, check the real window class and update the matching rule inside dev-mode.
xprop | grep WM_CLASSClick the target window after running xprop. Use the second class value in the i3 selector.
| App | Common class | dev-mode selector |
|---|---|---|
| Firefox | firefox | [class=“firefox”] |
| VS Code | Code | [class=“Code”] |
| Chromium | chromium | [class=“chromium”] |
| Alacritty | Alacritty | [class=“Alacritty”] |
Debug screen and power setup
Section titled “Debug screen and power setup”dev-mode calls screen-on and sets the power profile to performance. Use these commands to confirm both settings after starting dev-mode.
xset qpowerprofilesctl getFor screen-on, xset q should show timeout 0 and DPMS disabled. For powerprofilesctl, the active profile should be performance.
Debug Docker Compose startup
Section titled “Debug Docker Compose startup”If a pane reports that a service is not running, confirm the related Compose stack is up before attaching with docker compose exec or docker exec.
cd ~/projects/mlnotebooksdocker compose psdocker compose up -ddocker compose pscd ~/projects/mlnotebooks-wt1docker compose -f compose.wt1.yaml psdocker compose -f compose.wt1.yaml up -d --force-recreate backend frontenddocker compose -f compose.wt1.yaml pscd ~/projects/mlnotebooks-wt2docker compose -f compose.wt2.yaml psdocker compose -f compose.wt2.yaml up -d --force-recreate backend frontenddocker compose -f compose.wt2.yaml psCheck container names used by the tree sessions.
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"- The tree_1 tmuxp file expects mlnotebooks-wt1-frontend and mlnotebooks-wt1-backend.
- The tree_2 tmuxp file expects mlnotebooks-wt2-frontend and mlnotebooks-wt2-backend.
Debug tmuxp YAML files
Section titled “Debug tmuxp YAML files”Use tmuxp directly when testing a YAML file without running the full dev-mode flow.
tmuxp load -d ~/.config/tmuxp/io_main.yamltmux attach -t io_mainValidate that the files exist.
ls -lh ~/.config/tmuxp/home.yamlls -lh ~/.config/tmuxp/io_main.yamlls -lh ~/.config/tmuxp/io_ec2.yamlls -lh ~/.config/tmuxp/hermes.yamlls -lh ~/.config/tmuxp/tree_1.yamlls -lh ~/.config/tmuxp/tree_2.yamlDebug Alacritty usage
Section titled “Debug Alacritty usage”dev-mode should use Alacritty for the tmux workspace. This keeps the main development terminal aligned with the base i3 setup.
grep -n "tmux attach" ~/.local/bin/dev-modeExpected line:
i3-msg 'exec alacritty -e tmux attach -t io_main'If it says konsole, update dev-mode. Konsole can make tmux look visually different because it does not use the same Alacritty font, opacity, and color settings.
Debug tmux colors
Section titled “Debug tmux colors”tmuxp does not control colors. tmuxp controls sessions, windows, panes, and commands.
- Alacritty terminal colors
- ~/.tmux.conf
- tmux defaults
- Picom opacity if the terminal supports it
tmux show-options -g | grep -E 'status-style|status-left|status-right|window-status|pane-border|message-style|mode-style|clock-mode-colour'ls -la ~/.tmux.confls -la ~/.config/tmux/tmux.conf
cat ~/.tmux.conf 2>/dev/nullcat ~/.config/tmux/tmux.conf 2>/dev/nullA soft tmux palette can be kept in ~/.tmux.conf, while tmuxp YAML files should stay layout-only.
Optional soft tmux palette
Section titled “Optional soft tmux palette”Use this only if tmux itself needs a dedicated palette after switching dev-mode to Alacritty.
cat > ~/.tmux.conf <<'EOF'set -g status onset -g status-position bottomset -g status-interval 5set -g status-style "bg=#121216,fg=#d4d4d4"
set -g status-left-length 40set -g status-right-length 100set -g status-left "#[fg=#ffffff,bg=#3a3a3a,bold] #S #[default]"set -g status-right "#[fg=#909090,bg=#121216] %a %d %b %H:%M "
set -g window-status-format "#[fg=#909090,bg=#121216] #I:#W "set -g window-status-current-format "#[fg=#ffffff,bg=#3a3a3a,bold] #I:#W "set -g window-status-separator ""
set -g pane-border-style "fg=#2a2a2a"set -g pane-active-border-style "fg=#3a3a3a"
set -g message-style "bg=#2a2a2a,fg=#ffffff"set -g mode-style "bg=#3a3a3a,fg=#ffffff"
set -g clock-mode-colour "#d4d4d4"
set -g display-panes-active-colour "#d4d4d4"set -g display-panes-colour "#909090"EOF
tmux source-file ~/.tmux.confUpdated flow
Section titled “Updated flow”dev-mode -> tmux-run -> docker compose up -d in ~/projects/mlnotebooks -> docker compose -f compose.wt1.yaml up -d --force-recreate backend frontend in ~/projects/mlnotebooks-wt1 -> docker compose -f compose.wt2.yaml up -d --force-recreate backend frontend in ~/projects/mlnotebooks-wt2 -> tmuxp load home -> tmuxp load io_main -> tmuxp load io_ec2 -> tmuxp load hermes -> tmuxp load tree_1 -> tmuxp load tree_2 -> screen-on -> powerprofilesctl set performance -> workspace 1: Alacritty attaches to io_main -> workspace 2: Firefox -> workspace 3: VS Code- Run dev-mode once after reboot.
- Keep tmuxp files focused on layout and commands.
- Keep terminal styling in Alacritty.
- Keep tmux status and pane styling in ~/.tmux.conf when needed.
- Keep i3 workspace and GUI app behavior in dev-mode.
- Use Alacritty in dev-mode, not Konsole, to keep the visual style consistent with the base i3 setup.
File locations
Section titled “File locations”- Home tmuxp session: ~/.config/tmuxp/home.yaml
- Main project tmuxp session: ~/.config/tmuxp/io_main.yaml
- EC2 tmuxp session: ~/.config/tmuxp/io_ec2.yaml
- Hermes tmuxp session: ~/.config/tmuxp/hermes.yaml
- Worktree 1 tmuxp session: ~/.config/tmuxp/tree_1.yaml
- Worktree 2 tmuxp session: ~/.config/tmuxp/tree_2.yaml
- tmux session loader: ~/.local/bin/tmux-run
- Screen wake helper: ~/.local/bin/screen-on
- i3 development launcher: ~/.local/bin/dev-mode
- Main project path: ~/projects/mlnotebooks
- Worktree 1 path: ~/projects/mlnotebooks-wt1
- Worktree 2 path: ~/projects/mlnotebooks-wt2
- Hermes data path: ~/.hermes
- Hermes workspace path: ~/hermes-workspace
- Claude Box state path: ~/.config/claude-box/state