Skip to content

feat(docker): add Network Plumbing world with internal attempt-owned networks - #34

Merged
Aleksandergreg merged 1 commit into
mainfrom
feat/docker-networks
Sep 30, 2026
Merged

Aleksandergreg merged 1 commit into
mainfrom
feat/docker-networks

Conversation

@Aleksandergreg

Copy link
Copy Markdown
Owner

Summary

Adds Docker World 3, "Network Plumbing" (Missions 35–39), and the network support it needs. Players learn that containers can reach each other only over a network they share. They read network membership, connect and disconnect existing containers, create a private network, and finish by segmenting a flat network. Every lab network is internal, owned by the attempt, labeled, and removed afterwards.

This is Stage 3a of the Docker expansion roadmap. Volumes remain future work because they attach only at container creation, so they depend on a limited docker run.

Player-facing changes

  • New teaching commands, parsed by OpsQuest and never passed through: docker network ls, inspect NETWORK, create NETWORK, rm NETWORK, connect NETWORK ALIAS, and disconnect NETWORK ALIAS. None accept options, and the built-in bridge, host, none, and default networks are rejected by name.
  • docker inspect ALIAS now lists a container's logical networks under NetworkSettings.Networks. docker network inspect shows a network's logical ID, driver, internal flag, and attached containers, with no engine IDs or generated names.
  • opsquest doctor now reports "orphaned lab resources", because leftover networks are counted alongside containers.
  • Five new missions (80–140 XP):
    • Network Census (beginner): find the network two services share.
    • Can't Reach the DB (beginner): connect api to the database network without exposing the database to the web tier.
    • Need to Know (intermediate): disconnect a batch job from a network it should never have joined.
    • Private Channel (intermediate): create a network and move two services onto it.
    • Segmentation (advanced): split a flat network into tiers, then retire a stale network and the container holding it open.

Mission schema

  • A Docker setup can declare up to 8 networks by logical name. Fixtures join up to 4 of them through a new networks field. A fixture without networks keeps --network none, so existing missions behave exactly as before.
  • New route-independent conditions:
    • docker_containers_share_network and docker_containers_isolated take exactly two containers and pass only while both still exist.
    • docker_network_absent takes a declared network. A player who solves a mission by creating their own network also passes.

Safety and cleanup

  • OpsQuest creates every network with fixed options: the bridge driver and --internal, so there is no route to the host network or the internet. Networks carry the same five ownership labels, advisory owner labels, and generated names (opsquest-<session>-nNN) as containers.
  • Players can create at most 4 networks per attempt. They can only attach this attempt's containers to this attempt's networks, resolved to exact IDs.
  • docker network rm is refused while any container is attached, even a stopped one. Docker would allow it and leave that container unable to start.
  • Engine errors are rewritten so network and container IDs and generated names never reach the player.
  • Close removes containers first, then networks, verifies ownership labels on each exact ID, and keeps unresolved networks for a retry.
  • The orphan janitor now also sweeps networks, after containers, using the same label, generated-name, owner-process, and 24-hour age checks.
  • The internal flag is not the only control. Players cannot run commands inside containers (there is no exec, and fixtures run fixed programs), so no player-controlled traffic starts inside a lab network. This is documented in the safety model.

Compatibility

  • Schema changes are additive, and all existing missions load unchanged. Older binaries reject the new catalog under strict loading.
  • Persisted profiles need no migration: completions are keyed by stable mission IDs.
  • docker ps output is unchanged. Container inspect gains a NetworkSettings.Networks list.
  • Catalog: 39 missions (23 Linux, 16 Docker in three worlds).

Documentation

Updates README, AGENTS.md, the docs overview, Docker guide, mission map (Missions 35–39 and a World 3 section), learning-journey diagram, safety model (lab networks, new limits, threat rows), mission authoring guide, mission checklist, and roadmap. The roadmap now lists volumes and reachability probes as the next increments.

Testing

  • make check-all: passed, including race detection and the smoke test
  • make docs-check: passed in strict mode
  • GOOS=windows and GOOS=linux go vet ./internal/dockerlab: passed
  • git diff --check: clean
  • OPSQUEST_DOCKER_TEST=1 go test ./internal/dockerlab -run Integration on Docker Desktop (engine 29.7.2): all 6 integration tests passed. They cover network connect and internal isolation, the full Segmentation route, the orphaned network sweep, and, for the first time against a real engine, Stage 1 health probes, crash loops, --tail, and rm.
  • After the run: 0 OpsQuest-labeled containers or networks remained, and opsquest doctor reported no orphaned lab resources.

