Secure a Local Kubernetes AI Lab
Build a local kind lab with a deterministic Kubernetes exposure check.
Introduction
30 Second Summary
A local AI model feels private because it runs on your laptop. One exposure mistake can quietly place it within reach of strangers.
In this project, you will build a reproducible local Kubernetes lab that runs Ollama behind loopback access. You will use a deterministic Go check to test its exposure before publishing the evidence in a public open-source repository.
What You'll Build
One Go command brings your local AI cluster back at 127.0.0.1:21434 under a PASS result that proves its normal Ollama Service stays closed to strangers.
By the end of this project, you'll have:
- A one-command rebuild that returns the laptop-ai-lab cluster to a ready state at 127.0.0.1:21434.
- Measured model evidence from ten scored questions with recorded tokens per second for qwen3:1.7b.
- A deterministic exposure gate whose result changes from PASS to OPEN for each planted unsafe state.
- Secret Mission: A completed public contribution trail with a defended rule, one merged pull request, release v0.1.0, a recorded upstream link, an AI-assistance disclosure, and a stopped local cluster.
Are there any prerequisites?
This timed lab assumes Go, kind, kubectl, Git, GitHub CLI, Ollama, and either Docker or Podman are already installed.
It also assumes qwen3:1.7b plus both day-zero images are ready because the guide stops at the first failed prerequisite.
Before We Start
Before any setup begins, this checkpoint anchors why your laptop-ai-lab kind cluster keeps Ollama available only through the loopback address 127.0.0.1:21434. The decision boundary stays clear: qwen3:1.7b supplies measured answers while the learner-authored Go functions checkExposure and runCheck, backed by Go tests, independently decide PASS or OPEN for Kubernetes Service exposure.
Verify Prerequisites and Start the Lab
The sixty-minute lab depends on a prepared local Kubernetes toolchain. A stale cluster or project folder would make the later rebuild measurement unreliable.
You will verify every prerequisite without installing software or downloading large assets. You will then create a clean repository before starting the safe lab.
In this step, get ready to:
- Record your operating system plus your shell.
- Verify the prepared tools plus local assets.
- Start the safe lab at 127.0.0.1:21434.
Verify the prepared toolchain
This lab uses Go for orchestration. Docker or Podman supplies the container engine beneath kind.
Every heavy image must already exist locally. The checks stop at the first missing prerequisite so repairs stay outside the timed project.
- Press Cmd+Space on macOS or the Windows key on Windows to open system search. Use your application launcher on Linux.
- Type the name of your installed editor. Press Enter to start Cursor or Visual Studio Code.
- Select Open Folder from the editor start screen.
- Choose your home folder.
- Open the Terminal menu in the editor.
- Select New Terminal.
The integrated terminal keeps every setup command inside your editor. Its profile label identifies whether you are using PowerShell, bash, or zsh.
- Record the operating system running on your computer.
- Record the shell named by the terminal profile.
- Run each prerequisite check below one line at a time. Stop after the first failed line:
go version
kind version
kubectl version --client
git --version
gh --version
gh auth status
ollama --version
ollama ls
What do these checks prove?
- The version lines prove that Go, kind, kubectl, Git, GitHub CLI, and Ollama are available in this terminal.
- The authentication check confirms that GitHub CLI has an active account for later repository work.
- The final line reads the local Ollama model store without downloading a model.
Each tool should print its installed version. The Ollama model list should contain qwen3:1.7b.
Your container engine must also respond successfully. Its local image list must contain the kind node image plus the Ollama container image named by the supplied starter bundle.
Docker
- Verify the Docker engine plus its local images by running each line:
docker version
docker image ls
What does Docker report?
The first line reports the Docker client plus server components. The second line reads the local image store without pulling anything.
You should see both client and server version sections. You should also find both day-zero image references from the supplied starter bundle.
Docker engine unavailable?
Confirm that the prepared Docker engine is running. Stop this timed project if either required image is absent.
Share the first failing line when you ask for help. Help me diagnose my Docker prerequisite check.
Podman
- Verify the Podman engine plus its local images by running each line:
podman version
podman images
What does Podman report?
The first line reports the prepared Podman version. The second line reads the local image store without pulling anything.
You should see Podman version details. You should also find both day-zero image references from the supplied starter bundle.
Podman engine unavailable?
Confirm that the prepared Podman engine is available. Stop this timed project if either required image is absent.
Share the first failing line when you ask for help. Help me diagnose my Podman prerequisite check.
Choose the outcome that matches your complete prerequisite check.
✔️ Every prerequisite passed
Your local toolchain is ready. Keep the printed versions visible while you record the environment below.
ⓧ A prerequisite failed
Stop at the first failing line. Fix only the named prerequisite outside this timed project.
- Read the first error line.
- Repair the named prerequisite outside this project.
- Run the same failed check again.
- Continue after the check reports its expected version or local asset.
Need help with the failed check?
Include your operating system plus shell when you ask for help. Never paste a GitHub authentication token.
Share the first error line here. Help me identify the failed prerequisite.
Create the clean repository
A clean baseline makes the later rebuild timing meaningful. You need to remove any earlier cluster before creating the single project folder.
- List the clusters managed by kind by running:
kind get clusters
What does this command check?
kind lists the clusters managed through the current container engine. Any entry named laptop-ai-lab belongs to an earlier run.
Removing an old cluster can feel risky. This command targets only the local cluster named laptop-ai-lab.
- Remove the earlier cluster by running this idempotent command:
kind delete cluster --name laptop-ai-lab
Why run the delete command every time?
kind treats deletion of a nonexistent cluster as a successful cleanup. This gives every learner the same empty starting state.
- Confirm the named cluster is absent by running the list command again:
kind get clusters
What should the list show?
The output should contain no entry named laptop-ai-lab. This proves that the previous local cluster cannot affect the first build.
Deleting a stale folder is also final. Preserve anything you need before removing an earlier ~/laptop-ai-lab folder.
- Check your home folder in the editor's file sidebar for an earlier laptop-ai-lab folder.
- Remove the earlier folder with the command for your shell if it exists.
macOS
- Delete the stale folder from zsh or bash by running:
rm -rf ~/laptop-ai-lab
What does this remove?
The shell removes the named folder plus every item inside it. The command completes quietly when the folder is already absent.
Windows
- Delete the stale folder from PowerShell by running:
Remove-Item -Path ~/laptop-ai-lab -Recurse -Force
What does this remove?
PowerShell removes the named folder plus every item inside it. Skip this command when the folder is already absent.
Linux
- Delete the stale folder from bash or zsh by running:
rm -rf ~/laptop-ai-lab
What does this remove?
The shell removes the named folder plus every item inside it. The command completes quietly when the folder is already absent.
The old state is gone. You can now create the single home-folder repository used by every later command.
- Create ~/laptop-ai-lab with the command for your shell.
macOS
- Create the folder from zsh or bash by running these commands:
mkdir -p ~/laptop-ai-lab
cd ~/laptop-ai-lab
What do these commands do?
The shell creates the project folder inside your home folder. It then makes that folder the active terminal location.
Windows
- Create the folder from PowerShell by running these commands:
New-Item -Path ~/laptop-ai-lab -ItemType Directory
cd ~/laptop-ai-lab
What do these commands do?
PowerShell creates the project folder inside your home folder. It then makes that folder the active terminal location.
Linux
- Create the folder from bash or zsh by running these commands:
mkdir -p ~/laptop-ai-lab
cd ~/laptop-ai-lab
What do these commands do?
The shell creates the project folder inside your home folder. It then makes that folder the active terminal location.
- Initialize the local repository by running:
git init
What does Git create?
Git creates the repository metadata inside ~/laptop-ai-lab. The project is ready to track its supplied files.
- Switch the editor to the new ~/laptop-ai-lab folder with its folder-opening control.
- Copy only the supplied starter files into ~/laptop-ai-lab without renaming anything.
- Resolve the supplied Go module from the built-in terminal by running:
go mod tidy
What does module tidy do?
Go adds missing module requirements. It removes unused requirements plus updates go.sum when required.
That is the clean repository ready. The command should return to the terminal prompt without reporting a missing dependency.
Start the safe lab
The supplied up command creates the cluster plus its safe Ollama path. This first run saves the already-pulled image once before starting the local resources.
- Open the Terminal menu in the editor.
- Select New Terminal to create a second terminal.
- Move the second terminal into the project folder by running:
cd ~/laptop-ai-lab
Why use a second terminal?
The first terminal stays available for later commands. The second terminal remains attached to the process that keeps the loopback path running.
Expect the first start to take a few minutes while the prepared image is saved into the lab workflow. Leave this terminal open after the cluster becomes ready.
Before you run it, which cluster details do you expect the command to report?
- Start the prepared lab by running:
go run . up
What does the up command establish?
- The Go program creates the local kind cluster named laptop-ai-lab.
- The cluster uses the context kind-laptop-ai-lab plus the namespace lab.
- The Ollama Deployment runs behind a ClusterIP Service on 11434/TCP.
- The host reaches that Service through the loopback address 127.0.0.1:21434.
You should see the cluster laptop-ai-lab, context kind-laptop-ai-lab, namespace lab, plus the ollama Deployment and Service in the output.
You should also see the image-saving line once. The safe Service should report ClusterIP with 11434/TCP while the host path reports 127.0.0.1:21434.
- Leave the second terminal running to preserve the loopback path.
Lab did not become ready?
Read the first error line. Check the prerequisite or supplied file named by that line before running the same command again.
Do not start a large image pull during this timed project. Help me diagnose the first up error.
Your clean lab is running through a loopback-only path. Next, you will place the design plus threat model ahead of the public implementation.
Publish the Design and Contribution Plan
Your safe local cluster is running through its loopback-only path. That gives you a working foundation for the public contributor lab.
Public implementation needs an explicit threat model before code changes begin. In this step, you will publish the design through GitHub with a contribution plan that reviewers can audit.
In this step, get ready to:
- Commit the five design documents before any source code.
- Add the open-source kit with pull-request testing.
- Publish the repository with a public Project and issue.
Commit the design before the code
A Git commit creates a reviewable snapshot of selected files. Your first snapshot contains only the design documents so the implementation can be judged against a plan.
- Switch back to the editor containing your ~/laptop-ai-lab repository.
- Populate the docs/ folder by copying each supplied design document into its matching path.
Your file tree now shows docs/build-spec.md, docs/decisions.md, docs/topology.md, docs/implementation-plan.md, and docs/test-plan.md.
- Confirm that docs/decisions.md records that the model runs inside the cluster.
- Confirm that docs/decisions.md states whether LoadBalancer Services count as open.
- Confirm that docs/decisions.md states whether .spec.externalIPs count as open.
Why record these decisions first?
The model-placement decision defines where inference runs. The Service decisions define which declarative states make the deterministic check return OPEN.
Reviewers can now compare the later implementation with a rule that already exists on paper.
- Create the design-only first commit by running these commands in the terminal from earlier:
git add docs/build-spec.md docs/decisions.md docs/topology.md docs/implementation-plan.md docs/test-plan.md
git commit -m "Add the design docs"
What do these commands do?
- The first command stages only the five named documents.
- The second command records the exact commit message Add the design docs.
- The supplied starter source remains outside this first commit.
Before you inspect the commit, do you expect to see any source files in its file list?
- Inspect the subject and file list of the first commit by running:
git log -1 --name-only --format=%s
What does this check show?
The command prints the latest commit subject. It also prints the paths stored in that commit.
You should see Add the design docs followed by exactly the five paths inside docs/.
That first commit now gives reviewers a design baseline before implementation begins.
Seeing extra files in the first commit?
Extra paths mean another file was staged before the commit. Preserve the required first-commit boundary by correcting the commit before continuing.
Need help correcting the file list? Help me fix my first Git commit so it contains only the five design documents
Add the open-source contribution kit
A public repository needs rules for licensing and responsible reporting. The supplied contribution guide also uses the Developer Certificate of Origin to attach a public sign-off to contributed commits.
- Copy the canonical Apache License 2.0 text from the Apache Software Foundation license page.
- Save the copied license text as LICENSE inside ~/laptop-ai-lab.
Your file tree now shows LICENSE beside the supplied starter files.
- Populate the remaining kit by copying the supplied CONTRIBUTING, SECURITY.md, CNCF Code of Conduct, and .github files without renaming them.
The repository now has a license and contribution rules. It also has a pull-request CI/CD workflow.
- Confirm that CONTRIBUTING requires contributors to use git commit -s for DCO sign-off.
- Compare .github/workflows/test.yml with this supplied workflow reference:
name: test
on:
pull_request:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
with:
go-version-file: go.mod
cache: false
- run: go test
What does this workflow do?
- The pull_request trigger runs the workflow for pull-request activity.
- The permissions block limits repository contents to read access.
- Both actions use full commit SHAs for stable workflow dependencies.
- The final step runs go test as the decision gate.
- Save .github/workflows/test.yml.
- Confirm that the first line reads name: test.
- Confirm that the final workflow step reads - run: go test.
Does the workflow differ from the reference?
Check the indentation beneath steps and with. YAML uses indentation to define structure.
Need help comparing the workflow? Help me find the mismatch in my pull-request test workflow
✔️ Awesome, I've got everything!
Great. Your pull-request workflow is saved with the exact action pins and test command.
ⓧ I'd like to double check the full code
name: test
on:
pull_request:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
with:
go-version-file: go.mod
cache: false
- run: go test
What should match?
Compare every line with your saved file. The action SHAs and indentation must match exactly.
- Commit the open-source kit separately with DCO sign-off by running these commands:
git add LICENSE CONTRIBUTING SECURITY.md CODE_OF_CONDUCT.md .github
git commit -s -m "Add the contribution kit"
What does the sign-off add?
The staging command selects the supplied contribution files. The -s flag adds a public Signed-off-by trailer to the commit.
Before you inspect this commit, do you expect the DCO trailer to appear in its message?
- Inspect the contribution-kit commit by running:
git log -1
What should the commit contain?
The log shows the commit subject and its message body. The final message line should start with Signed-off-by:.
You should see Add the contribution kit with your DCO sign-off below it.
Missing the sign-off trailer?
Confirm that the commit command included the -s flag. Git also needs your configured name and email to create the trailer.
Need help restoring the sign-off? Help me correct my latest Git commit so it includes a DCO sign-off
Publish the repository and contributor plan
The design and contribution rules are ready for public review. You will now publish them through a repository with security reporting and a public planning trail.
- Prepare to choose the current local repository if GitHub CLI asks what you want to publish.
- Create the public laptop-ai-lab repository by running:
gh repo create laptop-ai-lab --public
What does this command create?
GitHub CLI creates a repository named laptop-ai-lab with public visibility. Follow any prompt to publish the current local repository and push its commits.
- Confirm the repository visibility by running:
gh repo view --json visibility -q .visibility
What does this check read?
The command requests the repository's visibility field. The query prints only that field's value.
You should see PUBLIC in the terminal.
Repository creation failed?
Confirm that GitHub CLI is still authenticated. Also confirm that your account does not already contain a repository named laptop-ai-lab.
Need help publishing the current repository? Help me troubleshoot gh repo create for my existing laptop-ai-lab Git repository
- Open the repository URL printed by GitHub CLI in your browser.
- Select Settings in the repository navigation.
- Select Security and quality in the settings navigation.
- Open Advanced Security.
- Enable Private vulnerability reporting.
The repository now gives security researchers a private route for reporting vulnerabilities.
Refreshing authentication expands the GitHub CLI token's access to Projects. This action does not reveal your credentials in the terminal output.
- Add the required Project scope by running:
gh auth refresh -s project
Why add this scope?
GitHub CLI requires the project token scope to create Projects or add issues to them. Complete the authentication confirmation if GitHub requests it.
- Create the contributor Project by running:
gh project create --owner "@me" --title "laptop-ai-lab"
What does this command create?
The command creates a Project owned by your authenticated account. Its title matches the repository name.
The Project number can be easy to miss. It appears in the URL printed after creation.
- Record the Project number here: your GitHub Project number.
- Change the Project visibility to public by running:
gh project edit [[PROJECT_NUMBER="your GitHub Project number"]] --owner "@me" --visibility PUBLIC
What changes here?
The command updates the Project identified by your recorded number. Its visibility becomes PUBLIC.
- Create issue #1 on the public Project by running:
gh issue create --title "Implement the deterministic exposure check" --body "Implement checkExposure and runCheck from docs/build-spec.md." --project "laptop-ai-lab"
What does the issue establish?
The issue defines the implementation work against docs/build-spec.md. The fresh repository assigns it number #1 and adds it to the Project.
- Create the implementation branch by running:
git switch -c lab-check
What does this branch separate?
The command creates lab-check from the current commit. Your upcoming implementation work stays separate from the published baseline.
Before the final check, do you expect the oldest public commit to contain code or only the five design documents?
- Return to the public laptop-ai-lab repository in your browser.
- Open the repository's commit history.
- Open the oldest commit to inspect its subject and file list.
- Open the public Project to confirm that issue #1 appears on it.
The oldest commit shows Add the design docs with only the five supplied files inside docs/. The public Project shows issue #1 for the deterministic exposure check.
Your public contribution now begins with an auditable design and a test-backed plan. Next, you will measure the local model before exposing the missing deterministic check.
Measure the Baseline and Expose the Gap
Your public design is in place. Your safe Kubernetes lab is still reachable only through loopback.
Now you will measure qwen3:1.7b on the supplied ten-question set. The run gives you a concrete score.
Those measurements cannot decide whether the Kubernetes Service is safe. The supplied Go tests must enforce that decision through a deterministic policy check.
In this step, get ready to:
- Measure the model's ten-question score.
- Record the model's printed generation rate.
- Run the supplied tests to expose the missing decision gate.
Measure the model baseline
The evaluation asks the local Ollama model all ten supplied questions. Its summary separates answer quality from generation speed.
- Switch back to the second terminal from earlier.
- Confirm the terminal prompt shows ~/laptop-ai-lab.
- Start the ten-question model evaluation by running this command:
go run . ask
What Do These Measurements Show?
- The score reports how many of the ten supplied questions the model answered correctly.
- Tokens per second reports the generation rate printed by the evaluation.
- Both values describe model performance.
- Neither value controls the PASS or OPEN security verdict.
When the run finishes, you will see a ten-question score out of 10. You will also see tokens per second.
- Record the printed score with the Measured label here: your score out of 10.
- Record the printed generation rate with the Measured label here: your tokens per second.
Model Evaluation Did Not Finish?
- Return to the terminal that keeps the loopback path at 127.0.0.1:21434 alive.
- Confirm its process is still running.
- Confirm the evaluation terminal is still inside ~/laptop-ai-lab.
Still stuck? Help me diagnose why the local model evaluation did not finish.
Expose the missing decision gate
Model measurements describe answer quality and speed. A security gate needs a deterministic result from the Kubernetes Service configuration.
Before you run the tests, do you think the project can produce a deterministic security verdict yet?
- Run the supplied Go tests by running this command:
go test
This Failure Is the Designed Gap
You will see go test report FAIL. The first error identifies an absent checkExposure or runCheck implementation in check.go.
This failure is intentional. The supplied test defines a security decision that the project cannot enforce yet.
- Read the first error line in the terminal.
- Preserve the supplied test exactly as it is.
- Select docs/build-spec.md from the editor's Explorer sidebar.
- Read the full build specification.
- Select the supplied Go example referenced by docs/build-spec.md.
- Read how the example separates measured output from a deterministic decision.
- Explain aloud the boundary between the qwen3:1.7b measurements and the Go PASS or OPEN decision.
Why the Model Does Not Decide
The model supplies measured answers. Its score shows performance on the ten questions.
The Go check examines the Service rule. Its tests make PASS or OPEN independent of model confidence.
- Return to the terminal output from go test.
- Confirm the first error identifies the absent checkExposure or runCheck implementation in check.go.
You have captured the model baseline. The intended test failure now pinpoints the deterministic rule you will implement next.
Implement the Check and Rebuild
Your model measurements are recorded. The deliberate test failure has exposed the missing security gate.
You will implement the learner-authored Go check from the existing specification. The supplied tests become the decision gate for the safe Kubernetes Service.
You will also rebuild the cluster from a clean stop. The measured rebuild proves that the saved image can be reused.
In this step, get ready to:
- Implement checkExposure plus runCheck in check.go.
- Prove that the safe lab/ollama Service returns PASS.
- Rebuild the cluster while measuring how long the saved-image path takes.
Implement the deterministic check
Test-driven development turns the failure from the previous step into an implementation contract. Your code has to satisfy the supplied tests without weakening them.
- Switch back to the editor window for ~/laptop-ai-lab.
- Read docs/build-spec.md to identify every exposure rule.
- Compare each rule with the supplied Go example.
- Create check.go inside ~/laptop-ai-lab with learner-written implementations of checkExposure plus runCheck.
- Save check.go.
Before you run the tests, do you expect the full suite to pass on the first attempt? Hold that prediction.
- Test your implementation by running this command:
go test
What does this test prove?
- The Go test runner compiles the current package with the supplied test files.
- The tests exercise the contract implemented by checkExposure plus runCheck.
- A final ok result shows that your implementation satisfies the deterministic rule.
A remaining FAIL result gives you the next precise gap to close. Treat the first error as the shortest route back to green.
- Read the first error line if the tests still fail.
- Update only check.go to satisfy the named contract.
- Save check.go.
- Repeat the test command above until the output ends with ok.
That is the core rule implemented. Your tests now make the exposure verdict repeatable.
Tests still failing?
- Check that the function names are exactly checkExposure plus runCheck.
- Compare each function signature with the supplied tests.
- Keep the supplied test file unchanged while correcting check.go.
Still stuck? Help me interpret the first Go test error without changing the supplied tests.
Prove the safe decision
Passing unit tests prove the rule in isolation. The live run applies that same rule to the safe ClusterIP Service in lab.
Before you run the live check, do you expect the real Service to satisfy the tested rule? Keep your answer in mind.
- Check the running Service by running this command:
go run . check
What does the live check prove?
- The application invokes your runCheck implementation against the running cluster.
- The rule evaluates the actual lab/ollama Service.
- The safe ClusterIP configuration produces PASS with exit status 0.
You should see PASS for lab/ollama. Your shell records exit status 0 for the successful run.
Live check not returning PASS?
- Confirm the first error names check.go before changing your implementation.
- Check that the active context remains kind-laptop-ai-lab.
- Confirm that the target remains the lab/ollama Service.
Need help? Help me diagnose why my live exposure check does not return PASS for the safe Service.
The implementation now works against the real cluster. Your public record also needs the reasoning behind its definition of an open Service.
- Add one decision record to README.md using the classification already recorded in docs/decisions.md for LoadBalancer Services plus .spec.externalIPs.
- Defend why that classification produces OPEN or remains outside the rule.
- Save README.md.
Your contribution guide requires a DCO sign-off. The commit command opens your configured editor so you can provide a message.
- Create the signed implementation checkpoint by running these commands:
git add check.go README.md
git commit -s
What do these commands record?
- The first line stages only check.go plus README.md.
- The second line creates a Git commit from those staged files.
- The -s flag adds the required sign-off trailer.
- Enter a short commit message describing the working check plus its decision record.
- Finish the editor session using its save-and-close action.
- Verify the resulting commit by running this command:
git show
What should the commit show?
The latest commit should contain check.go plus the README.md decision. Its message should include a Signed-off-by trailer.
Your implementation now has an accountable checkpoint. The deterministic rule plus its design rationale travel together.
Missing the sign-off trailer?
- Confirm that you used the sign-off flag when creating the commit.
- Check that Git has your contributor name plus email configured.
- Keep the existing commit unchanged until you understand which sign-off detail is missing.
Need a second pair of eyes? Help me understand why my signed commit is missing its DCO trailer.
Rebuild the lab from scratch
A passing check proves the current cluster is safe. A clean stop followed by a timed start proves that the whole lab can be reproduced with the saved image.
- Stop the running lab from the first terminal by running this command:
go run . down
What does the clean stop prove?
The command removes the running local lab through the project workflow. This gives the rebuild a clean starting point.
The laptop-ai-lab cluster is now down. The next run has to recreate it.
- Switch back to the second terminal from earlier.
- Start a stopwatch for the rebuild.
Before you rebuild, do you expect the output to save the already-saved image again? Hold that prediction.
- Recreate the lab by running this command:
go run . up
What does the rebuild prove?
- The command recreates the laptop-ai-lab cluster.
- The cluster returns with context kind-laptop-ai-lab plus namespace lab.
- The absence of a second image-saving line proves that the existing saved image was reused.
- Stop the stopwatch when the rebuild reports the lab as ready.
- Record the elapsed time here: your measured rebuild seconds.
- Inspect the rebuild output for the absence of another image-saving line.
- Add your measured rebuild seconds to README.md with the label Measured.
- Save README.md.
The lab is running again from a clean start. You now have measured evidence that the rebuild reused the saved image.
Rebuild not returning to ready?
- Read the first error line before changing any project file.
- Confirm the second terminal is still in ~/laptop-ai-lab.
- Check that the same container engine from setup remains available.
Need help with the rebuild? Help me diagnose the first rebuild error without pulling a new image.
- Create the signed rebuild checkpoint by running these commands:
git add README.md
git commit -s
What does this checkpoint preserve?
The commit preserves the measured rebuild time in project history. The sign-off keeps the measurement record under the same contribution policy as the implementation.
- Enter a short commit message describing the measured rebuild record.
- Finish the editor session using its save-and-close action.
- Verify the latest signed commit by running this command:
git show
What should the final commit show?
The latest commit should contain the README.md rebuild record. Its message should include the Signed-off-by trailer.
Your check is green against the safe Service. Next, you will plant both exposure mistakes to prove that the same gate turns red.
Validate Safe and Open States
Your learner-authored Go check already returns PASS for the safe Kubernetes Service. One successful run gives you a baseline.
A credible policy check also needs evidence from each planted unsafe state. In this step, you will execute every test-plan row before recording the observed result.
In this step, get ready to:
- Record the safe baseline plus every measured result.
- Prove the open kind configuration returns OPEN.
- Restore the safe state for the signed test-plan commit.
Record the safe baseline
The baseline anchors every later comparison. It captures the passing tests plus the live ClusterIP result before you introduce either opening.
- Select docs/test-plan.md from your editor's file explorer.
- Run the supplied Go tests from ~/laptop-ai-lab with this command:
go test
What does this test prove?
The supplied tests exercise checkExposure independently of the live model. A passing result proves the deterministic rule behaves as specified.
You should see ok laptop-ai-lab in the output.
- Enter ok laptop-ai-lab beside the matching test-plan row.
- Run the Service inspection command supplied in the safe-Service row.
You should see ClusterIP as the type. You should also see 11434/TCP as the port.
- Enter the observed Service type beside the matching row.
- Enter the observed Service port beside the matching row.
Before you run the live check, do you expect its verdict to agree with the Service inspection?
- Run the live exposure check with this command:
go run . check
What does the live check prove?
The runCheck path evaluates the live lab/ollama Service. Its verdict comes from the deterministic rule in check.go.
You should see PASS for lab/ollama. The command should finish with exit status 0.
- Enter PASS with exit status 0 beside the safe-check row.
- Enter your recorded score out of 10 beside the model-score row.
- Label the model score Measured.
- Enter your recorded tokens per second beside the throughput row.
- Label the throughput Measured.
- Enter your recorded rebuild seconds beside the rebuild row.
- Label the rebuild time Measured.
- Record that the rebuild output contained no image-saving line.
Safe baseline not matching?
- Check the first failing test line for the exact file or function that differs from the supplied specification.
- Confirm the inspected Service is lab/ollama.
- Confirm the Service type is ClusterIP before recording the row.
Still stuck? Help me diagnose why my safe laptop-ai-lab baseline does not show passing Go tests or a PASS verdict.
Plant both open states
The next two rows challenge different parts of the rule. The open kind configuration tests the cluster path. The NodePort Service tests a declarative exposure that the check must reject.
- Locate the open kind configuration row in docs/test-plan.md.
- Run the supplied open-configuration rebuild workflow from that row.
- Wait for the supplied up workflow to report that the lab is ready.
Before you check this rebuilt cluster, do you think the deterministic rule will still return PASS?
- Evaluate the open kind configuration with this command:
go run . check
Why test the cluster configuration?
The rebuilt cluster uses testdata/kind-config-open.yaml to plant the first opening. This confirms the check covers the exposure decision recorded in the build specification.
You should see OPEN. Go should report exit status 1.
- Enter OPEN with exit status 1 beside the open-configuration row.
Open configuration still returns PASS?
- Confirm the rebuild row selected testdata/kind-config-open.yaml.
- Check that the rebuilt cluster is named laptop-ai-lab.
- Read the first unexpected line before changing check.go.
Need another pair of eyes? Help me determine why the open kind configuration still passes my exposure check.
The second planted opening creates lab/ollama-open as a NodePort Service. Kubernetes exposes a NodePort on each node IP at its assigned static port.
- Apply the supplied NodePort manifest with this command:
kubectl apply -f testdata/ollama-nodeport.yaml
What does this command change?
The manifest adds the planted lab/ollama-open Service to the current cluster. Its declared type gives the deterministic check a second unsafe state to evaluate.
- Run the Service inspection command supplied in the NodePort row.
You should see lab/ollama-open using type NodePort. You should also see node port 30434.
- Enter the observed Service type beside the NodePort row.
- Enter the observed node port beside the NodePort row.
Before you run the check, which live Service do you expect it to flag?
- Evaluate the planted NodePort Service with this command:
go run . check
Why this failure matters
The check inspects the declared Service state. This catches an opening even when local networking makes a live probe unreliable.
You should see OPEN for lab/ollama-open with exit status 1.
- Enter OPEN with exit status 1 beside the NodePort row.
NodePort test not turning red?
- Confirm the manifest application completed successfully.
- Confirm the inspected Service name is lab/ollama-open.
- Confirm the inspected node port is 30434.
Still seeing PASS? Help me compare lab/ollama-open with the NodePort rule in docs/build-spec.md.
Restore PASS and commit the evidence
A planted failure becomes useful evidence when removing it restores the safe verdict. The final row proves the check responds to the live Service state.
- Delete the planted NodePort Service with this command:
kubectl delete -f testdata/ollama-nodeport.yaml
What does this deletion preserve?
This removes the resource defined by testdata/ollama-nodeport.yaml. The safe lab/ollama Service remains available for the final check.
Before the final run, do you expect the verdict to recover now that the planted Service is gone?
- Run the same live check again:
go run . check
What does the recovery prove?
The result now reflects the restored safe state. This closes the test loop from PASS to OPEN to PASS.
You should see PASS for lab/ollama with exit status 0.
- Enter PASS beside the final test-plan row.
- Enter exit status 0 beside the final test-plan row.
- Save docs/test-plan.md.
- Review every row for an observed result.
- Stage the completed test plan with the signed commit command below:
git add docs/test-plan.md
git commit -s -m "Complete the test plan"
Why sign the evidence commit?
The DCO sign-off records your certification on the public contribution. The commit keeps the completed test plan separate from the earlier implementation work.
You should see a new Git commit summary for Complete the test plan. Your repository now contains the full observed evidence trail.
Commit did not complete?
- Confirm docs/test-plan.md was saved before staging it.
- Read the first Git error line for the missing identity or repository state.
- Run the same signed commit command again after fixing the named issue.
Need help with the signed commit? Help me diagnose why my DCO-signed test-plan commit failed.
Secret mission
Close-out, 10 minutes
Defend the Service rule in public with its blind spots documented. Open one upstream contribution before you merge and release the reviewed work. Finish with the local cluster stopped.
Clean Up Your Resources
Clean Up Your Resources
Your local kind cluster is already down, which stops its local compute use. The remaining resources cost $0.00 USD, so decide whether to keep them, pause here, or delete them entirely.
Resources you used:
- The local project folder is ~/laptop-ai-lab. It holds the project files. It also holds the measured evidence. It includes the image archive created during the first build.
- The public GitHub repository is laptop-ai-lab. It preserves issue #1. It also preserves the merged pull request. Its release is v0.1.0.
- The public GitHub Project holds the planning record for the lab.
Keep everything running
No action is needed. Choose this option if you want your local evidence and public contribution trail to remain available.
- Keep the ~/laptop-ai-lab folder for future experiments or demonstrations.
- Keep the public laptop-ai-lab repository as the permanent record of your issue. Its merged pull request also remains available.
- Keep the public GitHub Project as the planning record for the contribution.
- Leave the laptop-ai-lab cluster stopped until you want to rebuild the lab.
Pause - I'll come back to this later
Pausing preserves your local files and public work. The cluster remains down, so the lab does not use local compute.
- Keep the ~/laptop-ai-lab folder on your computer.
- Keep the public laptop-ai-lab repository available on GitHub.
- Keep the public GitHub Project available with its planning history.
- Leave the laptop-ai-lab cluster stopped.
Delete - I don't want to use this again
Remove the project resources when you want a clean slate. Your upstream contribution remains part of the external discussion.
Before You Delete
Deletion permanently removes your local evidence and public release trail. Preserve any records you want before continuing.
- Return to the public laptop-ai-lab repository on GitHub.
- Select Settings in the repository navigation bar.
- Scroll to the repository deletion controls at the bottom of the settings page.
- Use the repository deletion control.
- Complete the repository confirmation prompt.
The repository no longer appears under your account. Issue #1 is removed with it. The merged pull request is also removed. Release v0.1.0 is removed too.
- Return to the public GitHub Project for the lab.
- Select Settings in the Project menu.
- Find the Project deletion control in the settings page.
- Use the Project deletion control.
- Complete the Project confirmation prompt.
The Project no longer appears under your account. Its planning board has been removed.
What Stays Public
Your upstream kind issue comment or documentation review lives in the upstream repository. Deleting your repository and Project does not remove that contribution.
Delete your local project folder:
macOS
- Delete ~/laptop-ai-lab from your home folder by running these commands:
cd ~
rm -rf laptop-ai-lab
ls
What Do These Commands Do?
- The first command moves your terminal to your home folder before deletion.
- The second command permanently removes ~/laptop-ai-lab and every file inside it.
- The final command lists the remaining items in your home folder.
The final listing should no longer include laptop-ai-lab.
Folder Still Listed?
Check that the folder name is spelled exactly as laptop-ai-lab. Run the deletion commands again after correcting the name.
If your shell reports a permissions problem, confirm that your account owns the project folder.
Help me remove the local laptop-ai-lab folder safely.
Windows
- Delete ~/laptop-ai-lab from your home folder by running these commands in PowerShell:
Set-Location ~
Remove-Item -Recurse -Force ~/laptop-ai-lab
Get-ChildItem ~
What Do These Commands Do?
- The first command moves PowerShell to your home folder before deletion.
- The second command permanently removes ~/laptop-ai-lab and every file inside it.
- The final command lists the remaining items in your home folder.
The final listing should no longer include laptop-ai-lab.
Folder Still Listed?
Close any editor window that still has a project file open. Run the deletion commands again afterward.
If PowerShell reports a permissions problem, confirm that your account owns the project folder.
Help me remove the local laptop-ai-lab folder safely.
Linux
- Delete ~/laptop-ai-lab from your home folder by running these commands:
cd ~
rm -rf laptop-ai-lab
ls
What Do These Commands Do?
- The first command moves your terminal to your home folder before deletion.
- The second command permanently removes ~/laptop-ai-lab and every file inside it.
- The final command lists the remaining items in your home folder.
The final listing should no longer include laptop-ai-lab.
Folder Still Listed?
Check that the folder name is spelled exactly as laptop-ai-lab. Run the deletion commands again after correcting the name.
If your shell reports a permissions problem, confirm that your account owns the project folder.
Help me remove the local laptop-ai-lab folder safely.
Nice Work!
Nice Work!
You did it! You built a public contributor lab around a local Kubernetes cluster.
You've learned how to:
- Rebuild a local Kubernetes cluster with kind through a single Go command. Keep the Ollama Service at ClusterIP. Reach the model through the loopback address 127.0.0.1:21434.
- Measure qwen3:1.7b with a ten-question score. Measure its throughput in tokens per second. Use deterministic Go tests as the security decision gate. Prove that lab/ollama returns PASS. Prove that both planted exposure cases return OPEN with exit status 1.
- Publish a public GitHub repository whose design came before its implementation. Pin the pull-request test workflow to full commit SHAs. Merge the reviewed change through issue #1. Publish release v0.1.0.
- Secret Mission: Document the deterministic rule's limits in docs/limits.md. Add an after-action review. Open one human-reviewed upstream kind contribution. Disclose AI assistance. Stop laptop-ai-lab after the release.
Ready to quiz yourself?