Skip to content

Quickstart

This quickstart uses one scenario: creating a container image that prints a required message. You will package that scenario either as a scored SkillCred item or as a guided lab.

What you will build

A sandbox around one container-image task:

Path Outcome
SkillCred Score whether a candidate can create linux-foundation-education/hello:1.0.
Lab Guide a learner through creating linux-foundation-education/hello:1.0.

The image must print:

hello tux

Before you begin, set up your Codespace.

Follow your content repository workflow

Step 1: Scaffold the environment

Run the initializer from the items/ directory:

@codespace-author ➜ .../quickstart (main)
cd items
sandbox init

When prompted: select Exam task → name it hello-image → accept all other defaults → Create → validate.

bash ×

Enter the item directory (named after what you entered above):

@codespace-author ➜ .../items (main)
cd hello-image

You now have a bootable sandbox with one Debian-based virtual machine. Drop into the VM to see what the candidate will see:

@codespace-author ➜ .../items/hello-image (main)
sandbox shell

bash ×

You can now exit the virtual machine by using Ctrl+D

You have successfully used sandbox init to scaffold out a sandbox environment, started the virtual machine, and stopped it.

You may want to take some time to review the file structure created in step 1.

Generated file structure
hello-image/
├── .gitignore
├── metadata.yaml
├── sandbox.yaml
├── task.en.md
└── host1/
    ├── assets/
    │   └── .gitkeep
    └── scripts/
        ├── build.sh
        ├── setup.sh
        ├── answer.sh
        └── score.sh

Checkpoint

You have a scaffolded SkillCred item in items/hello-image.

Step 2: Write the candidate task

task.en.md is what the candidate reads. The sandbox renders it as a MkDocs site in the instructions tool, visible by default in the left panel when the instructions tool is defined in sandbox.yaml.

You may preview the instructions as they will be rendered by the instructions tool in your development environment using the sandbox CLI. It's helpful to keep this running in a Codespace terminal while you author because the preview auto-updates.

@codespace-author ➜ .../items/hello-image (main)
sandbox instructions

Preview server started

The port may differ in your environment. Open the localhost URL shown in your terminal.

terminal output
ghcr.io/lf-certification/p3-sandbox-instructions-generator:latest
Building instructions...
Starting preview server at http://127.0.0.1:<port>

The preview tab shows the instructions exactly as the platform will render them to the end user of your environment.

Keep preview running

Leave sandbox instructions running while you edit. The preview reloads automatically when task.en.md changes.

Open task.en.md in your editor and save the contents to the following:

Copy into task.en.md

task.en.md
# Task

A starter `Dockerfile` is available at:

```text
/home/tux/container-image/Dockerfile
```

Modify it, then create a container image tagged:

```text
linux-foundation-education/hello:1.0
```

When the image is run, it must print exactly:

```text
hello tux
```

You may use `docker build`.

Checkpoint

The preview tab now shows the candidate-facing task.

You may now Ctrl+C in the terminal where sandbox instructions is running and close the preview tab.

Step 3: Implement the sandbox lifecycle scripts

Open sandbox.yaml and configure the tools. Each tool name becomes a tab the candidate sees. Then implement the lifecycle scripts in host1/scripts/.

Lifecycle map for this task
Script When it runs Purpose in this task
build.sh Once, when the VM image is built Install Docker, enable and start it with --now, and copy a starter asset into the candidate workspace.
setup.sh Every time a sandbox instance starts No-op; Docker enabled with --now in build.sh starts reliably with the VM.
answer.sh On demand during local testing Apply the reference solution by creating the Dockerfile and building the expected image.
score.sh On demand during grading Run independent checks for image existence and expected container output.

3.1 Build the VM image

First, add a starter Dockerfile asset that candidates can modify:

Copy into host1/assets/Dockerfile.starter

host1/assets/Dockerfile.starter
FROM docker.io/library/alpine:3.20

# TODO: make this image print the required message.
CMD ["echo", "replace me"]

Then update host1/scripts/build.sh to install Docker, enable it at boot, and copy the starter asset into the candidate workspace:

Copy into host1/scripts/build.sh

host1/scripts/build.sh
#!/usr/bin/env bashp

install::docker
systemctl enable --now docker

mkdir -p /home/tux/container-image
chown -R tux:tux /home/tux/container-image
file::copy assets/Dockerfile.starter /home/tux/container-image/Dockerfile
usermod -aG docker tux

