Step 3 - Content & Tasks
With your infrastructure ready, it’s time to make your lab interactive. In this step, you’ll create engaging content that guides users through hands-on activities. You’ll write validation scripts that check their progress, provide helpful feedback when they get stuck, and celebrate their success when they complete tasks correctly.
Create Your First Interactive Task
Section titled “Create Your First Interactive Task”Tasks make your lab interactive. They define specific activities that users must complete, and they are automatically checked to verify their success.
Add this task definition to tasks.hcl:
resource "task" "edit_homepage" { description = "Customize the nginx homepage with your own message"
config { target = resource.container.webserver }
condition "file_modified" { description = "Customize the nginx homepage with your own message"
check { script = "scripts/check_homepage.sh" failure_message = "Please edit /usr/share/nginx/html/index.html with your custom message" } }}Three blocks do the work here:
configdecides where the task’s scripts run.target = resource.container.webserverruns them inside your web server container, so they see the same files and environment as the user.conditionis one part of the task that has to pass. Its label,"file_modified", is an id you pick yourself, not a built-in check type. A task can hold several conditions, which lets you give users feedback per step. The condition’sdescriptionis what they read on the task.checkis the script that decides whether the condition passed. It runs whenever the task is validated. Exit 0 means passed; any other exit code means not yet, and the user is shown thefailure_message.
Build the Validation Script
Section titled “Build the Validation Script”Now you’ll create the actual script that checks whether users completed the task correctly. This script uses simple bash commands to look for specific content in the nginx homepage file. If it finds the expected text, the task passes; if not, it fails with a helpful message.
Remember, this script runs inside the container, so it can directly check files that users modified.
Create the validation script directory and file:
mkdir -p scriptsIn VS Code, create a new file scripts/check_homepage.sh:
#!/bin/bash
EXPECTED_MESSAGE="Hello from my web server!"NGINX_HTML="/usr/share/nginx/html/index.html"
if grep -q "$EXPECTED_MESSAGE" "$NGINX_HTML"; then exit 0else exit 1fiMake the script executable:
chmod +x scripts/check_homepage.shCreate User Instructions
Section titled “Create User Instructions”Now you need to create the actual content that users will see and follow. This includes step-by-step instructions that guide them through customizing the nginx homepage.
Create the instruction content directory:
mkdir -p instructionsIn VS Code, create a new file instructions/customize_homepage.md with this content:
# Customize Your Web Server
Web servers like nginx serve static files to visitors who access your website. When someone types your website's URL into their browser, nginx looks for an `index.html` file in its web directory and sends that content back to display in the browser.
## Understanding Web Server File Structure
Nginx stores its web files in `/usr/share/nginx/html/` by default. The main file, `index.html`, contains the HTML code that creates your homepage. Any changes you make to this file will immediately be visible to visitors since nginx serves static files directly without requiring a restart.
## Working with HTML Files
HTML (HyperText Markup Language) uses tags to structure content. You can add text, headings, paragraphs, and other elements by using HTML tags like:
- `<h1>` for main headings- `<p>` for paragraphs- `<strong>` for bold text
## Editing Files on a Server
Minimal container images like nginx ship without a text editor, so you change files with shell commands instead. In the Terminal tab, replace the homepage with your own message:
```bashecho '<h1>Hello from my web server!</h1>' > /usr/share/nginx/html/index.html```
Verify your change with `cat`:
```bashcat /usr/share/nginx/html/index.html```
To see your changes, refresh your browser or the Service tab.
<instruqt-task id="edit_homepage"></instruqt-task>
## Troubleshooting
**Can't find the file?**
- Make sure you're in the right directory: `/usr/share/nginx/html`- List files with: `ls -la`
**Changes not showing?**
- Force refresh the Service tab (Ctrl+F5 or Cmd+Shift+R)- Wait a few seconds and refresh again
## Finish the Lab
<instruqt-completion text="You customized a live web server and validated your change with an automated check."></instruqt-completion>Two embedded components make this page interactive:
<instruqt-task>shows the task you defined earlier inside the instructions.<instruqt-completion>adds the Completion panel. Labs have no built-in finish button, so without this panel users can’t end the session. The panel shows the user’s progress. When every activity is done, it shows a Finish & Exit button that stops the lab and its sandbox. See the Completion component docs for its options.
Create the Layout Configuration
Section titled “Create the Layout Configuration”Layouts define how the lab’s user interface is organized on screen. Multi-column layouts work well for instructional labs - users can read instructions alongside practice areas and see results in real-time. This keeps everything visible at once, reducing the need to switch between tabs.
The layout connects to your infrastructure by referencing the terminal and service resources you created earlier.
Define the user interface with layouts.hcl:
resource "layout" "two_column" { column { width = "50"
instructions {} }
column { width = "50"
tab "terminal" { title = "Terminal" target = resource.terminal.shell }
tab "service" { title = "Service" target = resource.service.webserver } }}This layout creates two equal-width columns (50% each):
- Left column: Shows the instruction content you’ll create next, using the
instructions {}block - Right column: Contains two tabs that users can switch between:
- Terminal tab for running commands (references your shell resource)
- Service tab for viewing the web server (references your webserver service)
Each tab block has a label, such as "terminal", that must be unique within the layout. Error messages call this label the tab’s slug.
The target attribute points the tab at a resource you defined earlier, such as resource.terminal.shell. Tab targets take the resource reference directly. Add .meta.id only where a field asks for an ID, such as network { id = resource.network.main.meta.id } in your infrastructure file.
Create Page Structure
Section titled “Create Page Structure”Pages are the building blocks of your lab’s content. Each page is a step in your lab where users read instructions and complete activities. This page definition ties together the markdown content and the interactive task.
Create a pages.hcl file to organize your content:
resource "page" "customize_homepage" { title = "Customize Your Web Server" file = "instructions/customize_homepage.md"
activities = { edit_homepage = resource.task.edit_homepage }}This page configuration:
- Points to your markdown file with the educational content using the
filefield - Connects to your task through the activities map
The activities map is crucial - it tells Instruqt which tasks are available on this page. When users see the <instruqt-task id="edit_homepage"> component in your markdown, Instruqt knows to connect it to the actual task definition. The key (edit_homepage) must match the ID used in the markdown file.
Update Your Lab Configuration
Section titled “Update Your Lab Configuration”The skeleton template created a basic main.hcl file, but it doesn’t know about all the new resources you’ve created. You need to update it to reference your custom layout and page so users can access your content.
Update the skeleton’s main.hcl to reference your new content:
resource "lab" "main" { title = "My Web Server Lab" description = "Learn web server basics by customizing an nginx homepage" layout = resource.layout.two_column
settings { timelimit { duration = "1h" } }
content { chapter "getting_started" { title = "Getting Started with Web Servers"
page "customize_homepage" { reference = resource.page.customize_homepage } } }}This updated configuration:
- Updates the layout reference to use your custom two-column layout (
resource.layout.two_column) - Sets a time limit -
timelimit.durationcaps how long a lab session can run (here one hour); the lab won’t run without it. See the timelimit settings in the lab reference for the related options, like extensions and showing a countdown timer. - Adds a content block with the required chapter/page hierarchy
- Creates a chapter called “Getting Started with Web Servers”
- References your page (
customize_homepage) within the chapter
Now that you’ve connected all the components, let’s visualize how everything works together:
%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#1f2440', 'primaryTextColor':'#e2e8f0', 'primaryBorderColor':'#2a2f45', 'lineColor':'#ffffff', 'secondaryColor':'#141829', 'tertiaryColor':'#181c32', 'background':'#181c32', 'mainBkg':'#181c32', 'secondBkg':'#181c32', 'tertiaryTextColor':'#e2e8f0', 'edgeLabelBackground':'#181c32', 'clusterBkg':'#181c32'}}}%%
graph TD
A["Task 'edit_homepage'
Validates homepage
changes"] --> B["Page 'customize_homepage'
Educational content
+ task reference"]
B --> C["Layout 'two_column'
Instructions and
Terminal + Service"]
B --> D["Lab 'main'
Chapter structure
+ metadata"]
E["Instructions
'customize_homepage.md'"] --> B
F["Validation Script
'check_homepage.sh'"] --> A
style A fill:#667eea,stroke:#2a2f45,color:#ffffff
style B fill:#667eea,stroke:#2a2f45,color:#ffffff
style C fill:#667eea,stroke:#2a2f45,color:#ffffff
style D fill:#667eea,stroke:#2a2f45,color:#ffffff
style E fill:#4facfe,stroke:#2a2f45,color:#ffffff
style F fill:#4facfe,stroke:#2a2f45,color:#ffffff
This shows how your content and task components connect: the task validates user actions, the page combines content with the task, and everything is organized through the lab’s chapter structure.
Validate Your Complete Lab
Section titled “Validate Your Complete Lab”Now that you’ve created all the components and connected them together, it’s important to validate that your complete lab configuration is correct. This checks for syntax errors, missing references, and ensures all your resource connections are properly defined.
Validate that all components work together:
instruqt lab validateSave Your Complete Lab
Section titled “Save Your Complete Lab”Your lab is now fully functional with interactive tasks and validation. Save this milestone to Git so you have a complete working version to build upon or return to if needed.
Stage all your new lab files:
git add .Create a commit documenting your complete interactive lab:
git commit -m "Add interactive homepage customization activity"[main def9012] Add interactive homepage customization activity6 files changed, 120 insertions(+), 7 deletions(-)create mode 100644 instructions/customize_homepage.mdcreate mode 100644 pages.hclcreate mode 100755 scripts/check_homepage.shcreate mode 100644 tasks.hcl
Push your complete lab to GitHub:
git pushWhat You’ve Built
Section titled “What You’ve Built”You’ve created a complete, interactive Instruqt lab with all the essential components:
- Interactive task that validates user progress automatically using custom validation scripts
- Educational content with step-by-step instructions that teach web server concepts
- Complete lab structure connecting tasks, pages, layouts, and infrastructure through proper resource chaining
- Professional organization with clear separation between configuration files, content files, and validation logic
Your lab now provides a complete learning experience where users can read instructions, complete hands-on activities, and receive immediate feedback on their progress. This represents a fully functional Instruqt lab ready for users.
Final Code
Section titled “Final Code”Here’s all the code from this step in one place for easy reference:
resource "task" "edit_homepage" { description = "Customize the nginx homepage with your own message"
config { target = resource.container.webserver }
condition "file_modified" { description = "Customize the nginx homepage with your own message"
check { script = "scripts/check_homepage.sh" failure_message = "Please edit /usr/share/nginx/html/index.html with your custom message" } }}#!/bin/bash
EXPECTED_MESSAGE="Hello from my web server!"7 collapsed lines
NGINX_HTML="/usr/share/nginx/html/index.html"
if grep -q "$EXPECTED_MESSAGE" "$NGINX_HTML"; then exit 0else exit 1fi# Customize Your Web Server
Web servers like nginx serve static files to visitors who access your website. When someone types your website's URL into their browser, nginx looks for an `index.html` file in its web directory and sends that content back to display in the browser.42 collapsed lines
## Understanding Web Server File Structure
Nginx stores its web files in `/usr/share/nginx/html/` by default. The main file, `index.html`, contains the HTML code that creates your homepage. Any changes you make to this file will immediately be visible to visitors since nginx serves static files directly without requiring a restart.
## Working with HTML Files
HTML (HyperText Markup Language) uses tags to structure content. You can add text, headings, paragraphs, and other elements by using HTML tags like:
- `<h1>` for main headings- `<p>` for paragraphs- `<strong>` for bold text
## Editing Files on a Server
Minimal container images like nginx ship without a text editor, so you change files with shell commands instead. In the Terminal tab, replace the homepage with your own message:
```bashecho '<h1>Hello from my web server!</h1>' > /usr/share/nginx/html/index.html```
Verify your change with `cat`:
```bashcat /usr/share/nginx/html/index.html```
To see your changes, refresh your browser or the Service tab.
<instruqt-task id="edit_homepage"></instruqt-task>
## Troubleshooting
**Can't find the file?**
- Make sure you're in the right directory: `/usr/share/nginx/html`- List files with: `ls -la`
**Changes not showing?**
- Force refresh the Service tab (Ctrl+F5 or Cmd+Shift+R)- Wait a few seconds and refresh again
## Finish the Lab
<instruqt-completion text="You customized a live web server and validated your change with an automated check."></instruqt-completion>resource "layout" "two_column" { column { width = "50"
instructions {} }
column { width = "50"
tab "terminal" { title = "Terminal" target = resource.terminal.shell }
tab "service" { title = "Service" target = resource.service.webserver } }}resource "page" "customize_homepage" { title = "Customize Your Web Server" file = "instructions/customize_homepage.md"
activities = { edit_homepage = resource.task.edit_homepage }}resource "lab" "main" { title = "My Web Server Lab" description = "Learn web server basics by customizing an nginx homepage" layout = resource.layout.two_column
settings { timelimit { duration = "1h" } }
content { chapter "getting_started" { title = "Getting Started with Web Servers"
page "customize_homepage" { reference = resource.page.customize_homepage } } }}What’s Next
Section titled “What’s Next”In the final step, you’ll test your complete lab experience and learn how to deploy it to the Instruqt platform for users to access.
