1 - Base Image

Base Image

The base image is the foundation for all aibox container flavors. It provides a complete, opinionated development environment built on Debian Trixie Slim.

Installed Tools

ToolVersion / SourcePurpose
tmuxDebian packageTerminal multiplexer
Yazi25.4.8 (prebuilt binary from GitHub releases)Terminal file manager
VimDebian package (vim + vim-runtime)Editor
GitDebian packageVersion control
Claude CLIOfficial install scriptAI assistant
ripgrep (rg)Debian packageFast recursive search (grep replacement)
fdDebian packageFast file finder (find replacement)
batDebian packageSyntax-highlighting cat replacement
ezaDebian packageModern ls replacement with git integration
zoxideDebian packageSmarter cd that learns your habits
fzfDebian packageFuzzy finder for files, history, and more
deltaDebian packageSyntax-highlighting diff viewer (used by git)
starshipPrebuilt binaryMinimal, fast shell prompt with context
curlDebian packageHTTP client
jqDebian packageJSON processor
lessDebian packagePager
unzipDebian packageArchive extraction
iproute2Debian packageNetwork route/interface inspection for status segments
iputils-pingDebian packageICMP latency checks for status segments
bash-completionDebian packageShell completions
ca-certificatesDebian packageTLS root certificates
localesDebian packageLocale support (en_US.UTF-8)
tzdataDebian packageTimezone data

GitHub CLI (gh) and lazygit are provided by the optional git-ui addon, not by the base image. Add [addons.git-ui.tools] to aibox.toml when a project needs those tools.

Audio tools are provided by the internal audio-voice recipe, which is selected automatically when [audio] enabled = true and install = true. File-preview and archive helpers such as chafa, timg, poppler-utils, mupdf-tools, entr, and resvg are provided by the optional preview-archive addon.

Build Architecture

The Dockerfile keeps the runtime stage on Debian Trixie Slim and installs tmux from the distro package set. That keeps the terminal multiplexer on the same security update path as the rest of the base image and avoids a separate prebuilt-binary fetch stage.

tmux Configuration

Plugin Policy

aibox-managed tmux plugins are preinstalled and pinned by the generated runtime and image build. TPM is documented only as a user convenience layer for adding personal tmux plugins after initialization; aibox does not rely on TPM to install or update managed plugins.

tmux-resurrect and tmux-continuum are installed and available in the image, but disabled by default until the workspace persistence policy is decided. Users can opt into them in local tmux config, but generated layouts should not assume session resurrection is active.

Key Bindings

All bindings use Ctrl+g as a leader key — press Ctrl+g, release, then press the action key. This avoids conflicts with macOS Option key (which produces special characters like @, , |) and with Vim/bash Ctrl bindings.

KeyAction
Ctrl+g then h/j/k/lNavigate panes (vim-style)
Ctrl+g then nNew pane
Ctrl+g then dSplit down
Ctrl+g then rSplit right
Ctrl+g then xClose focused pane
Ctrl+g then fToggle fullscreen
Ctrl+g then zToggle pane frames
Ctrl+g then eToggle embed/floating
Ctrl+g then = / -Resize pane (increase / decrease)
Ctrl+g then tNew window
Ctrl+g then wClose window
Ctrl+g then [ / ]Previous / next window
Ctrl+g then 1-5Jump to window N
Ctrl+g then i / oMove window left / right
Ctrl+g then sSession chooser
Ctrl+g then mSession manager
Ctrl+g then uEnter scroll mode
Ctrl+g then /Search scrollback
Ctrl+qQuit tmux

Press Escape or Ctrl+g again to cancel the leader and return to normal mode.

Layouts

aibox ships four tmux layouts. Select one with aibox up --layout <name> (the default is dev). Layouts include harness windows based on the enabled entries in [ai].harnesses and [ai].harness_order; they include the lazygit window only when the git-ui addon selects lazygit.

dev (default)

Window work has Yazi and the 1st harness stacked on the left, with shell on the right. Further harnesses use the ai window; lazygit and shell get their own windows.

focus – one tool per window, fullscreen

Each tool gets the entire screen in its own window. Switch with Ctrl+g [/] or Ctrl+g 1-5.

Windows: files (yazi) | one window per harness | optional lazygit | shell

cowork – side-by-side coding with AI

Window work has Yazi on the left and shell on the right. The ai window contains all harnesses split evenly across full-height panes; the lazygit window is generated when enabled.

Opening Files from Yazi

  • Enter – opens file in vim in-place (suspends Yazi, :q returns to Yazi). Works in all layouts.
  • e – opens file in a full-screen vim popup and returns to Yazi when vim exits.

Theme

Gruvbox dark, defined in themes/gruvbox.conf.

Vim Configuration

Notable settings baked into the image:

  • Leader key: Space
  • Line numbers: Relative + absolute (hybrid)
  • Indentation: 4 spaces default, 2 spaces for YAML, JSON, KDL, HTML, CSS, JavaScript
  • Undo: Persistent undo files stored in /home/aibox/.vim/undo
  • No swap files – clean container environment
  • Color column at 88 (Black/PEP8 default)
  • Grep program: ripgrep if available (rg --vimgrep --smart-case)
  • Netrw: Tree mode, no banner, 25% width
  • Colorscheme: desert (ships with vim-runtime, no plugins needed)

Git Configuration

Git config lives at /home/aibox/.config/git/config (XDG path, not ~/.gitconfig). The environment variable GIT_CONFIG_GLOBAL is set in the generated docker-compose.yml to point to this location.

Using a directory mount (rather than a single-file mount) allows a credentials file to coexist alongside config.

AI Coding Agents

AI coding agents (Claude, Codex, Aider, Gemini, and others) are not pre-installed in the base image. They are installed per-project when you select them in the ordered [ai].harnesses list, for example { harness = "claude", enable = true, install = true }.

Audio Support

Set [audio] enabled = true to enable audio bridging. aibox then selects the internal audio-voice recipe and configures the PulseAudio environment for the container. See Audio Support for setup details.

Configuration Persistence

All user configuration is persisted on the host under .aibox-home/ and bind-mounted into the container:

Host PathContainer PathContents
.aibox-home/.ssh//home/aibox/.ssh (read-only)SSH keys
.aibox-home/.vim//home/aibox/.vimVim config and undo history
.aibox-home/.config//home/aibox/.configGit, tmux, Yazi, prompt, and tool config
.aibox-home/.cache//home/aibox/.cacheRuntime caches
.aibox-home/.local//home/aibox/.localHelper scripts, state, and local data
.aibox-home/.tmux//home/aibox/.tmuxtmux plugin and socket state

The Dockerfile bakes identical defaults into the image as a fallback. If no mounts are present, the container still works out of the box.

On first aibox init or aibox up, the .aibox-home/ directory is auto-seeded from built-in templates. User-owned files are retained. Files explicitly managed by aibox may be refreshed on aibox apply when their upstream template changes.

Tool credentials saved below /home/aibox/.config, including a GitHub CLI login stored in /home/aibox/.config/gh, therefore survive container replacement and image rebuilds. They remain local secret-bearing files rather than encrypted storage. See GitHub authentication for the security tradeoffs and the recommended scoped-PAT alternative.

File Preview

The base image ships the Yazi configuration and preview plugins. Install the optional preview-archive addon for raster/SVG/PDF/archive helper binaries and preview-enhanced for Markdown, EPS, video, and Ghostscript support. PDF and SVG also support watch-mode preview when the required preview tools are selected.

See the dedicated File Preview page for full documentation, including format coverage, standalone tools (chafa, timg), and the PDF/SVG watch-mode patterns.

Container Entrypoint

CMD ["sleep", "infinity"]

The container stays alive and idle. Both VS Code and aibox up exec into it. tmux is never the container entrypoint – it is launched on attach.

2 - Container Configuration

Container Configuration

The [container] section in aibox.toml controls per-project container settings.

Container Identity

[container]
name = "my-project"        # Container name (used by compose)
hostname = "my-project"    # Container hostname
user = "aibox"             # Container user (default: aibox)

The user field determines the non-root user inside the container. The default aibox user (UID 1000) is recommended. Set user = "root" only if needed for specific tools.

aibox apply generates a Compose file with an explicit top-level project name, an explicit service image, and container_name = [container].name. Docker Desktop, OrbStack, and Compose UIs therefore group aibox projects by their project/container names instead of under a generic devcontainer identity.