3.2 Leave setup as a no-op

In host1/scripts/setup.sh, leave it as a no-op:

No wait needed for Docker

Docker was enabled with systemctl enable --now in build.sh, so it starts reliably with the VM. Only add waits in setup.sh when startup delay is significant enough to outlast SSH availability — Kubernetes cluster readiness is the canonical case.

Copy into host1/scripts/setup.sh

host1/scripts/setup.sh
#!/usr/bin/env bashp

3.3 Add the reference answer

In host1/scripts/answer.sh, write the reference solution:

Copy into host1/scripts/answer.sh

host1/scripts/answer.sh
#!/usr/bin/env bashp

mkdir -p /home/tux/container-image
chown -R tux:tux /home/tux/container-image

cat > /home/tux/container-image/Dockerfile <<'EOF'
FROM docker.io/library/alpine:3.20
CMD ["echo", "hello tux"]
EOF

chown tux:tux /home/tux/container-image/Dockerfile
sudo -iu tux docker build -t linux-foundation-education/hello:1.0 /home/tux/container-image

3.4 Score the candidate work

In host1/scripts/score.sh, check that the image exists and prints the expected message:

Copy into host1/scripts/score.sh

host1/scripts/score.sh
#!/usr/bin/env bashp

IMAGE_TAG="linux-foundation-education/hello:1.0"
EXPECTED_OUTPUT="hello tux"

imageExists() {
    sudo -iu tux docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1
}

imagePrintsExpectedMessage() {
    output="$(sudo -iu tux docker run --rm "${IMAGE_TAG}")"
    test "${output}" = "${EXPECTED_OUTPUT}"
}

scoring::check imageExists
scoring::check imagePrintsExpectedMessage
scoring::report

Checkpoint

The sandbox now has setup, reference answer, and scoring logic for the container image task.

Step 4: Add item metadata

Open metadata.yaml, change the generated settings.nickname, and add the required item fields:

Update metadata.yaml

metadata.yaml
---
settings:
  nickname: [petname]
competency: quickstart
difficulty: easy
title: "Build a greeting container image"
total_check_count: 2
revision: 1

Tip

nickname should be a unique item name, such as a generated pet name. competency must match an id in blueprint.yaml. total_check_count must match the number of checks in score.sh. See Blueprint reference for the full list of metadata constraints.

Checkpoint

The metadata now matches the two scoring::check calls in score.sh.

Step 5: Validate

Rebuild the VM image, then run the lifecycle test:

@codespace-author ➜ .../items/hello-image (main)
sandbox build && sandbox test

Why build first?

You changed build.sh after first starting the sandbox. sandbox test runs against the existing sandbox build; it does not rebuild a pre-existing VM image for you.

bash ×

sandbox test succeeds only when both scoring paths are valid:

  • No candidate work

    setup.sh -> score.sh

    Expected score: 0 of 2 checks pass

    Confirms the task does not award points before the candidate does any work.

  • Reference answer

    setup.sh -> answer.sh -> score.sh

    Expected score: 2 of 2 checks pass

    Confirms the reference answer satisfies every scoring check.

Successful test result

The final report includes checks_executed: 2, checks_passed: 2, and the two successful checks: imageExists and imagePrintsExpectedMessage.

Test successful!
Exact final host report

The CLI prints the JSON after host1 | on one line. It is formatted here for readability.

{
  "output_version": 1,
  "checks_executed": 2,
  "checks_passed": 2,
  "checks_successful": [
    "imageExists",
    "imagePrintsExpectedMessage"
  ],
  "checks_failed": []
}

Preview the candidate instructions:

@codespace-author ➜ .../items/hello-image (main)
sandbox instructions

Validation runs at commit time

A skills validate item git hook re-checks the items you touched on every git commit; you do not need to run validation manually.

You are done when

  • sandbox build && sandbox test ends with Test successful!.
  • The final score report has checks_executed: 2 and checks_passed: 2.
  • The instructions preview renders the candidate task correctly.

Step 1: Scaffold the lab environment

Run the lab initializer from the items/ directory:

@codespace-author ➜ .../quickstart (main)
cd items
sandbox init

When prompted: select Lab exercise → name it hello-image → accept all other defaults → Create → validate.

bash ×

Enter the lab directory:

@codespace-author ➜ .../items (main)
cd hello-image

Drop into the VM to see the learner environment:

@codespace-author ➜ .../items/hello-image (main)
sandbox shell

