Tutorial

Run a throwaway Linux in a tab

Sometimes you want a clean machine: try a package without polluting your host, check a script against a distro you don't run, or poke at something sketchy in a room you can burn down afterwards. Sinclair opens a fresh OS userland as a regular tab — a container is the tab's backing process, so it scrolls, records, themes, and closes like any other pane. This walkthrough opens one, makes one persistent, attaches to containers you already have running, and adds your own images to the picker.

OS tabs need Docker or Podman installed. Sinclair detects whichever is on your PATH (Docker preferred when both are present); pin one explicitly with "container-engine": "docker" or "podman" in your settings. On macOS, is the modifier — on Linux, read it as (Ctrl).

1. Open one

Press ⌘⇧T (new_os_tab, also in the File menu as OS Tabs…). A small picker window opens with the built-in profiles — Debian, Ubuntu, Alpine, Fedora, and Arch Linux, each shown with the image it runs — plus a text field. Pick one and you're dropped at a root shell inside a brand-new container, in a brand-new tab.

The text field does three things: leave it empty and enter launches the first profile; type a profile's name to pick it by label; or type any OCI image reference — say node:22-slim — and Sinclair runs that image as a one-off, no configuration required. The engine pulls an image the first time you use it, so a first launch can take a moment; after that it's effectively instant.

Tip

Alpine ships without bash, so its profile drops you into sh. Every other built-in runs bash.

2. It's a real tab — and it's disposable

Under the hood the tab's process is simply docker run --rm -it <image> <shell> on a pty — the same seam every tab uses. Scrollback, search, copy mode, recording, themes: everything you know works inside it. There's no special "container mode" to learn.

By default containers are ephemeral: that --rm means exiting the shell or closing the tab removes the container and everything you did in it. Install packages, make a mess, close the tab — gone. That's the point.

3. Keep one around

When you do want your changes to survive — a tools image you've set up just right, a long-running experiment — flip the default lifecycle in ~/.config/sinclair/settings.json:

{
  // keep containers when their tab closes (default: remove them)
  "container-persist": true
}

Persistent containers are launched with a name, so they can be found and re-entered later. You can also set the lifecycle per profile, which usually reads better than flipping the global default — see step 5.

4. Attach to what's already running

Fresh containers aren't the only thing you can open in a tab — you can drop a shell into a container that's already running: your compose stack's database, a long-lived dev box, anything the engine reports. Press ⌘⇧C (attach_container, also in the File menu as Attach to Container…) to open the Attach to Container picker. It lists every running Docker/Podman container by name, image, and uptime — click one to attach a shell in a new tab. The text field also takes a container name or id directly, for one that isn't listed.

Prefer to browse? The Containers sidebar panel shows the same list. Open it with sidebar:left:containers from the command palette (⌘⇧P) or by clicking through the sidebar (⌘B), then double-click a row to attach. Either way, if a tab is already attached to a container Sinclair focuses that tab instead of opening a second one; a refresh row at the bottom of the panel re-lists.

Note

Attaching uses the engine's exec, so you get a fresh shell inside the running container without disturbing its main process. With nothing running the picker shows No running containers; with neither engine installed it shows No container engine found — install Docker or Podman and reopen.

5. Add your own images to the picker

Repeatable container entries add profiles to the picker. Each is label | image | command | lifecycle — only the label and image are required; the command defaults to bash, and the lifecycle (persist or ephemeral) defaults to the global container-persist setting:

{
  "container": [
    "Tools | ghcr.io/me/tools:latest | zsh | persist",
    "Node  | node:22-slim | bash",
    "Debian | debian:12 | bash"
  ]
}

A profile whose label matches a built-in (case-insensitively) replaces it — the Debian entry above pins that picker row to debian:12 instead of debian:latest. Anything else is appended below the built-ins. A malformed entry never breaks the picker: it's skipped, and the settings diagnostics tell you what's wrong with it.

Where to next