The generated main service also sets Compose init: true. Compose starts a small init process as PID 1 so orphaned child processes are reaped correctly while the service still runs command: sleep infinity as its long-lived container command. This prevents zombie buildup from tools that spawn helper processes, including bubblewrap-based sandbox helpers.

init: true is part of the Compose Specification. Docker Compose and modern Compose-spec providers support it. podman compose delegates to an external Compose provider, so support depends on the configured provider; if an older provider rejects the key, upgrade the provider rather than removing the reaper from generated projects.

Use the configured container name when writing Compose overrides:

services:
  my-project:
    ports:
      - "8080:80"

Post-Create Command

Run a command after the container is first created:

[container]
post_create_command = "npm install"

This maps to devcontainer.json’s postCreateCommand.

Network Keepalive

Prevent OrbStack/VM NAT from dropping idle connections:

[container]
keepalive = true

This sends a lightweight DNS lookup every 2 minutes via the devcontainer postStartCommand.

Custom Packages, Ports, Volumes, and Environment Variables

Container customizations such as extra packages, port forwarding, volume mounts, and environment variables are handled through standard Docker mechanisms rather than aibox.toml.

Extra Packages — Dockerfile.local

Install additional apt packages by adding them to .devcontainer/Dockerfile.local, which is appended to the generated Dockerfile at build time:

