Decision Guide

Instruqt Decision Trees

Sixteen architectural decisions you will make on every track, rendered as navigable reference cards.

16
Trees
46
Questions
75
Recommendations
  • Each tree is read top to bottom: questions are numbered, answers either resolve to a recommendation (green) or send you to the next question (gray).
  • Every recommendation card carries a single primary link to the implementing recipe in the companion Pattern Cookbook — chase that link when you are ready to implement.

Container, virtual machine, or sandbox preset

The first architectural decision in any track. Boot time, capability, image selection, and which lifecycle helpers work all flow from this single choice. Walk the questions in order. Tree 11 covers the preset-versus-custom choice in more depth — start there if your product already has a preset.

Question 1

Is there an Instruqt sandbox preset for the product you are demonstrating that covers roughly 90% or more of what you need?

Yes
Recommendation
Use the sandbox preset.
Skip config.yml entirely. You eliminate boot timing, image pulls, and agent setup as failure modes — focus on assignment content. Fight the platform only if the preset is below a 60% fit. Tree 11 walks the preset-vs-custom decision in detail.
→ Cookbook Recipe 6: Sandbox preset adoption
No
Continue to Question 2.
Question 2

Does your track require any of the following: a display server (X11 or Xvfb), a real kernel and systemd, nested virtualization, or a custom image with heavy preinstalled dependencies?

Yes — at least one
Continue to Question 3.
No
Continue to Question 4.
Question 3

Will a stock cloud image (Ubuntu LTS, Debian) work, or do you need a custom image with the dependencies pre-baked?

Stock image is fine
Recommendation
Use a virtual machine with a stock cloud image.
Boot is roughly one minute. Install your tooling in the setup script. The agent helpers (fail-message, set-status) are available on most platform-published VM images and on stock cloud images via bootstrap. If you build on a raw upstream image that doesn't ship them, fall back to plain echo plus exit 1 in check scripts.
→ Cookbook Recipe 2: Single VM track
Need a custom image
Recommendation
Use a virtual machine with a custom image.
Bake heavy dependencies into a published image. Faster boot than installing at setup time. Trade-off: you maintain the image as the underlying product evolves. Tree 14 walks the custom-image-vs-runtime-generation decision.
→ Cookbook Recipe 2: Single VM track (custom-image variant)
Question 4

Does your track use cloud CLIs (aws, az, gcloud)?

Yes
Recommendation
Use a container with the cloud-client image.
Ships with AWS, Azure, and GCP CLIs preinstalled, plus common scripting tools. Image pull takes longer than the lighter shell image, but the alternative is installing CLIs yourself in setup.
→ Cookbook Recipe 1: Single container track
No
Recommendation
Use a container with the lightweight shell image.
The fastest-booting option in the platform — well under thirty seconds. fail-message, set-status, and the agent variable commands are built in.
→ Cookbook Recipe 1: Single container track

Choosing the right tab type

Most production tracks pair the terminal with one or more browser-rendered surfaces. The tab type is how you choose what the learner sees — and the choice depends entirely on what the surface connects to.

Question 1

What kind of surface is the learner viewing or interacting with?

Code or text the learner is editing
Recommendation
Use a code tab.
Pair an editable starter file with a second code tab pointing at a completed reference implementation as a built-in hint. The platform handles the file-editing UX entirely.
→ Cookbook Recipe 31: Companion code-tab pair
A web UI served by your own infrastructure
Recommendation
Use a service tab.
Routes through Instruqt's managed proxy. Critical: any client-side fetch() calls must use relative paths — the learner's browser cannot resolve internal Docker hostnames. If you need a portal and an API, co-host them on the same hostname and port. If the upstream sets X-Frame-Options or a strict CSP, layer in the header-override pattern.
→ Cookbook Recipe 25: Co-hosted portal plus API · Recipe 43: Service-tab CSP and header overrides
A cloud console or external web app
Continue to Question 2.
A graphical desktop application
Continue to Question 3.
Question 2

Does the platform need to manage authentication and session state for the learner?

Yes — provisioned cloud console
Recommendation
Use a browser tab tied to a virtualbrowsers block.
AWS, Azure, and GCP consoles authenticate using the credentials the platform provisioned for the lab. The virtualbrowsers block is what enables that handoff.
→ Cookbook Recipe 15: VM plus virtual browser
No — public URL
Recommendation
Use a website tab.
Strict requirement: the URL must be HTTPS. An http:// URL causes instruqt track push to fail at validation, and the failure mode is silent at the platform level. ${_SANDBOX_ID} interpolation is supported in the url field for sandbox-public URLs.
→ Cookbook Recipe 39: Sandbox-public URL exposure
Question 3

Is the learner using one specific application, a full Linux desktop, or a Windows RDP session?