New unit coverage: network creation arguments and order, every command with its success, edge, and failure cases, parser rejection before any Docker call, cleanup order and ownership refusal, setup-failure cleanup, janitor network sweeping, error redaction, schema validation and deep copies, and canonical, alternative, and incomplete routes for all five missions.

Description

Please provide a summary of the change and explain what problem it solves. Include any relevant motivation and context.

Related Issue:
If applicable, please reference the issue number that this pull request fixes:
Fixes #[issue-number]

Type of Change

Please check the type(s) of changes your code introduces:

  • Bug fix: Non-breaking change which fixes an issue.
  • New feature: Non-breaking change which adds functionality.
  • Breaking change: Fix or feature that would cause existing functionality to change.
  • Documentation update: Changes to documentation only.

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce the testing environment.

  • Test A
  • Test B

Include any details regarding your testing environment, test configuration, or steps to reproduce.

Checklist

Before creating this pull request, please ensure that your code meets the following requirements:

  • My code follows the style guidelines of this project.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have made corresponding changes to the documentation.
  • My changes generate no new warnings.
  • Any dependent changes have been merged and published in downstream modules.

Screenshots (if applicable)

If your pull request includes visual changes, please include screenshots or animated GIFs to help explain your changes.

Additional Context

Add any other context or screenshots about your pull request here.

…networks

Summary

Adds Docker World 3, "Network Plumbing" (Missions 35–39), and the
network support it needs. Players learn that containers can reach each
other only over a network they share. They read network membership,
connect and disconnect existing containers, create a private network,
and finish by segmenting a flat network. Every lab network is internal,
owned by the attempt, labeled, and removed afterwards.

This is Stage 3a of the Docker expansion roadmap. Volumes remain future
work because they attach only at container creation, so they depend on
a limited `docker run`.

Player-facing changes

- New teaching commands, parsed by OpsQuest and never passed through:
  `docker network ls`, `inspect NETWORK`, `create NETWORK`,
  `rm NETWORK`, `connect NETWORK ALIAS`, and `disconnect NETWORK ALIAS`.
  None accept options, and the built-in bridge, host, none, and default
  networks are rejected by name.
- `docker inspect ALIAS` now lists a container's logical networks under
  NetworkSettings.Networks. `docker network inspect` shows a network's
  logical ID, driver, internal flag, and attached containers, with no
  engine IDs or generated names.
- `opsquest doctor` now reports "orphaned lab resources", because
  leftover networks are counted alongside containers.
- Five new missions (80–140 XP):
  - Network Census (beginner): find the network two services share.
  - Can't Reach the DB (beginner): connect api to the database network
    without exposing the database to the web tier.
  - Need to Know (intermediate): disconnect a batch job from a network
    it should never have joined.
  - Private Channel (intermediate): create a network and move two
    services onto it.
  - Segmentation (advanced): split a flat network into tiers, then
    retire a stale network and the container holding it open.

Mission schema

- A Docker setup can declare up to 8 `networks` by logical name.
  Fixtures join up to 4 of them through a new `networks` field. A
  fixture without networks keeps `--network none`, so existing missions
  behave exactly as before.
- New route-independent conditions:
  - `docker_containers_share_network` and `docker_containers_isolated`
    take exactly two `containers` and pass only while both still exist.
  - `docker_network_absent` takes a declared `network`.
  A player who solves a mission by creating their own network also
  passes.

Safety and cleanup

- OpsQuest creates every network with fixed options: the bridge driver
  and `--internal`, so there is no route to the host network or the
  internet. Networks carry the same five ownership labels, advisory
  owner labels, and generated names (`opsquest-<session>-nNN`) as
  containers.
- Players can create at most 4 networks per attempt. They can only
  attach this attempt's containers to this attempt's networks, resolved
  to exact IDs.
- `docker network rm` is refused while any container is attached, even
  a stopped one. Docker would allow it and leave that container unable
  to start.
- Engine errors are rewritten so network and container IDs and
  generated names never reach the player.
- `Close` removes containers first, then networks, verifies ownership
  labels on each exact ID, and keeps unresolved networks for a retry.