bash ×

You can now exit the virtual machine by using Ctrl+D

Generated file structure
hello-image/
├── .gitignore
├── instructions.md
├── sandbox.yaml
└── host1/
    ├── assets/
    │   └── .gitkeep
    └── scripts/
        ├── build.sh
        ├── setup.sh
        ├── answer.sh
        └── score.sh

Checkpoint

You have a scaffolded lab sandbox in items/hello-image.

Step 2: Register the lab

Describe the lab in metadata.yaml inside the lab directory:

Copy into items/hello-image/metadata.yaml

items/hello-image/metadata.yaml
---
competency: quickstart
difficulty: easy
title: "Create a greeting container image"
total_check_count: 0
revision: 1

Scoring comes later

total_check_count: 0 is allowed while the lab has no scoring checks yet. Once score.sh calls scoring::check, set it to the exact check count.

Then select the lab in manifest.yaml at the content repository root:

Copy into manifest.yaml

manifest.yaml
assemblies:
  primary:
    selected_items:
      - hello-image

Checkpoint

The lab content repository now selects items/hello-image.

Step 3: Implement the sandbox lifecycle scripts

Open sandbox.yaml and review the tools configuration. Each tool name becomes a tab the learner sees, then implement the lifecycle scripts in host1/scripts/.

Scoring is still useful in labs. It validates that the instructions being taught are achievable in the authored environment produced by the lifecycle scripts.

Lifecycle map for this lab
Script When it runs Purpose in this lab
build.sh Once, when the VM image is built Copy a starter asset into the learner workspace.
setup.sh Every time a sandbox instance starts No-op, no services to check for readiness in this lab.
answer.sh On demand during local testing Apply the reference solution by running the same Docker setup commands taught to the learner, then building the expected image.
score.sh On demand during validation Check whether the learner-visible instructions produce a usable Docker runtime, the expected image, and the expected output.

3.1 Prepare the learner workspace

First, add a starter Dockerfile asset that learners can modify:

Copy into host1/assets/Dockerfile.starter

host1/assets/Dockerfile.starter
FROM docker.io/library/alpine:3.20

# TODO: make this image print the required message.
CMD ["echo", "replace me"]

Then update host1/scripts/build.sh to copy the starter asset into the learner workspace. In this lab, Docker setup is part of what the learner practices, so do not bake it into the VM image.

Copy into host1/scripts/build.sh

host1/scripts/build.sh
#!/usr/bin/env bashp

mkdir -p /home/tux/container-image
chown -R tux:tux /home/tux/container-image
file::copy assets/Dockerfile.starter /home/tux/container-image/Dockerfile

3.2 Leave setup empty

In host1/scripts/setup.sh, keep the script as a no-op:

Use setup.sh only for runtime readiness

setup.sh runs every time a sandbox instance starts, after the VM boots from the image produced by build.sh. Put work here only when it depends on runtime state or verifies readiness. In this lab, Docker is intentionally not ready yet because installing it is part of the learner workflow.

Copy into host1/scripts/setup.sh

host1/scripts/setup.sh
#!/usr/bin/env bashp

3.3 Add the reference answer

In host1/scripts/answer.sh, automate the same outcome the lab teaches:

Copy into host1/scripts/answer.sh

host1/scripts/answer.sh
#!/usr/bin/env bashp

apt-get update
apt-get install -y docker.io
systemctl enable --now docker
usermod -aG docker tux
sudo -iu tux docker ps >/dev/null

mkdir -p /home/tux/container-image
chown -R tux:tux /home/tux/container-image

cat > /home/tux/container-image/Dockerfile <<'EOF'
FROM docker.io/library/alpine:3.20
CMD ["echo", "hello tux"]
EOF

chown tux:tux /home/tux/container-image/Dockerfile
sudo -iu tux docker build -t linux-foundation-education/hello:1.0 /home/tux/container-image

3.4 Validate the lab result

In host1/scripts/score.sh, check that Docker is usable, the image exists, and the image prints the expected message:

Copy into host1/scripts/score.sh

host1/scripts/score.sh
#!/usr/bin/env bashp

IMAGE_TAG="linux-foundation-education/hello:1.0"
EXPECTED_OUTPUT="hello tux"

dockerIsUsable() {
    sudo -iu tux docker ps >/dev/null
}

imageExists() {
    sudo -iu tux docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1
}

