diff --git a/README.md b/README.md index ec7e62a..69d6051 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ Stack allows building and deployment of a system of related containerized applications as a single "stack". Transparently deploy to local Docker, Podman or to remote Kubernetes. +![Building and deploying a three-container todo app — front end, API and PostgreSQL — to local Docker with stack](./docs/images/quickstart.gif) + +_An unedited recording of the [Docker quick start](#docker) below. Regenerate it with `./demo/record-quickstart.sh`._ + ## What is Stack good for? Stack is useful for a wide category of software applications including those that have a web app component, back-end services and optionally a database: web systems. Development of such a system ususally begins on a laptop where it's quick and easy to prototype concepts, try things out and iterate. @@ -34,6 +38,8 @@ stack init \ --map-ports-to-host localhost-same # create the deployment from the config +# (the parent directory must already exist) +mkdir -p ~/deployments stack deploy --spec-file todo.yml --deployment-dir ~/deployments/todo-docker # start / status / logs / stop @@ -61,6 +67,8 @@ stack init \ --config REACT_APP_API_URL=https://example-todo.myexample.com/api/todos # create the deployment from the config +# (the parent directory must already exist) +mkdir -p ~/deployments stack deploy --spec-file todo.yml --deployment-dir ~/deployments/todo-k8s # push image tags for this deployment to the image registry used by Kubernetes diff --git a/demo/quickstart.tape b/demo/quickstart.tape new file mode 100644 index 0000000..277d812 --- /dev/null +++ b/demo/quickstart.tape @@ -0,0 +1,143 @@ +# VHS tape for the README quick-start animation. +# +# Renders docs/images/quickstart.gif — a real, unfaked run of the Docker quick +# start from README.md against the example todo stack. +# +# Run it via ./demo/record-quickstart.sh, which prepares the caches this tape +# assumes. Running `vhs demo/quickstart.tape` directly will still work, but the +# build and clone steps may take minutes instead of seconds. +# +# Requires: vhs, ttyd, ffmpeg, docker, and the `stack` CLI on PATH. + +Output docs/images/quickstart.gif + +Set Shell bash +Set FontSize 14 +Set Width 1100 +Set Height 640 +Set Padding 18 +Set Framerate 20 +Set TypingSpeed 20ms +Set Theme "Catppuccin Mocha" + +# --- off-camera setup ------------------------------------------------------ +# Work in a scratch dir, with a scratch repo base dir, so the demo never +# collides with real clones or drops files in the repo. The `cd` and `export` +# apply to the recorded shell only; vhs resolves Output against its own cwd. +Hide +Type "cd /tmp/stack-demo && export STACK_REPO_BASE_DIR=/tmp/stack-demo/repos" +Enter +Type `PS1='\[\e[38;5;114m\]▸\[\e[0m\] '` +Enter +Type "clear" +Enter +Show + +# --- clone and build ------------------------------------------------------- +# Real clone (~2s) and a real container build (~8s, warm layer cache). +Type "stack fetch repo bozemanpass/example-todo-list" +Enter +Wait+Line@180s /^▸$/ +Sleep 1s + +# Show what the stack actually is before building it, so the rest of the +# recording has some context: three services, two built here, one stock +# postgres, and how requests reach them. +Type "stack chart --stack todo --format text" +Enter +Wait+Line@60s /^▸$/ +Sleep 3s + +# --quiet-build drops the docker build log, which is many screens of BuildKit +# step output that would swamp a short animation. It keeps the per-container +# progress lines. The README quick start deliberately shows the plain command; +# the flag is here only to keep the recording legible. +Type "stack prepare --stack todo --quiet-build" +Enter +Wait+Line@600s /^▸$/ +Sleep 2s + +Type "clear" +Enter + +# --- confirm the build prerequisites are in place -------------------------- +# Answers "did I already build the images?" without re-running a prepare that +# might take minutes — the question you have after stepping away from the build. +Type "stack check --stack todo" +Enter +Wait+Line@120s /^▸$/ +Sleep 2s + +# --- generate the deployment spec, then the deployment --------------------- +# init and deploy are near-instant and quiet, so pause a beat to let them be read. +Type "stack init --stack todo --output todo.yml --deploy-to compose --map-ports-to-host localhost-same" +Enter +Wait+Line@120s /^▸$/ +Sleep 1s + +# The whole generated spec is only 20 lines, so show all of it: the proxy +# routes, the host port mappings and the db volume are the interesting part. +Type "cat todo.yml" +Enter +Wait+Line@60s /^▸$/ +Sleep 2500ms + +Type "mkdir -p ~/deployments" +Enter +Wait+Line@60s /^▸$/ + +Type "stack deploy --spec-file todo.yml --deployment-dir ~/deployments/todo-demo" +Enter +Wait+Line@180s /^▸$/ +Sleep 1500ms + +Type "clear" +Enter + +# --- run it ---------------------------------------------------------------- +# ~12s: containers created, db comes up healthy, then the backend starts. +Type "stack manage --dir ~/deployments/todo-demo start" +Enter +Wait+Line@300s /^▸$/ +Sleep 1500ms + +Type "stack manage --dir ~/deployments/todo-demo status" +Enter +Wait+Line@120s /^▸$/ +Sleep 2s + +# --- prove the whole system works, not just that a port answers ------------ +# The web app is served... +Type `curl -s -o /dev/null -w "frontend HTTP %{http_code}\n" localhost:3000` +Enter +Wait+Line@60s /^▸$/ +Sleep 1500ms + +# ...and the API is backed by a working database. The list starts empty +# because the recorder removes the deployment (and its db volume) each run. +Type "curl -s localhost:5000/ | jq -c ." +Enter +Wait+Line@60s /^▸$/ +Sleep 2s + +# The id comes back from postgres, so a successful round trip proves the +# backend and the database, not merely that the web server responded. +Type `curl -s -X POST localhost:5000/ \` +Enter +Type ` -H 'Content-Type: application/json' \` +Enter +Type ` -d '{"title":"Deploy without a PaaS"}' | jq -c '{id, title, completed}'` +Enter +Wait+Line@60s /^▸$/ +Sleep 2500ms + +Type `curl -s localhost:5000/ | jq -c '.[] | {id, title, completed}'` +Enter +Wait+Line@60s /^▸$/ +Sleep 3s + +# --- and tear down --------------------------------------------------------- +Type "stack manage --dir ~/deployments/todo-demo stop" +Enter +Wait+Line@180s /^▸$/ +Sleep 3s diff --git a/demo/record-quickstart.sh b/demo/record-quickstart.sh new file mode 100755 index 0000000..410bb41 --- /dev/null +++ b/demo/record-quickstart.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# +# Record docs/images/quickstart.gif from demo/quickstart.tape. +# +# The recording is a real run of the README quick start — no output is faked. +# To keep it short enough to work as a README animation, this script prepares +# state off camera so that each recorded command does genuine work but returns +# quickly: +# +# * STACK_REPO_BASE_DIR points at a scratch dir, so the recorded `stack fetch` +# performs a real clone. This also means your existing clones under +# ~/.config/stack/repos are never touched. +# * The todo image *tags* are removed, but Docker's build layer cache is left +# intact, so the recorded `stack prepare` runs a real build (~8s) rather +# than reporting "existing-image" or rebuilding from scratch (minutes). +# +# Requires: vhs, ttyd, ffmpeg, docker, and the `stack` CLI on PATH. + +set -euo pipefail + +cd "$(dirname "$0")/.." +REPO_ROOT="$PWD" +# Deliberately outside $HOME: `stack fetch` prints the absolute clone destination, +# so a scratch dir under $HOME would put the recorder's username in the recording. +SCRATCH="/tmp/stack-demo" +DEPLOYMENT="$HOME/deployments/todo-demo" + +export STACK_REPO_BASE_DIR="$SCRATCH/repos" + +missing=() +for tool in vhs ttyd ffmpeg docker stack; do + command -v "$tool" >/dev/null || missing+=("$tool") +done +if [ ${#missing[@]} -gt 0 ]; then + echo "error: not on PATH: ${missing[*]}" >&2 + echo " ffmpeg, ttyd: sudo apt-get install -y ffmpeg ttyd" >&2 + echo " vhs: https://github.com/charmbracelet/vhs/releases" >&2 + exit 1 +fi + +echo "==> Cleaning previous demo state" +if [ -d "$DEPLOYMENT" ]; then + # Best effort — the deployment may already be stopped. + stack manage --dir "$DEPLOYMENT" stop || true + rm -rf "$DEPLOYMENT" +fi +rm -rf "$SCRATCH" +mkdir -p "$SCRATCH/repos" +# Note: ~/deployments is deliberately NOT created here. `stack deploy` requires +# the parent of --deployment-dir to already exist, and the tape creates it on +# camera so the recording matches the README. +rmdir "$HOME/deployments" 2>/dev/null || true + +echo "==> Warming the build cache (off camera)" +cd "$SCRATCH" +stack fetch repo bozemanpass/example-todo-list +stack prepare --stack todo + +echo "==> Dropping image tags so the recorded build is real but cached" +docker images --format '{{.Repository}}:{{.Tag}}' \ + | grep -E '^bozemanpass/todo-' \ + | xargs -r docker rmi >/dev/null 2>&1 || true + +echo "==> Resetting the scratch clone so the recorded fetch is a real clone" +rm -rf "$SCRATCH/repos" +mkdir -p "$SCRATCH/repos" + +echo "==> Recording" +cd "$REPO_ROOT" +vhs demo/quickstart.tape + +echo "==> Cleaning up" +if [ -d "$DEPLOYMENT" ]; then + stack manage --dir "$DEPLOYMENT" stop || true + rm -rf "$DEPLOYMENT" +fi +rm -rf "$SCRATCH" + +echo +echo "Wrote $REPO_ROOT/docs/images/quickstart.gif" +ls -lh "$REPO_ROOT/docs/images/quickstart.gif" diff --git a/docs/images/quickstart.gif b/docs/images/quickstart.gif new file mode 100644 index 0000000..0f8f4ea Binary files /dev/null and b/docs/images/quickstart.gif differ