- The orphan janitor now also sweeps networks, after containers, using
  the same label, generated-name, owner-process, and 24-hour age
  checks.
- The internal flag is not the only control. Players cannot run
  commands inside containers (there is no exec, and fixtures run fixed
  programs), so no player-controlled traffic starts inside a lab
  network. This is documented in the safety model.

Compatibility

- Schema changes are additive, and all existing missions load unchanged.
  Older binaries reject the new catalog under strict loading.
- Persisted profiles need no migration: completions are keyed by stable
  mission IDs.
- `docker ps` output is unchanged. Container inspect gains a
  NetworkSettings.Networks list.
- Catalog: 39 missions (23 Linux, 16 Docker in three worlds).

Documentation

Updates README, AGENTS.md, the docs overview, Docker guide, mission map
(Missions 35–39 and a World 3 section), learning-journey diagram,
safety model (lab networks, new limits, threat rows), mission authoring
guide, mission checklist, and roadmap. The roadmap now lists volumes
and reachability probes as the next increments.

Testing

- make check-all: passed, including race detection and the smoke test
- make docs-check: passed in strict mode
- GOOS=windows and GOOS=linux go vet ./internal/dockerlab: passed
- git diff --check: clean
- OPSQUEST_DOCKER_TEST=1 go test ./internal/dockerlab -run Integration
  on Docker Desktop (engine 29.7.2): all 6 integration tests passed.
  They cover network connect and internal isolation, the full
  Segmentation route, the orphaned network sweep, and, for the first
  time against a real engine, Stage 1 health probes, crash loops,
  `--tail`, and `rm`.
- After the run: 0 OpsQuest-labeled containers or networks remained,
  and `opsquest doctor` reported no orphaned lab resources.

New unit coverage: network creation arguments and order, every command
with its success, edge, and failure cases, parser rejection before any
Docker call, cleanup order and ownership refusal, setup-failure
cleanup, janitor network sweeping, error redaction, schema validation
and deep copies, and canonical, alternative, and incomplete routes for
all five missions.
@Aleksandergreg
Aleksandergreg enabled auto-merge (squash) September 30, 2026 18:36
@Aleksandergreg
Aleksandergreg merged commit b4b1ba9 into main Sep 30, 2026
5 checks passed
@Aleksandergreg
Aleksandergreg deleted the feat/docker-networks branch September 30, 2026 18:37
Aleksandergreg pushed a commit that referenced this pull request Sep 30, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.8.0](v0.7.1...v0.8.0)
(2026-09-30)


### Features

* **docker:** add Container Triage world and orphaned lab cleanup
([56dbfe2](56dbfe2))
* **docker:** add Container Triage world and orphaned lab cleanup
([#32](#32))
([56dbfe2](56dbfe2))
* **docker:** add Network Plumbing world with internal attempt-owned
networks
([#34](#34))
([b4b1ba9](b4b1ba9))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Aleksandergreg added a commit that referenced this pull request Sep 30, 2026
…#35)

## Problem

`isMissingNetwork` treated any Docker output containing "not found" as a
missing network. When the selected Docker context is unavailable, the
CLI prints `context "x": context not found`, and that was misread as a
missing network:

- `docker_network_absent` passed while the network still existed.
- `Close` dropped the network from its retryable cleanup set, so all lab
networks leaked once the context recovered.

This fix was committed on `feat/docker-networks` after #34 was
squash-merged, so it is not in `main` or in release 0.8.0.

## Fix

A network counts as missing only when Docker names the exact reference:

- `network <ref> not found` (current engines, verified on Docker
29.7.2);
- `No such network: <ref>` (older engines).

Every caller passes its exact reference: inspect, `Close`, and the
orphan janitor. Context, transport, and CLI failures now propagate as
errors.

## Tests

- Classification table built from real Docker 29.7.2 messages, including
the exact `context not found` output.
- Context failure and recovery: validation returns an error, and a
retried `Close` removes every network after recovery.
- The janitor's network removal path.

All three fail against the old substring match.

## Validation (on `main` + this commit)

- `make check-all`: passed, including race detection and the smoke test
- `OPSQUEST_DOCKER_TEST=1 go test ./internal/dockerlab -run
Integration`: all 6 passed on Docker Desktop 29.7.2
- 0 OpsQuest-labeled containers or networks left afterwards
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant