Demo toolkit¶
The toolkit is a set of statically linked command-line tools that look good in recordings, plus settings tuned for video. It runs in any Linux image (Debian, Ubuntu, Alpine, UBI, even busybox), as any user, without a package manager.
| Tool | Use in demos |
|---|---|
bat |
Show files with syntax highlighting |
delta |
Show diffs with syntax highlighting (used by the diff action) |
eza |
ls and eza --tree with colours and icons |
fd, rg |
Find files and text |
jq |
Pretty-print JSON |
micro |
Editor with familiar keys (Ctrl+s saves, Ctrl+q quits) |
yazi |
File explorer with a preview |
file |
File type detection (yazi needs it for previews) |
tmux |
Split the terminal into panes |
zsh |
Alternative shell (terminal.shell: zsh) |
It also brings the Symbols Nerd Font (icons in yazi and eza --icons), a
tmux.conf with a quiet status line and titled panes, and configs for yazi, bat
and micro. Licenses are in /usr/local/share/narratty/licenses.
In the narratty image¶
The narratty image contains the toolkit, with its settings active. Specs that run in the container can use all tools directly.
In your own image¶
The toolkit is published as ghcr.io/ditschi/narratty-toolkit, an image that holds
only these files. Add it to a Dockerfile with one line:
COPY --from=ghcr.io/ditschi/narratty-toolkit:latest / /usr/local/
ENV YAZI_CONFIG_HOME=/usr/local/share/narratty/yazi \
BAT_CONFIG_PATH=/usr/local/share/narratty/bat/config \
MICRO_CONFIG_HOME=/usr/local/share/narratty/micro
- The target must be
/usr/local:zshandtmuxlook for their files there. COPYneeds noUSER root, and nothing is installed at run time.- Instead of the
ENVlines, a shell can load the settings with. /usr/local/share/narratty/env.sh.tmuxreads its settings from/usr/local/etc/tmux.confon its own; a~/.tmux.confstill wins. - Images exist for
linux/amd64andlinux/arm64. Tags follow narratty's versions (<version>,<major.minor>,latest,edge); pin a version for reproducible builds.
In a dev container¶
The toolkit goes into the image the dev container is built from. Pick the case that matches your setup.
devcontainer.json with a Dockerfile. Add the lines to that Dockerfile. In a
multi-stage Dockerfile, add them to the stage the dev container uses (build.target):
# .devcontainer/Dockerfile
FROM mcr.microsoft.com/devcontainers/python:3.12
# ... your setup ...
COPY --from=ghcr.io/ditschi/narratty-toolkit:latest / /usr/local/
ENV YAZI_CONFIG_HOME=/usr/local/share/narratty/yazi \
BAT_CONFIG_PATH=/usr/local/share/narratty/bat/config \
MICRO_CONFIG_HOME=/usr/local/share/narratty/micro
devcontainer.json with only image. Replace image with a build of a
two-line Dockerfile:
// .devcontainer/devcontainer.json
{
"build": { "dockerfile": "Dockerfile" } // was: "image": "ghcr.io/acme/dev:2"
}
# .devcontainer/Dockerfile
FROM ghcr.io/acme/dev:2
COPY --from=ghcr.io/ditschi/narratty-toolkit:latest / /usr/local/
ENV YAZI_CONFIG_HOME=/usr/local/share/narratty/yazi \
BAT_CONFIG_PATH=/usr/local/share/narratty/bat/config \
MICRO_CONFIG_HOME=/usr/local/share/narratty/micro
Docker Compose, without touching the project. An override file adds the toolkit on top of the service's image with an inline Dockerfile (Compose 2.17 or later):
# compose.narratty.yaml
services:
dev:
image: acme-dev:narratty # own tag, the original image stays as it is
build: !override # replaces the service's own build settings
dockerfile_inline: |
FROM ghcr.io/acme/dev:2
COPY --from=ghcr.io/ditschi/narratty-toolkit:latest / /usr/local/
ENV YAZI_CONFIG_HOME=/usr/local/share/narratty/yazi \
BAT_CONFIG_PATH=/usr/local/share/narratty/bat/config \
MICRO_CONFIG_HOME=/usr/local/share/narratty/micro
docker compose -f compose.yaml -f compose.narratty.yaml up -d --build
docker compose -f compose.yaml -f compose.narratty.yaml exec dev bash
If the service is built from its own Dockerfile (build: in compose.yaml), build
it first without the override and use its image name after FROM:
The dev container keeps its user; nothing runs as root at run time.
Onboarding videos¶
To run the whole demo in the project's image, Compose service or dev container, use a project environment; the toolkit is mounted there automatically. The pattern below records the steps that lead into the container.
A video that clones a repository, starts its dev container and works inside it needs
Docker during the recording. Either give the sandboxed demo your engine with
sandbox.docker: true (full host access, you
approve it once), or run with --runtime native:
sandbox:
docker: true
network: allowlist # for git clone
allow_hosts: [github.com:443]
scenes:
- id: clone
narration: Clone the repository.
actions:
- run: git clone https://github.com/acme/app && cd app
- wait: {screen: "done\\.", timeout_ms: 120000}
- id: toolkit # add the toolkit, unrecorded
hidden: true
actions:
- run: >-
echo 'COPY --from=ghcr.io/ditschi/narratty-toolkit:latest / /usr/local/'
>> .devcontainer/Dockerfile
- id: build
narration: Build and start the dev container. This is sped up.
actions:
- run: docker compose up -d --build && echo rea""dy
- id: build-runs # the build output, ten times faster
timelapse: 10
actions:
- wait: {screen: "\\nready", timeout_ms: 900000}
- id: enter
narration: Open a shell in the dev container.
actions:
- run: docker compose exec dev bash
- id: settings
hidden: true
actions:
- run: . /usr/local/share/narratty/env.sh
- id: explore
narration: This is the project inside the container.
actions:
- run: eza --tree --level 2
Things to watch:
- A timelapse scene shows the build sped up. To leave it out
instead, make the scene
hidden: true: hidden scenes take no time in the video, and the screen is cleared after them. echo rea""dyprintsready, but the typed command does not match thewait.- Instead of editing the Dockerfile, the hidden scene can write the Compose override and start with both files.
- The appended
COPYline lands in the last stage of the Dockerfile. If the dev container builds an earlier stage (target:), add it there. - Viewers who follow the video do not have that line. Use the toolkit to show the project, not for steps they are meant to repeat.
- The dev container keeps running after the render. End with a hidden scene that
leaves it (
exit) and runsdocker compose down.
Editor layout¶
terminal.layout: editor records a layout like an editor: yazi as explorer and
preview on top, the shell below, in a tmux window of its own. narratty builds it
before the first scene and closes it before the end card; the spec only describes the
demo:
terminal:
layout: editor
scenes:
- id: explore
narration: Open the source folder in the explorer.
actions:
- focus: explorer # keys now go to yazi
- key: Right
- id: tests
narration: Reveal jumps to a file, and Enter opens it in micro.
actions:
- reveal: tests/test_greet.py
- key: Enter
- id: diff
narration: The diff shows what the demo changed.
actions:
- diff
- The shell starts with focus.
focus: explorerandfocus: terminalswitch. - In the explorer, the arrow keys move and Enter opens a file in
$EDITOR; the layout sets it tomicrowhen that is installed andEDITORis unset. revealselects a path relative to the workspace, whichever pane has focus.diffopens a popup over the layout; the next key press of the scene, or its end, closes it.- After a hidden scene the shell pane is cleared.
- The layout needs
tmuxandyazi,diffneedsgit; the narratty image has all three. It runs its owntmuxserver with its own settings, so a~/.tmux.confdoes not change the recording. - Both also work with an
environment.
The editor layout example shows all of it.