How to Build a Basic Self‑Hosted Demo Capture and GIF Generator on Linux

By the end of this guide, you’ll have a fully local, repeatable pipeline on Linux that can:

Tower of stacked tape reels casting a long shadow, symbolizing automated app demo capture and replay.

How to Build a Basic Self‑Hosted Demo Capture and GIF Generator on Linux

By the end of this guide, you’ll have a fully local, repeatable pipeline on Linux that can:

  • Record short app demos with Peek.
  • Script repeatable flows with prompt-chain-style shell scripts ("promptchan-like" tools).
  • Convert recordings into optimized GIFs with FFmpeg.
  • Trigger everything from LunarVim so capture and export feel like part of your dev loop.

It’s a lightweight, self-hosted version of the “record app flow → polish output → ship fast” workflow that Reely gives macOS developers — tuned for non‑macOS environments.

If you want a broader comparison of self-hosted options, see this related guide: self-hosted demo video generators for devs who want everything local.

Prerequisites

Before step 1, you should have:

  • A Linux desktop (Ubuntu, Debian, Fedora, etc.).
  • X11 session recommended. Peek and xdotool are primarily X11-focused; Wayland support is improving but still inconsistent.
  • A GUI app you want to demo (web app in a browser, desktop app, etc.).
  • Basic command-line familiarity.

Tools we’ll use

  • Peek – region-based Linux screen recorder, optimized for short, silent GIF/WebM/MP4 screencasts.
  • FFmpeg – command-line media converter and GIF optimizer.
  • xdotool – simulate keyboard/mouse to script demo flows.
  • wmctrl – control and focus windows.
  • LunarVim – Neovim-based editor with first-class command and automation support.

All of these are local, self-hosted, and run entirely on your machine — no cloud processing.

1. Install Peek, FFmpeg, xdotool, wmctrl, and LunarVim

This step sets up the core self-hosted demo video generator stack.

Commands for Debian/Ubuntu-based systems

sudo apt update
sudo apt install peek ffmpeg xdotool wmctrl

  • Peek: designed for short screencasts of a selected screen area, can export GIF, WebM, or MP4.
  • FFmpeg: industry-standard for video transcoding and GIF optimization.
  • xdotool: simulates keyboard/mouse, perfect for scripted screen capture.
  • wmctrl: interacts with windows and virtual desktops via EWMH.

For LunarVim, follow their official installer (one-liner as of today):