RUN apt-get update && apt-get install -y --no-install-recommends \
    universal-ctags graphviz postgresql-client \
    && rm -rf /var/lib/apt/lists/*

Ports, Volumes, and Environment Variables — docker-compose.override.yml

Use .devcontainer/docker-compose.override.yml to add port mappings, volume mounts, and environment variables:

services:
  my-project:                # must match [container] name in aibox.toml
    ports:
      - "8080:80"
      - "5432:5432"
    volumes:
      - /host/data:/container/data:ro
    environment:
      DATABASE_URL: "postgres://localhost/mydb"
      NODE_ENV: "development"

Both Dockerfile.local and docker-compose.override.yml are scaffolded by aibox init and are never overwritten by aibox apply.

Compose Override

For project-specific services (databases, sidecars, test companions), use Docker Compose’s standard override mechanism. During aibox init, an empty .devcontainer/docker-compose.override.yml is scaffolded with example usage.

Docker Compose automatically merges the override file with the generated docker-compose.yml using a strategic merge — maps (services, environment) are deep-merged by key, lists (ports, volumes) are appended, and scalars (image, command) are replaced.

When aibox apply detects the override file, it wires both files into devcontainer.json so VS Code picks them up.

Example — add a PostgreSQL sidecar:

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: dev
    ports:
      - "5432:5432"

Example — add depends_on to the main service:

services:
  my-project:            # must match [container] name in aibox.toml
    depends_on:
      - postgres

3 - Audio Support

Audio Support

aibox can enable audio support for voice-capable tools. Audio is bridged from the container to the host via PulseAudio over TCP, and the required container packages live in the optional audio-voice addon.

Why Audio Matters

Claude Code supports voice interaction. For this to work inside a container, audio output (and optionally input) must be forwarded to the host’s sound system. aibox handles this by installing PulseAudio client utilities in the container and connecting them to a PulseAudio server running on the host.

Architecture

Container                          Host
┌─────────────────────┐     ┌─────────────────────┐
│  Claude Code        │     │  PulseAudio Server   │
│       │             │     │       │              │
│  pulseaudio-utils   │────>│  TCP :4714           │
│  sox                │     │       │              │
│  .asoundrc          │     │  Speakers / Mic      │
└─────────────────────┘     └─────────────────────┘

The container sets PULSE_SERVER to point at the host’s PulseAudio TCP module. Audio data flows over the network socket.

Configuration in aibox.toml

[audio]
enabled = true
backend = "pulseaudio"
install = true
pulse_server = "tcp:host.docker.internal:4714"
FieldDefaultDescription
enabledfalseWhether to set up audio environment variables in the container
backendpulseaudioAudio bridge backend. Only PulseAudio is currently supported
installtrueWhether to install the internal audio-voice tool recipe when audio is enabled
pulse_servertcp:host.docker.internal:4714PulseAudio server address

When enabled = true, the generated docker-compose.yml sets PULSE_SERVER in the container environment. When install = true, it also selects the internal audio-voice recipe during aibox apply, which installs Sox, PulseAudio client utilities, and ALSA PulseAudio plugins in the container.

Host Setup

The fastest way to set up audio on your host is the built-in CLI command:

# Check if your host is ready
aibox doctor audio

# Automatic setup (macOS: installs PulseAudio, configures TCP, creates launchd agent)
aibox apply audio

aibox apply audio handles:

  • Installing PulseAudio via Homebrew (macOS) if not present
  • Configuring ~/.config/pulse/default.pa with the TCP module on port 4714
  • Creating a launchd agent with KeepAlive so PulseAudio auto-starts and restarts on crash (macOS)
  • Loading the TCP module immediately

aibox doctor audio diagnoses: PulseAudio installation, daemon status, TCP module, persistence config, port listening, launchd agent (macOS), and connectivity.

Both commands accept --port to override the default port (4714).

Manual setup

If you prefer manual configuration:

macOS

  1. Install PulseAudio:

    brew install pulseaudio
    
  2. Enable the TCP module. Add to ~/.config/pulse/default.pa:

    load-module module-native-protocol-tcp port=4714 auth-anonymous=1
    
  3. Start PulseAudio:

    pulseaudio --start
    
  4. Verify it is listening:

    lsof -i :4714
    

Docker Desktop and OrbStack provide host.docker.internal automatically. For Podman, check your machine’s network configuration — you may need to use the host IP directly:

[audio]
enabled = true
pulse_server = "tcp:192.168.64.1:4714"

Linux

  1. PulseAudio is likely already running. Enable the TCP module:

    pactl load-module module-native-protocol-tcp port=4714 auth-ip-acl=127.0.0.1;172.16.0.0/12;10.0.0.0/8;192.168.0.0/16
    

    To make this persistent, add to ~/.config/pulse/default.pa:

    load-module module-native-protocol-tcp port=4714 auth-ip-acl=127.0.0.1;172.16.0.0/12;10.0.0.0/8;192.168.0.0/16
    
  2. Use host.docker.internal (Docker 20.10+) or the Docker bridge IP:

    [audio]
    enabled = true
    pulse_server = "tcp:host.docker.internal:4714"
    

Claude Code OAuth in Containers

When running claude auth inside a container with bridge networking (OrbStack, Docker Desktop), the OAuth callback may fail. Claude Code starts a temporary HTTP server on a random ephemeral port to receive the callback, but that port isn’t forwarded to the host browser.

Workaround: Use claude setup-token to authenticate manually, or authenticate on the host first. aibox bind-mounts Claude’s .claude/, .claude.json, and XDG cache/config/state locations, so credentials survive container rebuilds.

The .asoundrc File

The seeded home configuration includes an .asoundrc file at /home/aibox/.asoundrc. This configures ALSA to route through PulseAudio, so applications that use ALSA rather than PulseAudio directly also get audio output when the audio-voice addon is installed.

Troubleshooting

No sound output

  1. Verify PulseAudio is running on the host:

    pulseaudio --check && echo "running" || echo "not running"
    
  2. Verify the TCP module is loaded:

    pactl list modules | grep module-native-protocol-tcp
    
  3. Test from inside the container:

    paplay /usr/share/sounds/freedesktop/stereo/bell.oga
    

    If the file does not exist, use sox to generate a test tone:

    play -n synth 0.5 sine 440
    

Connection refused

The PULSE_SERVER address is not reachable from the container.

  • Check that the PulseAudio TCP module is listening on the correct port
  • Check that host.docker.internal resolves from inside the container:
    # From inside the container
    getent hosts host.docker.internal
    
  • Try using the host’s explicit IP address instead

Audio works but is choppy

This is usually a network or resource issue. PulseAudio over TCP adds latency. Ensure the container has sufficient CPU resources and the host is not under heavy load.

Disabling audio

If you do not need audio, set enabled = false in aibox.toml:

[audio]
enabled = false

This removes the PULSE_SERVER environment variable from the container. The audio packages (sox, pulseaudio-utils) remain installed in the base image but are inert without a server to connect to.

4 - File Preview

File Preview

aibox containers ship with Yazi preview configuration and can install TUI-native preview tools for raster images, vector graphics, PDF, archives, and video through optional addons. Several formats also support watch-mode preview, where the rendered output updates automatically whenever the source file changes.

Overview

There are two independent preview mechanisms:

MechanismWhen to use
Yazi file previewBrowsing files — preview appears automatically in the right panel as you navigate
Standalone TUI viewersViewing a specific file in a pane, or piping output from a build tool

Watch-mode (live-updating preview) is available via the standalone tools — see Watch-Mode Preview.


Yazi File Preview

When you open Yazi (Ctrl+g s from the file manager pane, or via the layout sidebar), files are previewed automatically in the right panel as you navigate. No manual invocation needed.

Supported formats

FormatExtensionsPreviewerRequirement
JPEG / PNG.jpg .jpeg .pngimage (built-in)chafa
GIF (incl. animated).gifimage (built-in)chafa
WebP.webpimage (built-in)chafa
BMP.bmpimage (built-in)chafa
TIFF.tiff .tifimage (built-in)chafa
SVG.svgsvg.yazi pluginresvg or rsvg-convert
EPS.epseps.yazi pluginghostscript (addon)
PDF.pdfpdf (built-in)poppler-utils
Markdown.md .markdownrich-preview.yazi pluginpreview-enhanced addon
SQLite.sqlite .sqlite3 .dbsqlite-preview.yazi plugindata-preview addon
CSV / TSV.csv .tsvtabular-preview.yazi plugindata-preview addon
Excel.xls .xlsxtabular-preview.yazi plugindata-preview addon
Video.mp4 .mkv .webm .avivideo (built-in)ffmpeg (addon)
Text / codemost text formatscode (built-in)

Raster image, SVG, PDF, archive, and standalone terminal viewers require the preview-archive addon. SQLite, CSV/TSV, and Excel previews require the data-preview addon. Markdown rendering, EPS, and video thumbnails require the preview-enhanced addon, which depends on preview-archive:

aibox set addon preview-archive enabled --apply    # adds chafa, timg, poppler, mutool, entr, p7zip, resvg
aibox set addon data-preview enabled --apply       # adds sqlite3 and csvkit for data previews
aibox set addon preview-enhanced enabled --apply   # adds python3-rich, ffmpeg, ghostscript

How previewer dispatch works

Yazi matches files against a list of prepend_previewers in ~/.config/yazi/yazi.toml. The first matching entry wins:

[plugin]
prepend_previewers = [
    { url = "*.svg", run = "svg" },
    { url = "*.eps", run = "eps" },
    { url = "*.md",  run = "rich-preview" },
    { url = "*.jpg",  run = "image" },
    { url = "*.jpeg", run = "image" },
    { url = "*.png",  run = "image" },
    { url = "*.gif",  run = "image" },
    { url = "*.webp", run = "image" },
    { url = "*.bmp",  run = "image" },
    { url = "*.tiff", run = "image" },
    { url = "*.tif",  run = "image" },
]

Custom plugins (svg.yazi, eps.yazi) live at ~/.config/yazi/plugins/<name>.yazi/init.lua. They are seeded into .aibox-home/.config/yazi/ on first aibox init.

Format notes

SVG — converted to PNG by resvg, a fast standalone Rust-based SVG renderer bundled as a static binary in /usr/local/bin/resvg. The rendered PNG is cached under Yazi’s cache directory. If resvg is absent from PATH, the plugin fails gracefully and Yazi falls back to the text previewer.

EPS — rendered to PNG at 150 DPI by gs (Ghostscript), then displayed as an image. Result is cached.

Markdown — when the preview-enhanced addon is enabled, .md and .markdown files are rendered through rich-preview.yazi, which uses Python Rich for terminal-native Markdown rendering. Without the addon, Yazi falls back to its built-in text/code preview.

PDF — page 1 is rendered by pdftoppm (from poppler-utils). Navigate multi-page documents with Yazi’s built-in PDF plugin controls.

Wide text previews — the seeded Yazi keymap includes a pager shortcut for the selected file using less -R -S. -R preserves ANSI color from rich/code output; -S disables wrapping so long lines can be inspected with horizontal scrolling.

SQLitesqlite-preview.yazi opens databases read-only through sqlite3 and shows schema objects plus table/view columns. It is enabled only when the data-preview addon is configured.

CSV / TSV / Exceltabular-preview.yazi formats CSV/TSV with csvlook; .xls and .xlsx are converted with in2csv before formatting. It is enabled only when the data-preview addon is configured.

.excalidraw files — Excalidraw’s native format is JSON. A graphical preview is not possible in a TUI environment. Yazi falls back to the text previewer showing the raw JSON. This is a known limitation — Excalidraw requires a browser to render.


Standalone TUI Viewers

These tools are available directly in the shell for viewing a specific file or integrating into a pipeline when the preview-archive addon is enabled.

chafa — universal image renderer

chafa converts images to terminal graphics using Sixel, Kitty protocol, half-block Unicode, or plain ASCII, auto-detecting the best mode for the current terminal.

# View any raster image
chafa photo.jpg

# Force half-block mode (safe inside tmux)
chafa --format=halfblock diagram.png

# SVG via librsvg (if chafa was compiled with librsvg support)
chafa --format=halfblock logo.svg

# Constrain to a specific cell size
chafa -s 80x40 banner.png

Supported formats: JPEG, PNG, GIF (animated), WebP, BMP, TIFF, AVIF, and more. SVG support depends on whether chafa was built with librsvg — run chafa --version and check for SVG: yes.

timg — terminal image and document viewer

timg renders images, animated GIFs, videos, and PDFs (page by page) directly in the terminal.

# View an image
timg photo.jpg

# View a PDF — renders all pages sequentially
timg document.pdf

# View a specific PDF page (page 2)
timg -p2 document.pdf

# Constrain output size
timg -g 120x40 wide-image.png

# Clear previous output before rendering (useful in watch loops)
timg --clear output.pdf

Supported formats: JPEG, PNG, GIF (animated), WebP, BMP, TIFF, PDF (via MuPDF), video (via ffmpeg).


Watch-Mode Preview

Watch-mode preview automatically re-renders a file whenever it changes on disk. This is particularly useful for LaTeX, Typst, and other document workflows where you write source in one pane and see the rendered output update in real time in another.

The pattern uses entr (an inotify-based file watcher) combined with a rasteriser and timg --clear:

source file changes → entr triggers → rasteriser produces PNG → timg renders PNG in terminal

Install the preview-archive addon to get the watch-mode tools (entr, mupdf-tools, resvg, timg).

PDF watch preview ⟳

Run this in a dedicated pane while editing your LaTeX or Typst source:

pdf-watch output.pdf

pdf-watch wraps the underlying entr + mutool draw + timg --clear pipeline and re-renders page 1 whenever the PDF changes:

ls output.pdf | entr -s 'mutool draw -o /tmp/p.png output.pdf 1 && timg --clear /tmp/p.png'
PartRole
entrWatches output.pdf for changes (inotify, near-zero CPU at idle)
mutool drawMuPDF rasteriser — renders a PDF page to PNG (fast, no X11 needed)
-o /tmp/p.png output.pdf 1Output file, input file, page number
timg --clearRenders the PNG inline, clearing the previous frame first

Yazi also seeds a PDF live-watch binding for selected .pdf files. It invokes pdf-watch so you can start the same live preview directly from the file manager.

Tips:

  • Change the final 1 to preview a different page number.
  • To watch all pages: mutool draw -o /tmp/p-%d.png output.pdf (produces /tmp/p-1.png, /tmp/p-2.png, …); then timg --clear /tmp/p-*.png.
  • Add -r 150 to mutool draw for higher resolution (default is 72 DPI).
  • Use timg -g 120x40 to constrain the rendered size to fit a specific pane.

SVG watch preview ⟳

ls diagram.svg | entr -s 'resvg diagram.svg /tmp/d.png && timg --clear /tmp/d.png'

resvg converts the SVG to a high-fidelity PNG. Unlike Inkscape or rsvg-convert, resvg is a static binary with no runtime dependencies and typically renders in under 100 ms for typical diagrams.

General pattern

The same entr + rasteriser + timg pattern works for any file format that has a headless rasteriser:

# Watch a file and re-render on change
ls <file> | entr -s '<rasterise-command> && timg --clear <output.png>'
Source formatRasteriser command
PDF (page 1)mutool draw -o /tmp/p.png file.pdf 1
SVGresvg file.svg /tmp/p.png
EPSgs -dBATCH -dNOPAUSE -sDEVICE=png16m -r150 -sOutputFile=/tmp/p.png file.eps

Format Coverage Summary

FormatYazi previewchafatimgWatch-mode
JPEG / PNG
GIF (animated)
WebP
BMP
TIFF
SVG✓ (resvg)✓ (librsvg)✓ (resvg)
EPS✓ (ghostscript)✓ (ghostscript)
PDF✓ (poppler)✓ (mutool)
Video✓ (ffmpeg)
.excalidrawtext fallback

5 - Runtime Operations

How to start, inspect, rebuild, and troubleshoot a running aibox workspace.

Runtime Operations

The generated devcontainer is standard Compose output, but the normal workflow should go through aibox so generated files, runtime home state, and diagnostics stay aligned.

Start and Attach

aibox up
aibox up --layout focus
aibox up --apply

aibox up creates or starts the container and attaches through tmux. Use --layout for a one-session layout override. Use --apply when you want aibox to reconcile configuration before starting.

Stop or Remove

aibox down
aibox delete runtime

down stops the Compose project. delete runtime removes the container while preserving project files and .aibox-home/.

Rebuild

aibox apply
aibox apply --no-cache
aibox apply --config-only

Use --no-cache after base-image, addon, or package-cache issues. --rebuild is kept as a visible alias for the same behavior. Use --config-only when you only want to regenerate files, and in processkit mode refresh processkit content, without building the image. In harness-only projects, --config-only regenerates config/runtime surfaces without processkit content work.

Inspect Runtime State

aibox get runtime
aibox get runtime --resources
aibox get runtime --resources -o json
aibox describe runtime

The resource snapshot is designed for low-dependency environments. It reads cgroupfs and procfs directly instead of relying on tools such as ps or free being installed.

Key fields:

FieldWhat it tells you
memory_current_bytescurrent cgroup memory usage
memory_maxmemory limit or unlimited
oom_kill_countwhether the kernel has killed a process in the cgroup
total_process_counttotal visible processes
processkit_mcp_python_process_countlive Python processkit MCP server processes

An oom_kill_count above zero is strong evidence that a missing agent or terminated tool was killed by the operating system rather than by the CLI.

With current processkit releases, [ai.mcp.gateway].mode = "auto" registers a processkit-gateway stdio proxy for MCP-capable harnesses. The proxy starts the local gateway on demand when no listener exists, so generated devcontainer startup no longer has to supervise one Python process per skill in the default mode. Use separate only when a harness needs the older one-server-per-skill layout. Harness-only projects do not register the processkit gateway.

Resource Thresholds

Configure warning thresholds in aibox.toml:

[container.resource_thresholds]
memory_mib_warn = 4096
process_count_warn = 400
processkit_mcp_python_warn = 50

aibox doctor uses these values when reporting runtime pressure.

Compose Identity

Generated Compose output includes:

  • a top-level project name derived from [container].name
  • an explicit image name
  • a main service named after the project
  • container_name = [container].name
  • init: true for PID 1 process reaping

This keeps Docker Desktop, OrbStack, and Compose UIs from grouping unrelated aibox projects under a generic devcontainer identity.

Existing Containers After Generator Changes

aibox apply rewrites generated files, but it does not magically replace a running container that was created from older Compose output. Recreate the runtime when a change affects process model, mounts, image labels, init behavior, or service identity:

aibox down
aibox apply
aibox up

If a container has already accumulated zombie processes, the fix is still a runtime recreate. A new generated init: true service can reap future orphaned children, but it cannot change PID 1 in an already-created container.

Common Symptoms

SymptomFirst checks
AI process disappearsaibox get runtime --resources, then inspect oom_kill_count
OrbStack groups projects oddlyrerun aibox apply, then recreate the container
Network drops after idle timeset [container] keepalive = true
Build keeps using stale layersrun aibox apply --no-cache
Runtime starts but tools are missingcheck selected addons with aibox describe workspace-manifest

Podman Notes

Podman support depends on the Compose provider behind podman compose. Generated files follow the Compose Specification, including init: true. If an older provider rejects a spec key, upgrade the provider instead of removing the generated setting.