One application
Recommendation
Use the VM plus KasmVNC single-app pattern.
KasmVNC wraps a single GUI app and serves it over WebVNC on port 8080. Must run on a VM (display server requirement). Pre-pull the image in your custom VM image to avoid timeout during live setup.
→ Cookbook Recipe 18: VM plus KasmVNC single-app
Full Linux desktop
Recommendation
Use the VM plus noVNC Linux desktop pattern.
Install tightvncserver and xfce4 on the VM, then layer noVNC to bridge VNC to WebSocket. Use the sandbox-aware URL for the website tab so it ties to the VM's external IP.
→ Cookbook Recipe 17: VM plus noVNC Linux desktop
Windows RDP
Recommendation
Use the VM plus Guacamole desktop pattern.
Heavier setup. Write the Guacamole user-mapping XML during VM setup and poll for RDP readiness on port 3389 before exiting setup. A native PowerShell terminal tab pointed at a Windows VM is a lighter alternative when learners only need a console, not a GUI.
→ Cookbook Recipe 16: VM plus Guacamole desktop

One host, many containers, many VMs, or hybrid

Most tracks need exactly one host. Some need two or more — a workstation plus the system being managed, a workstation plus a database, a primary plus a replica. The shape of the composition determines the recipe. Take the compute decision from Tree 1 first, then walk this tree.

Question 1

Does the lesson require more than one host of compute?

No — one host is enough
Recommendation
Use a single-host track.
Recipe 1 (single container) or Recipe 2 (single VM), depending on Tree 1's outcome. Most tracks land here. Resist adding hosts the lesson does not need — every additional host is another setup script, another boot timing window, another failure mode.
→ Cookbook Recipe 1: Single container track · Recipe 2: Single VM track
Yes — multiple hosts
Continue to Question 2.
Question 2

Are all the hosts the same compute type?

All containers
Recommendation
Use multi-container composition.
Declare each container under the containers block in config.yml, give each a hostname, and write per-host setup scripts (setup-workstation, setup-db, setup-queue). Containers boot in parallel, not in dependency order — always poll for readiness before any setup that depends on a peer service.
→ Cookbook Recipe 3: Multi-container composition · Recipe 22: Wait-for-port readiness probe
All VMs
Recommendation
Use multi-VM composition.
Declare each VM under virtualmachines. Boot is 60–120 seconds per host. Each VM's setup script runs independently with no implicit ordering. If one host's setup needs another running first, gate with an agent-variable hand-off.
→ Cookbook Recipe 4: Multi-VM composition · Recipe 34: Agent variable cross-host hand-off
Mixed — at least one container and one VM
Continue to Question 3.
Question 3

Is one host the workstation the learner uses, and the others are systems being managed?

Yes — workstation plus managed VM(s)
Recommendation
Use container plus managed VM.
The workstation is a container (fast boot, terminal tab targets it). The systems being managed are VMs. Provision SSH from the workstation to each VM via agent-variable hand-off. When a managed host should not have a public IP, layer in IAP tunnel on GCP.
→ Cookbook Recipe 5: Container plus managed VM · Recipe 11: Container plus private VM behind IAP tunnel
No — custom hybrid topology
Recommendation
Compose explicitly in config.yml.
Declare each host under its native block (containers, virtualmachines, sandbox). Write a setup script per host. Order assumptions become your problem — name them, document them in track_scripts comments, and gate with wait-for-port (Recipe 22) or agent-variable hand-off (Recipe 34) wherever ordering matters. Keep the topology as flat as the lesson allows.
→ Cookbook Recipe 4: Multi-VM composition · Recipe 5: Container plus managed VM

Choosing the right cloud account block

If the lesson touches a cloud, the cloud account block in config.yml is what makes the credentials show up in the workstation. The wrong block — or a block configured by copy-paste from a different cloud — produces silent auth failures that look like permission bugs. A common trap: scripts that reference INSTRUQT_AWS_*, INSTRUQT_AZURE_*, or INSTRUQT_GCP_* variables receive empty strings at runtime when no matching resource block is declared.

Question 1

Does the lesson require any cloud account at all?

No — pure local lab
Recommendation
Skip cloud account blocks entirely.
No aws_accounts, azure_subscriptions, or gcp_projects in config.yml. The track runs entirely on the compute you declared in Trees 1 and 3. Faster boot, simpler auth, fewer cost moving parts.
→ Cookbook Recipe 1: Single container track · Recipe 2: Single VM track
Yes — at least one cloud
Continue to Question 2.
Question 2

How many distinct clouds does the lesson touch?

One cloud
Continue to Question 3.
More than one cloud
Recommendation
Use a multi-cloud track.
Stack multiple cloud account blocks. Each cloud authenticates independently with its own provisioned credentials. Keep credential injection per-cloud — do not try to share a single setup snippet across AWS and Azure; their auth handshakes are not symmetric.
→ Cookbook Recipe 10: Multi-cloud track wiring
Question 3

Which cloud is the lesson on?

AWS
Recommendation
Use container plus AWS account.
Declare aws_accounts. Auth uses provisioned IAM credentials injected as environment variables. If the lesson needs the AWS console rather than just the CLI, layer in a virtualbrowsers block and a browser tab.
→ Cookbook Recipe 7: Container plus AWS account
Azure
Recommendation
Use container plus Azure subscription.
Declare azure_subscriptions. Azure has its own auth model and its own credential injection convention; do not copy-paste from an AWS track. The az login handshake is the most common copy-paste failure point.
→ Cookbook Recipe 8: Container plus Azure subscription
GCP
Continue to Question 4.
Question 4

Does the lesson involve a private GCP VM that should not have a public IP?

Yes — private VM behind IAP
Recommendation
Use container plus private GCP VM behind IAP tunnel.
SSH reaches the VM through Google's Identity-Aware Proxy. The workstation runs gcloud compute start-iap-tunnel; SSH is configured to use that local port. The learner experiences a normal SSH session; the network shape is genuinely private.
→ Cookbook Recipe 11: Container plus private VM behind IAP tunnel
No — standard public VM or no VM
Recommendation
Use container plus GCP project.
Declare gcp_projects. Activate user credentials in the workstation's setup so the terminal opens authenticated. For admin operations during setup, override the active credential — never leave the active gcloud session as the admin's, or every check that validates "the user can do X" trivially passes.
→ Cookbook Recipe 9: Container plus GCP project · Recipe 35: Cloud SDK auth credential override

Passing values between hosts and across challenges

A single-host track has one shell, one filesystem, and one process tree — state lives wherever you put it. The moment a track has two hosts, or one host plus multiple challenges, you need a discipline for moving values across those boundaries. The three options — agent variable, JSON broker, and cross-VM env passthrough — are not mutually exclusive; complex multi-VM tracks routinely use all three for different purposes.

Question 1

Does any host need a value that is computed by a different host?

Yes — cross-host value flow
Continue to Question 2.
No — values stay on one host
Continue to Question 4.
Question 2

Is the value a single string, or a structured payload with several fields?

Single string (an IP, a token, an ID, a password)
Continue to Question 3.
Structured payload (multiple fields, several consumers)
Recommendation
Use a JSON broker sidecar.
Stand up a small HTTP server on a known port (commonly 8081) that returns the structured payload as JSON. Downstream VMs curl the broker URL during their own setup and parse with jq. Avoids the string-parsing-JSON-in-shell trap and avoids hitting agent variable's effective single-key limit.
→ Cookbook Recipe 40: JSON broker sidecar for cross-VM payload
Question 3

Is the cross-host signal "host A is up" (readiness), or a value the producer computed?

Just a readiness signal
Recommendation
Use a wait-for-port readiness probe.
The dependent host polls a port on the dependency until it accepts connections, then proceeds. Cheap, deterministic, no shared state needed. Common shapes: pg_isready -h db, redis-cli -h queue ping, or plain nc -z. Reserve blind sleep N for inherently undetectable waits like DNS TTL propagation.
→ Cookbook Recipe 22: Wait-for-port readiness probe
A computed value
Recommendation
Use the agent variable cross-host hand-off.
Producing host writes with `agent variable set KEY=value`; consuming host reads with `agent variable get KEY`. Values persist for the life of the track and can also be surfaced in assignment.md via [[ Instruqt-Var ]] interpolation. Compose with wait-for-port when the consumer needs both a value and a readiness guarantee.
→ Cookbook Recipe 34: Agent variable cross-host hand-off
Question 4

Does any environment variable need to persist on the workstation across challenges and across both login and non-login shells?