bash <(curl -s https://raw.githubusercontent.com/lunarvim/lunarvim/master/utils/installer/install.sh)

Always check the latest install instructions at lunarvim.org in case the script changes.

Common failure at this step

  • Peek doesn’t work under Wayland: Peek’s GitHub issues document rough edges with Wayland and transparent windows. If you see black recordings or input issues:
    • Log into an Xorg/X11 session.
    • Or switch to a Wayland-compatible recorder and keep the rest of the pipeline intact.

2. Capture a Raw Demo with Peek

Here you’ll record a clean, short screencast of your app with Peek.

Steps

  1. Launch the app you want to demo.
  2. Start Peek from your app menu or terminal:peek &
  3. Resize and position the Peek window to tightly frame your app content.
  4. In Peek, choose output format:
    • Prefer WebM or MP4 for better quality + later GIF optimization.
    • You can still directly use GIF for quick tests.
  5. Click Record.
  6. Perform your app flow manually (for now):
    • Open a modal.
    • Click a button.
    • Show a short interaction.
  7. Click Stop and save the file as demo-raw.mp4 (or .webm).

Peek is explicitly designed for short, silent screencasts, making it ideal for Product Hunt-style app demos or short feature teasers.

Common failure at this step

  • Recording is blank or offset: usually due to compositor or Wayland issues.
    • Try disabling desktop effects.
    • Ensure Peek’s recording region isn’t overlapping multiple monitors.
    • Verify you saved as MP4/WebM and can play the output with:ffplay demo-raw.mp4

3. Convert Screen Recordings to an Optimized GIF with FFmpeg

Now you’ll turn the raw MP4/WebM into a high-quality GIF using FFmpeg’s palette workflow.

Why not export GIF directly from Peek?

Peek can export GIFs, but FFmpeg’s palettegen/paletteuse filters usually produce smaller files with better color — ideal for shareable app demos.

Commands

Assume you saved demo-raw.mp4 from Peek.

  1. Generate a color palette:

ffmpeg -i demo-raw.mp4 \
-vf "fps=12,scale=900:-1:flags=lanczos,palettegen" \
palette.png

  • fps=12 – smooth enough for UI motion without bloating file size.
  • scale=900:-1 – width of 900 px, auto height; good for blog posts and social.
  • lanczos – high-quality scaler for crisp UI text.
  1. Apply palette to create the final GIF:

ffmpeg -i demo-raw.mp4 -i palette.png \
-vf "fps=12,scale=900:-1:flags=lanczos[x];[x][1:v]paletteuse" \
demo-final.gif

  1. Inspect GIF size and quality:

identify demo-final.gif

Common failure at this step

  • GIF looks washed out or dithered: usually from skipping palettegen. Ensure you run both commands and re-use the same palette.png.
  • File too large: reduce fps (e.g., 8) or width (e.g., 720) in the filters.

4. Script a Repeatable Demo Flow with xdotool and wmctrl

This step is where you start mimicking a Reely-style agent: instead of freehand recording, your script drives the app demo.

Create a basic automation script

Create scripts/animate.sh in your project:

mkdir -p scripts
nano scripts/animate.sh

Paste:

#!/usr/bin/env bash
set -euo pipefail

APP_TITLE="MyApp" # window title or substring

# Focus the app window
wmctrl -a "$APP_TITLE"
sleep 1

# Example: navigate, type, click with readable pacing
xdotool key ctrl+l
sleep 0.5
xdotool type "https://example.local/demo"
sleep 0.5
xdotool key Return
sleep 1.5

# Click primary button
xdotool click 1
sleep 1

# Tab through fields and submit
xdotool key Tab Tab Return
sleep 1

Make it executable:

chmod +x scripts/animate.sh

This script:

  • Brings your app window to front with wmctrl.
  • Simulates keypresses and clicks with xdotool.
  • Uses explicit sleep calls to create demo-friendly pacing.

You can extend it with more steps:

  • Open a side panel.
  • Scroll a list.
  • Switch tabs.
  • Trigger a toast/notification.

Each command is a tiny, deterministic step — exactly the prompt-chain-style workflow you want for repeatable captures.

Common failure at this step

  • wmctrl can’t find the window: run wmctrl -l to list window titles and update APP_TITLE.
  • xdotool fails under Wayland: xdotool is X11-focused. For Wayland, consider ydotool or dotool, or run under an X11 session.

5. Combine Capture + Automation into a Single Script

Now you’ll orchestrate the whole flow: launch your app, run automation, and then convert to GIF.

Create a capture orchestration script

Create scripts/capture.sh:

nano scripts/capture.sh

Paste:

#!/usr/bin/env bash
set -euo pipefail

OUT_DIR="out/$(date +%F-%H%M%S)"
mkdir -p "$OUT_DIR"

# 1) Start your app (example: a dev server + browser)
# Adjust this section for your setup.

# Example: start a local web app in background
# (Assumes your app runs on http://localhost:3000)
# npm run dev &
# APP_PID=$!

# Give the app time to start
sleep 3

# 2) Prompt user to start Peek recording
cat <<EOF
***
Open Peek, position it over your app, and press Record.
Then press Enter here to run the scripted demo.
***
EOF

read -r _

# 3) Run scripted demo
bash scripts/animate.sh

# 4) Prompt to stop recording
cat <<EOF
***
Stop recording in Peek and save as $OUT_DIR/demo-raw.mp4.
Press Enter here after saving.
***
EOF

read -r _

# 5) Convert to optimized GIF
ffmpeg -i "$OUT_DIR/demo-raw.mp4" \
-vf "fps=12,scale=900:-1:flags=lanczos,palettegen" \
"$OUT_DIR/palette.png"

ffmpeg -i "$OUT_DIR/demo-raw.mp4" -i "$OUT_DIR/palette.png" \
-vf "fps=12,scale=900:-1:flags=lanczos[x];[x][1:v]paletteuse" \
"$OUT_DIR/demo-final.gif"

echo "Done. GIF saved to $OUT_DIR/demo-final.gif"

Make executable:

chmod +x scripts/capture.sh

This script:

  • Ensures consistent output folder naming (out/DATE-TIME).
  • Guides you through record → automate → stop → convert.
  • Keeps everything self-hosted with no cloud processing.

You could automate Peek control further using D-Bus or another recorder, but even semi-manual control here keeps the pipeline predictable.

Common failure at this step

  • FFmpeg can’t find demo-raw.mp4: double-check the save path. Ensure the filename and OUT_DIR in the script match what Peek saved.
  • App not ready before automation runs: increase sleep duration before calling animate.sh, or add repeated readiness checks.

6. Wire the Pipeline into LunarVim Automation

Now we integrate this workflow into LunarVim so you can trigger captures right from your editor — similar to how Reely’s agent sits alongside your coding tools.

Add custom commands in LunarVim

Open your LunarVim config (usually ~/.config/lvim/config.lua). Add:

-- Custom commands to drive the demo pipeline
vim.api.nvim_create_user_command("DemoCapture", function()
-- Run capture script and show output
vim.fn.system("bash scripts/capture.sh")
print("Demo capture pipeline finished")
end, {})

vim.api.nvim_create_user_command("DemoAnimate", function()
vim.fn.system("bash scripts/animate.sh")
print("Demo automation finished")
end, {})

Restart LunarVim or source your config.

Usage from LunarVim

Inside your project (with scripts/ checked into git):

  • Run the full pipeline:
    • :DemoCapture → follow the prompts.
  • Run just the automation (no new capture):
    • :DemoAnimate

You can also bind keymaps, e.g. in config.lua:

lvim.keys.normal_mode["<leader>dc"] = ":DemoCapture<CR>"
lvim.keys.normal_mode["<leader>da"] = ":DemoAnimate<CR>"

Now your editor becomes the control plane for a self-hosted demo video generator. The same project where you edit code is where you trigger new demo reels.

Common failure at this step

  • “bash: scripts/capture.sh: No such file or directory”: ensure scripts/ lives in your project root and you’re opening that directory in LunarVim.
  • Permission denied: run chmod +x scripts/*.sh.

7. Make It a Reusable, Reely-Style Pipeline

To make this truly practical, you want a repeatable pattern:

  • Per-feature branch, you can spin a new demo reel.
  • The same automation scripts evolve as the app grows.
  • Outputs land in predictable paths ready for CI, docs, or marketing.

Recommended project structure

my-app/
scripts/
animate.sh
capture.sh
out/
2026-09-13-1015/demo-final.gif
2026-09-13-1015/demo-raw.mp4
src/
...

Typical workflow

  1. Implement a new feature.
  2. Update scripts/animate.sh with the new flow.
  3. From LunarVim, run :DemoCapture.
  4. Upload out/.../demo-final.gif to:
    • README.
    • App docs.
    • Product Hunt, X, or Mastodon.
    • Internal changelogs.

This mimics the “promo from code in minutes” ethos:

  • Your automation script is the “spec” of the demo.
  • Peek and FFmpeg provide the motion.
  • LunarVim ties it into your everyday dev workflow.

Because everything is local and self-hosted, this pipeline aligns well with privacy-first teams who avoid SaaS upload tools.

Common failure at this step

  • Automation drift: scripts break as UI changes.
    • Treat animate.sh like code: keep it versioned, reviewed, and updated with UI changes.
  • Too many one-off scripts: prefer a single animate.sh with flags (e.g., --flow onboarding, --flow settings) rather than a new script per feature.

FAQ: Troubleshooting Your Self‑Hosted Demo Video Generator

1. Can I run this pipeline entirely offline?

Yes. Peek, FFmpeg, xdotool, wmctrl, and LunarVim all run locally on your Linux machine. There are no network calls required for capture, conversion, or automation. This makes the pipeline suitable for privacy-conscious teams and air-gapped environments.

2. How does this compare to Playwright’s built-in video recording?

Playwright can record browser sessions and output videos, which you can also feed into FFmpeg for GIFs. This Linux pipeline differs by:

  • Working with any GUI app, not only browsers.
  • Using Peek for flexible region capture.
  • Letting you script flows via xdotool and control them from LunarVim.

You can mix both: use Playwright videos for browser tests and this stack for desktop/GUI demos.

3. What if I’m on Wayland instead of X11?

Wayland is still a rough edge:

  • Peek and xdotool are X11-first. You may see black recordings or input failures.
  • Options:
    • Log in via an Xorg session when recording.
    • Use Wayland-friendly alternatives (ydotool, different screen recorders) while keeping FFmpeg + LunarVim.

4. How long should my demo GIFs be?

For shareable app demos:

  • Aim for 10–20 seconds for feature teasers.
  • 30–60 seconds for full walkthroughs.

Longer GIFs quickly become heavy. For longer content, share the MP4 instead and reserve GIFs for short, looping highlights.

5. Can I add text overlays or captions in this pipeline?

Yes. While this guide focuses on capture and scripting, you can:

  • Use FFmpeg filters (drawtext) for simple text overlays.
  • Or add a post-processing step in another local tool if you want more complex motion graphics.

For a true motion studio-level system (kinetic typography, device choreography, etc.), macOS developers can use Reely directly — and this Linux stack stays as a lightweight analog.

With this setup, you’ve built a self-hosted demo capture and GIF generator that respects your local-first workflow, plugs into your editor, and keeps your app as the source of truth for every launch-ready reel.

← All posts