imagePrintsExpectedMessage() {
    output="$(sudo -iu tux docker run --rm "${IMAGE_TAG}")"
    test "${output}" = "${EXPECTED_OUTPUT}"
}

scoring::check dockerIsUsable
scoring::check imageExists
scoring::check imagePrintsExpectedMessage
scoring::report

Then set total_check_count in metadata.yaml to match the three scoring::check calls:

Update metadata.yaml

metadata.yaml
---
competency: quickstart
difficulty: easy
title: "Create a greeting container image"
total_check_count: 3
revision: 1

Checkpoint

The lab sandbox now has a prepared workspace, a reference answer, and validation logic for the guided Docker exercise.

Step 4: Write the lab instructions

instructions.md is what the learner reads. The sandbox renders it as a MkDocs site in the instructions tool, always visible in the left panel.

When you write labs for an existing catalog, the Lab instruction style reference describes the voice, heading shape, prompt titles, and copy-target conventions the catalog labs follow. The quickstart example below is a minimal starting point; production labs use more of those conventions.

Open instructions.md and write the guided steps:

Copy into instructions.md

instructions.md
# Lab

In this lab, you will install Docker, configure your user to run Docker commands, and create a container image that prints a greeting.

Install Docker and enable it:

```bash title="tux@host1:~$"
sudo apt-get update
sudo apt-get install -y docker.io
sudo systemctl enable --now docker
```

Add your user to the Docker group, then start a shell with the updated group membership:

```bash title="tux@host1:~$"
sudo usermod -aG docker tux
newgrp docker
```

Confirm Docker works without `sudo`:

```bash title="tux@host1:~$"
docker ps
```

A starter `Dockerfile` is available at:

```text
/home/tux/container-image/Dockerfile
```

Open the file and replace its contents with:

```dockerfile
FROM docker.io/library/alpine:3.20
CMD ["echo", "hello tux"]
```

Build the image with this tag:

```text
linux-foundation-education/hello:1.0
```

```bash title="tux@host1:~$"
cd /home/tux/container-image
docker build -t linux-foundation-education/hello:1.0 .
```

Run the image:

```bash title="tux@host1:~$"
docker run --rm linux-foundation-education/hello:1.0
```

!!! success "Expected Output"
    ```{.text .no-copy}
    hello tux
    ```

Preview it at any time:

@codespace-author ➜ .../items/hello-image (main)
sandbox instructions

Keep preview running

Leave sandbox instructions running while you edit. The preview reloads automatically when instructions.md changes.

Checkpoint

The learner instructions now walk through the shared container image scenario.

Step 5: Validate

Rebuild the VM image, then run the lifecycle test:

@codespace-author ➜ .../items/hello-image (main)
sandbox build && sandbox test

Why build first?

You changed build.sh after first starting the sandbox. sandbox test runs against the existing sandbox build; it does not rebuild a pre-existing VM image for you.

bash ×

sandbox test succeeds only when both validation paths are valid:

  • Before the lab steps

    setup.sh -> score.sh

    Expected score: 0 of 3 checks pass

    Confirms the checks do not pass before the learner follows the instructions.

  • Reference answer

    setup.sh -> answer.sh -> score.sh

    Expected score: 3 of 3 checks pass

    Confirms the authored environment can complete the same outcome taught in the lab.

Successful test result

The final report includes checks_executed: 3, checks_passed: 3, and the three successful checks: dockerIsUsable, imageExists, and imagePrintsExpectedMessage.

Test successful!
Exact final host report

The CLI prints the JSON after host1 | on one line. It is formatted here for readability.

{
  "output_version": 1,
  "checks_executed": 3,
  "checks_passed": 3,
  "checks_successful": [
    "dockerIsUsable",
    "imageExists",
    "imagePrintsExpectedMessage"
  ],
  "checks_failed": []
}

Validation runs at commit time

A skills validate . git hook re-checks the lab content repository on every git commit; you do not need to run validation manually.

You are done when

  • The instructions preview renders the guided lab correctly.
  • sandbox build && sandbox test ends with Test successful!.
  • The final score report has checks_executed: 3 and checks_passed: 3.

Key Resources

Configure virtual machines, tools, base images, resources, and multi-VM layouts.

Learn when build.sh, setup.sh, answer.sh, and score.sh run and how to write scoring checks.

Understand the sandbox model, configuration flow, and authoring building blocks.

Prepare your Codespace and local authoring tools before building content repositories.