Yes — persistent shell environment
Recommendation
Use the profile.d plus .bashrc dual-write pattern.
Login shells (such as su -) read /etc/profile.d/*.sh; non-login interactive shells (terminal tab default) read ~/.bashrc. Setting the value in only one place leaves the other shell with stale or missing state. Dual-write covers both.
→ Cookbook Recipe 28: Profile.d plus .bashrc dual-write
No — values are local to a single script run
Recommendation
No state pattern needed.
Plain bash variables in the setup or check script are sufficient. Resist promoting a transient value to agent variable or .bashrc just because it exists — every promoted value is a future debugging surface.

Lazy, granular, or scored

A check script is the conversation the lab has with the learner when they click Check. The shape of that conversation is a design choice. The wrong shape teaches the wrong thing — a single-line "Try again" on a check that tested nine things is a worse experience than no check at all. Tree 13 (engagement validation depth) covers when to add anti-cheat instrumentation on top of any of these.

Question 1

Is this a certification-style track where the learner needs a numerical score at the end?

Yes — scored assessment
Recommendation
Use a scored certification rubric.
Each criterion contributes points. The final challenge tallies and surfaces a percentage with color-coded bands (red below 50%, orange ≥50%, green 100%). The lab feels like an exam: items, score, breakdown of what was missed. A binary pass/fail at the final challenge is too coarse for assessment, and a granular check per challenge runs the lab too quickly to feel like one.
→ Cookbook Recipe 37: Scored certification rubric
No — ordinary tutorial or demo
Continue to Question 2.
Question 2

Does the assignment make several distinct claims about the system state, or one single claim?

Several distinct claims (e.g., user exists, has the right role, can SELECT, cannot DELETE)
Recommendation
Use granular check messaging.
Each assertion gets its own line: ✓ on pass, ✗ with a one-line hint on fail. The learner sees exactly which claim failed and what to do about it. Compose with a Python helper module for consistent reporting across many checks. Pair with the failure-message anatomy pattern so each fail line has a hint, a where, and a how-to-fix.
→ Cookbook Recipe 36: Granular check messaging · Recipe 26: Helper Python module for tests · Recipe 50: Failure-message anatomy
One claim only
Recommendation
Use the lazy pattern.
Plain bash: a single test, a fail-message line where the helper is available (or echo + exit 1 if the image doesn't ship it), exit 0 otherwise. Resist inventing structure for a single assertion. The lazy pattern is not cynical — it is the right shape when the lesson is the right shape.
→ Cookbook Recipe 50: Failure-message anatomy

Step-based, prose, recap, or read-only intro

Every challenge has a body — the markdown the learner reads in the assignment pane. The body's shape signals what kind of work is ahead: study this, do these steps, recall what we covered, complete this single task. Picking the wrong shape works against the lesson.

Question 1

Does the learner perform any concrete action in this challenge — typing a command, editing a file, clicking through a UI — or is the body itself the lesson?

No action — the body is the lesson
Recommendation
Use a read-only intro challenge.
No check script. The body is the lesson. The learner reads the diagram, the architecture, the "what we are building," then clicks to advance. Use sparingly — read-only challenges consume a slot in the track without producing a graded outcome.
→ Cookbook Recipe 53: Read-only intro challenge
The learner does something concrete
Continue to Question 2.
Question 2

Is this challenge primarily about reinforcing concepts the learner has already practiced, or producing new work?

Reinforcing prior material — synthesis or recall
Recommendation
Use a mid-track recap challenge.
Lightweight check, focused body that re-summarizes what the prior block covered. Often combined with a small synthesis exercise (file-based quiz, single command). The pacing function is the point; the check is secondary.
→ Cookbook Recipe 52: Mid-track recap challenge
Producing new work
Continue to Question 3.
Question 3

Does the work involve multiple discrete steps the learner must perform in sequence, or is it a single focused task?

Multi-step task — sequence matters
Recommendation
Use the step-based body structure.
Number the steps. Keep each step short. Code blocks are runnable; learner-supplied placeholders (`<DOCUMENT_ID>`) sit in plain or `copy`-modifier blocks so the learner cannot run them blindly. Pair opt-in `<details>` collapsibles with the step body for graduated hints.
→ Cookbook Recipe 44: Step-based body structure · Recipe 51: Layered hint · Recipe 54: Opt-in details collapsible
Single-task lab — short and direct
Recommendation
Use a free-prose body.
Two or three paragraphs of context, one explicit goal, one or two code snippets, done. Do not invent steps for a one-step task. The numbered-list shape implies multiple steps; using it for a single step adds noise to the page.

Heredoc, sparse-checkout, or full git clone

Most tracks need to put a project tree in front of the learner at the start of a challenge: a config file to edit, a function to implement, a folder of fixtures. There are three reasonable ways to do that, and the choice depends on size, sensitivity of source, and how much editorial control you want. Tree 14 covers the related image-vs-runtime decision when the dependency stack itself is heavy.

Question 1

How big is the project tree the learner needs?

Small — single file or a handful, total under ~50 KB
Recommendation
Use heredoc-based project generation.
Write the files inline from the setup script with `cat > path/to/file << 'EOF' ... EOF`. The track is fully self-contained: no external repo to maintain, no clone failure mode, no rate limit. Quote the heredoc terminator (single quotes around 'EOF') if the body should not be interpolated.
→ Cookbook Recipe 24: Heredoc-based project generation
Medium — a few directories with a known dependency set
Continue to Question 2.
Large — a real codebase
Continue to Question 3.
Question 2

Does the learner need to see and edit one specific file with a "completed" reference version visible alongside?

Yes — file editing with a reference
Recommendation
Use the companion code-tab pair.
Generate the working file and the reference implementation as two separate files in the project tree. Surface them as two `code` tabs: the working file is editable, the reference is read-only. The learner has the answer one tab away — which is the point.
→ Cookbook Recipe 31: Companion code-tab pair
No — just generate the tree and move on
Recommendation
Heredoc the project tree.
Faster than git clone, no external dependency, the entire tree lives in the track repo. For a directory tree, mkdir the folders first, then heredoc each file. Acceptable up to roughly 100 KB; past that the cost of the heredoc grows past the cost of a managed dependency.
→ Cookbook Recipe 24: Heredoc-based project generation
Question 3

Is the source repository public, or is it private and credentialed?

Public, full repo is fine
Recommendation
git clone from a versioned repo.
Pin to a tag, never to a branch. The lab is reproducible only if the upstream is reproducible — a moving branch will eventually break the track silently. Mirror the repo internally if the upstream is not under your control.
Private, or you only need a subset
Recommendation
Use sparse-checkout with a short-lived token.
git sparse-checkout fetches only the paths you need, which keeps the on-disk footprint small and avoids exposing unrelated source. Pull the token from the secrets block, never from environment in config.yml or a hardcoded URL — both leak through repo history. The setup script uses the token only for the clone and immediately unsets it.
→ Cookbook Recipe 41: Sparse-checkout from a private repo with a short-lived token

How custom should the lesson feel

Branding is a spectrum, not a switch. At one end: a stock root prompt and a generic shell. At the other: a non-root user with a customer-themed prompt, a custom CLI wrapper, a branded portal page, and loading messages that name the product. The right point on the spectrum is set by what the lesson is teaching.

Question 1

Is the lesson teaching the customer's own product, or a generic technology that happens to be relevant?

Generic technology (Linux, Postgres, Kubernetes, Terraform)
Recommendation
Skip branding.
Default shell prompt, root user, no CLI wrapper. The technology is the lesson; branding only competes for attention. The track is shorter to maintain and easier to retarget if the customer pivots.
The customer's product
Continue to Question 2.
Question 2

Does the lesson need a branded user identity — a specific username that appears in prompts, screenshots, and shell history?

Yes — non-root, named user
Recommendation
Use the non-root user pattern.
Create the user in setup, copy or symlink dotfiles so env vars work in their session, set the prompt via /etc/profile.d. Combine with profile.d plus .bashrc dual-write so login and non-login shells both pick up the branded environment.
→ Cookbook Recipe 29: Non-root user pattern · Recipe 28: Profile.d plus .bashrc dual-write
No — root is fine, prompt only
Continue to Question 3.
Question 3

Does the customer have a CLI the lesson should demonstrate?

Yes — a real production CLI
Recommendation
Install the real CLI in the workstation image.
Bake it in if you control the image; install it in setup if not. Ensure it lands on PATH. If the CLI calls a backend that does not exist in the lab, wrap it with the idempotent binary wrapper pattern so retries are safe and re-runs do not infinitely re-wrap a wrapper.
→ Cookbook Recipe 27: Idempotent binary wrapper
Yes — but it is a teaching aid (mock, demo-shaped)
Recommendation
Build a custom CLI wrapper.
A small Python or Node CLI that calls the lab's API (real or mocked). Use a library like httpx for HTTP and rich for output formatting — the learner sees colored tables, formatted JSON, and helpful error messages. The CLI is part of the lesson, not just a polish item.
→ Cookbook Recipe 30: Custom CLI wrapper · Recipe 13: Container plus mock REST API and custom CLI
No — prompt and portal only
Recommendation
Apply prompt and portal page only.
Set the prompt via profile.d. A branded portal page (co-hosted portal plus API) carries the visual identity. A branded credential dashboard is the smallest "looks like a product" surface that pays for itself on demos. This is the lightest branded shape — present enough to feel custom, light enough to maintain.
→ Cookbook Recipe 28: Profile.d plus .bashrc dual-write · Recipe 25: Co-hosted portal plus API · Recipe 55: Branded credential dashboard

timelimit, idle_timeout, pausable, skipping_enabled

A track has a clock whether you set one or not. Choosing the clock settings deliberately matters because the wrong settings sabotage the experience: a workshop that times out mid-lesson, a self-paced lab that demands an unbroken hour, an instructor who cannot skip a challenge that ran long.

Question 1

How will the lab be delivered?

Live (workshop, instructor demo, conference session)
Continue to Question 2.
Self-paced (asynchronous, learner picks when)
Continue to Question 3.
Question 2

Does the live session have a hard wall-clock deadline?

Yes — fixed end time (e.g., a 60-minute workshop)
Recommendation
Tight timed-live configuration.
Set timelimit to the deadline (60m). Set show_timer: true so the learner sees the clock. Disable pausable. Disable skipping_enabled — live mode is sequential. Loading messages should be short; per-challenge enhanced_loading is overkill when the instructor is talking through it.
No — instructor controls pacing
Recommendation
Loose instructor-led configuration.
Set timelimit generously (2–4 hours). Disable show_timer — visible countdowns create artificial pressure when there is no real deadline. Enable skipping_enabled so the instructor can jump challenges as the demo evolves. Disable pausable.
Question 3

Will most learners complete the lab in one sitting, or will they typically need to step away?

Step-away likely (lab takes longer than 30 minutes)
Recommendation
Pausable, resumable configuration.
Set pausable: true and pausable_ttl to 7 days (604800) or longer. Leave idle_timeout at the platform default — the learner pauses explicitly when stepping away, rather than racing the idle clock. Enable skipping_enabled so a returning learner can resume without re-doing prior challenges. Lab state must survive an arbitrary suspend — avoid in-memory caches and tmux processes. Combine with defensive challenge-level setup so a returning learner does not hit broken state.
→ Cookbook Recipe 32: Defensive challenge-level setup · Recipe 33: Idempotent solve script
One sitting (lab takes 30 minutes or less)
Recommendation
Single-sitting configuration.
Disable pausable. Set idle_timeout: 30m to forgive a short interruption (a phone call, a tab switch) without ending the session. Disable skipping_enabled — the lab is short enough to complete linearly, and skipping invites confusion when later checks depend on earlier setup.

When the preset is enough and when it is a wall

A sandbox preset gives you a shared, fast-starting environment that the platform manages for you. The trade is loss of control: you can't run setup scripts in track_scripts, can't bring a custom image, can't compose multiple hosts, can't inject secrets, can't shape per-track networking. The preset is a real win when its defaults already match the lesson; it is a wall the moment they don't. New contributors often pick presets thinking they're "easier" and then hit that wall when a setup script becomes necessary — start with config.yml if there is any doubt.

Question 1

Is the lesson purely click-through against a managed UI (a hosted Kibana, the AKS console, Postgres in a managed playground)?

Yes — UI clicks only
Continue to Question 2.
No — there is a terminal, a script, or non-trivial setup
Recommendation
Use a custom config.yml.
If the lesson needs setup scripts, custom images, multiple hosts, secrets injection, or per-track networking — any one of those — the preset cannot do it. Skip ahead and shape the track in config.yml.
→ Cookbook Recipe 1: Single container track · Recipe 2: Single VM track
Question 2

Does an existing preset's pre-installed tooling cover roughly 90% or more of what the lesson needs?

Yes — fit is ≥ ~90%
Recommendation
Use the sandbox preset.
Skip config.yml entirely. Sandbox preset adoption eliminates boot timing, image pulls, and agent setup as failure modes. The first thirty seconds of the learner experience cost you nothing.
→ Cookbook Recipe 6: Sandbox preset adoption
Partial fit — 60% to 90%
Continue to Question 3.
Below ~60%
Recommendation
Use a custom config.yml.
Below 60% fit, you spend more effort working around the preset than you would have spent shaping the environment yourself. Pick the compute via Tree 1 and shape the rest in config.yml.
Question 3

Will the gap require setup scripts, secret injection, multiple hosts, or per-track networking — any of which a preset cannot provide?

Yes — at least one of those
Recommendation
Use a custom config.yml.
The preset stops at the platform boundary. The moment you need track_scripts, a secrets block, more than one host, or shaped networking, the preset locks you out. Move to config.yml early — refactoring later costs more than starting over.
No — small layered tweaks at challenge level
Recommendation
Use the sandbox preset with challenge-level overrides.
Most preset gaps that aren't infrastructure can be papered over in the assignment body or with per-tab settings (cmd:, workdir:, header overrides on a service tab). If the gap stays in the assignment layer, the preset still pays.
→ Cookbook Recipe 6: Sandbox preset adoption · Recipe 43: Service-tab CSP and header overrides

secrets block, environment block, runtime fetch, or never

Anything sensitive — an API key, an SSH private key, a registry credential, a license key, a long-lived service token — needs a deliberate home. Two anti-patterns dominate: baking the value into config.yml's environment block (visible to every learner via the debug log, and committed to git history forever) and base64-encoding it inside a setup script (base64 is not encryption). The right home depends on what the value is and how it gets used. The general framing: anything that proves identity or grants access goes in the secrets block; anything that just shapes behavior can sit in environment.

Question 1

Is the value sensitive — a credential, a key, a token, a license, anything that proves identity or grants access?

No — it is just configuration (a region name, a feature flag, a service URL)
Recommendation
Put it in the environment block.
The environment block is the right place for non-sensitive shape parameters. They are visible in the debug log on purpose — the lab is supposed to surface them. Reserve secrets for actual credentials.
Yes — sensitive
Continue to Question 2.
Question 2

Does the credential need to be the same value for every learner, or unique per sandbox?

Same value for every learner (a service license, a shared API key, a CA private key)
Recommendation
Put it in the secrets block.
Declare `secrets: - name: MY_SECRET` in config.yml; set the value in the Instruqt platform UI. Secrets are injected at runtime, never committed to git, and never appear in the debug log. Read the secret in your setup script, write it to disk only as long as it is needed, then delete it. This is the right home for SSH private keys, registry credentials (raw dockerconfigjson, not base64), and CA private keys used to mint per-host TLS certs.
Unique per sandbox or per learner
Continue to Question 3.
Question 3

Is the unique value something the platform already provisions for you (cloud account credentials, a sandbox-scoped TLS certificate)?

Yes — platform-provisioned
Recommendation
Use the platform-provided injection — no secrets entry needed.
Cloud credentials arrive as INSTRUQT_AWS_*, INSTRUQT_AZURE_*, or INSTRUQT_GCP_* environment variables when the matching resource block (aws_accounts, azure_subscriptions, gcp_projects) exists in config.yml. provision_ssl_certificate: true on a VM mints and rotates a per-sandbox cert, retrievable from GCP instance metadata at runtime. Both patterns are auth-free from your perspective: you neither inject nor delete the credential — the platform does.
→ Cookbook Recipe 7: Container plus AWS account · Recipe 8: Container plus Azure subscription · Recipe 9: Container plus GCP project
No — fetch from an external secret store at runtime
Recommendation
Fetch at setup time from a secret store, using a bootstrap secret.
Hold a long-lived bootstrap credential (service-account JSON, GCS-bucket reader, Vault token) in the secrets block, use it once during setup to fetch per-sandbox material from your store, then unset it. The bootstrap credential never touches disk; the per-sandbox material lives only as long as the sandbox does. Suitable for sparse-checkout from a private repo, dynamic license issuance, and per-attendee credential generation.
→ Cookbook Recipe 41: Sparse-checkout from a private repo with a short-lived token

When to add anti-cheat instrumentation, and how much

Engagement validation is the toolbox of patterns that confirm a learner actually did the lesson rather than clicked Check ten times in a row: shell-history forensics, anti-rapid-clicking attempt counters, marker files, cron-based pause trackers, query-counter snapshots. The patterns are powerful but heavy. Use them where the publisher needs to detect cheating or attribute progress to specific challenges. Avoid them where they will frustrate genuine learners who legitimately skip ahead or click through quickly.

Question 1

Is the track gated — certification, graded, used for hiring, or otherwise relied on by a downstream decision?

No — demo, workshop, self-paced tutorial, internal training
Recommendation
Skip engagement validation entirely.
No history forensics, no anti-rapid-click counters, no marker files. The cost of falsely flagging a fast learner outweighs the value of catching a careless one. Lean on the lesson and the granular check messaging — let the lab teach.
→ Cookbook Recipe 36: Granular check messaging
Yes — gated track
Continue to Question 2.
Question 2

What is the dominant failure mode you are guarding against?

Click-through cheating (learner clicks Check repeatedly without engaging)
Recommendation
Add an attempt counter and a shell-history check.
Increment a counter file on every check invocation; require N+ entries in the shell's history log (mongosh log, .bash_history, the equivalent for whichever shell the learner uses) before the check passes. Non-interactive bash disables history by default — turn it on explicitly in the check (set -o history; export HISTFILE=...; history -r). Pair with a layered hint so the learner who is genuinely stuck has a way forward.
→ Cookbook Recipe 51: Layered hint
Skip-ahead breakage (learner jumps a challenge that later checks depend on)
Recommendation
Make every solve idempotent and add defensive challenge-level setup.
A challenge-level setup-{hostname} that re-creates the prerequisite state if absent makes skipping_enabled safe. Solve scripts validate and re-create the entire prerequisite chain (server running → database exists → table exists → data present) before executing the current step. The lab survives any starting point.
→ Cookbook Recipe 32: Defensive challenge-level setup · Recipe 33: Idempotent solve script
Off-platform answer entry (learner types an answer that should be graded against live truth)
Recommendation
Use a file-based answer with live-truth comparison.
Provide a thin CLI on PATH (e.g., /usr/local/bin/submit) or a documented file path the learner writes to. The check script computes the expected value live from the data source and compares — never embed the expected answer in the check itself. Strip whitespace, normalize case, and emit a fail-message that tells the learner what was wrong without giving the answer away.
→ Cookbook Recipe 49: File-based quiz answers
TTL or pause abuse (learner pauses indefinitely or restarts to dodge time limits)
Recommendation
Add scored rubric plus pause tracker.
A scored rubric attaches an objective score to the run, so a learner who pauses and resumes still has to pass criteria. A cron-based pause tracker (where supported) emits telemetry while paused so excessive TTL use is observable. Reserve this combination for high-stakes certifications — the operational complexity is real.
→ Cookbook Recipe 37: Scored certification rubric

Bake into a published image, or build at setup time

Either you publish a custom image with the dependencies pre-installed, or you build the environment at setup time using heredocs and install commands. Both are legitimate. The trade-off shapes most architecture choices for a track. Custom image: fast learner experience, slow author iteration (Packer rebuild loop). Heredoc and install scripts: self-contained track, longer learner cold starts. Pick deliberately. This tree pairs with Tree 8 (project files) — they answer related but distinct questions.

Question 1

Roughly how long does the install plus dependency stack take from a stock cloud image?

Under ~30 seconds
Recommendation
Install at runtime — no custom image.
A 30-second runtime install is invisible against the VM boot itself. The author iteration loop (edit setup script → push → re-run) is much faster than any image rebuild cycle. Any package install you can express in 5 to 10 lines of apt-get plus a heredoc belongs here.
→ Cookbook Recipe 24: Heredoc-based project generation
30 seconds to 3 minutes
Continue to Question 2.
Over 3 minutes — heavy stack (a langchain pipeline, k3s plus a workshop-specific Kubernetes distro, an observability stack)
Recommendation
Bake into a custom image.
Once the install pushes past three minutes, the cold-start cost dominates the learner experience. Move the install into a Packer build, publish the image, reference it from config.yml. You absorb the rebuild cycle once per dependency change instead of on every play. For Kubernetes labs, a pre-baked k3s VM with a control-plane auto-join script gives you a sub-minute cluster on first challenge.
→ Cookbook Recipe 38: Pre-baked k3s VM with control-plane auto-join · Recipe 2: Single VM track (custom-image variant)
Question 2

How often does this dependency stack change during track iteration?

Frequently — daily or weekly while the track is in active iteration
Recommendation
Install at runtime, accept the boot cost.
A 1–3 minute setup penalty is cheaper than a Packer rebuild on every iteration. Pre-pull Docker images and warm Go module caches synchronously inside setup so the boot cost lands in one place and stays predictable. Move to a custom image only after the dependency surface stops moving.
→ Cookbook Recipe 20: Pre-pull Docker images in setup · Recipe 21: Bootstrap wait
Stable, or shipping the same scenario across many tracks
Recommendation
Bake into a custom image.
If many tracks share the same heavy stack, the rebuild loop happens once and pays back across every track that uses it. Apply the no-credentials-baked-in discipline rigorously — custom images get cached, copied, and shared, so anything baked in is effectively public. Hold credentials in the secrets block (Tree 12) and write them to disk only at setup time.
→ Cookbook Recipe 2: Single VM track (custom-image variant)

Picking the validation shape for a question or short-answer challenge

Three valid shapes for "did the learner get the right answer." The native quiz challenge type is easiest but only covers multiple-choice. The file-based pattern (learner echoes an answer to a known path) is a strict generalization — any short-answer question, custom validation logic, targeted fail-message feedback. A full programmatic check (Mocha/Chai for JS, pytest, plain bash) is for cases where the answer is the state of a system, not a string. Pick the simplest shape the lesson supports.

Question 1

What is the learner producing — a selection from options, a typed value, or a system state?

Selection from a fixed set of options
Continue to Question 2.
A short typed value (a name, a number, a token)
Recommendation
Use the file-based answer pattern.
Learner runs `echo "answer" > /tmp/answer` (or invokes a thin /usr/local/bin/submit wrapper that does the same). The check script reads, normalizes whitespace, normalizes case, and compares. This unlocks targeted fail-message feedback ("close — try the singular form"), custom comparison logic, and live-truth comparison where the expected value is computed at check time from the data source rather than embedded in the script.
→ Cookbook Recipe 49: File-based quiz answers
A system state (a row exists, a service is running, a file has the right contents)
Recommendation
Use a programmatic check.
Plain bash for trivial cases, the Python helper module for many parallel claims, or a real test framework (Mocha/Chai for JS courses, pytest for Python courses) when the assertions are layered enough that a structured runner pays for itself. Use granular check messaging so each assertion produces its own pass/fail line. Pair with the failure-message anatomy: hint, where, how-to-fix.
→ Cookbook Recipe 36: Granular check messaging · Recipe 26: Helper Python module for tests · Recipe 50: Failure-message anatomy
Question 2

Is plain multiple-choice with no per-option feedback enough, or does the lesson need targeted feedback per wrong answer?

Plain multiple-choice is enough
Recommendation
Use the native quiz challenge type.
Set type: quiz in frontmatter; provide answers (array) and solution (array of 0-based indices). Multi-select is supported by listing multiple correct indices. The body should read "Select all answers that apply" when more than one is correct. The native type renders the option list, handles state, and grades — no check script.
Need targeted feedback per wrong answer
Recommendation
Use the file-based answer pattern with a challenge-typed challenge.
Set type: challenge, list the options in the body, and have the learner write their pick to a file (or invoke a submit wrapper). The check script branches on the value: a wrong A gets one fail-message, a wrong C gets a different one. Native quiz cannot do this. The cost is one extra script; the gain is feedback that actually teaches.
→ Cookbook Recipe 49: File-based quiz answers

AssignmentLeft vs AssignmentRight, and how wide the assignment pane should be

The default_layout and default_layout_sidebar_size fields under lab_config decide where the assignment lives and how much screen real estate it claims. The defaults work for most tracks; the wrong settings are surprisingly costly when the lesson does not match. AssignmentRight (tool area is the visual focus) is used by roughly 80% of tracks and is the right starting point if you are unsure. The sidebar size is a percentage of screen width — values around 30 give wide tools and a narrow assignment, values around 70 give a wide assignment and a narrow tool area. A challenge can override either via challenge-level lab_config (or precise multi-pane custom_layout) when one challenge in the track is structurally different from the rest.

Question 1

Where does the learner spend most of their time — reading the assignment, or working in the tool area (terminal, code editor, browser)?

Reading — content-heavy, lots of explanation per step
Recommendation
AssignmentLeft, sidebar size around 45.
Setting default_layout: AssignmentLeft puts the assignment text on the left where reading flows naturally. A sidebar size in the 40 to 50 range gives the assignment enough width to render diagrams, tables, and longer explanations without forcing the learner to scroll. Suitable for SaaS-demo and reading-heavy training tracks where the tool area is supporting rather than primary.
Working — code-heavy, terminal-heavy, brief instructions
Continue to Question 2.
Mixed — varies by challenge
Recommendation
Default to AssignmentRight at sidebar size 33, override per challenge.
Track-level defaults set the baseline. Individual challenges that need a different shape can override with challenge-level lab_config — default_layout and default_layout_sidebar_size both accept overrides. For a finale that needs a precise multi-pane split (assignment + editor + terminal + browser visible at once), use custom_layout with a JSON-encoded layout tree.
Question 2

Is the primary tool a terminal or a wide visual surface (a code editor with side-by-side files, a virtual browser, a Kibana-style multi-pane UI)?

Terminal-primary
Recommendation
AssignmentRight, sidebar size 30.
A narrow assignment and a wide terminal is the standard code-heavy lab shape. Combine with a non-root user pattern and a clean prompt so the wide terminal looks like a real workstation. Loading messages should match the brand if the track is customer-facing — small touches read at the size of the actual visible terminal.
→ Cookbook Recipe 29: Non-root user pattern · Recipe 28: Profile.d plus .bashrc dual-write
Wide visual surface (UI-heavy, virtual browser, code editor with companion tabs)
Recommendation
AssignmentRight, sidebar size 33 — and consider a per-challenge custom_layout for the finale.
Sidebar 33 is the safe default. When a challenge benefits from showing the assignment, the editor, and the rendered output simultaneously, encode a precise layout tree under challenge-level lab_config.custom_layout. Use the reserved tab IDs "assignment" and "feedback" in the layout array — they refer to the platform's built-in panels and do not need to be declared in the frontmatter tabs list.
→ Cookbook Recipe 31: Companion code-tab pair