gns3-server/docs/design/docker-image-type.md
YueGuobin 1ccea916c3
docs: roadmap for docker image types (vendor profiles)
Design proposal for an image_type discriminator on Docker templates plus
a compute-side profile registry: vendor parameters graduate from
environment markers into schema-gated fields, generic-feature
applicability becomes declared capability data, existing markers stay as
a compatibility fallback. Explicitly not a new node type.

Four profiles already exist to shape it (iol-runner, XRd, SR Linux
prototype, FRR appliance); implementation follows the current PR series.
2026-09-05 21:54:52 +08:00

6.7 KiB

Docker Image Types (Vendor Profiles) — Roadmap

Status: proposal, not implemented. This is the agreed direction for the next iteration of vendor Docker support. Four vendor profiles already exist across branches and prototypes — iol-runner (this branch), XRd and the SR Linux prototype (vendor/skip-init family), and the FRR registry appliance — enough evidence to justify the registry. Implement it as a follow-up to the current PR series, shaped by all four rather than by iol-runner alone.

Problem

All Docker nodes share one template type (template_type: "docker") and one template schema. Vendor images (iol-runner, SR Linux, XRd, …) are selected and configured today through environment markers — free-form strings parsed by the compute (GNS3_IOL_RUNNER, GNS3_IOL_MEMORY, GNS3_IOL_STARTUP_CONFIG, GNS3_SKIP_INIT, GNS3_UNIX_SOCKET_NIO, …). Three pains have already surfaced:

  1. Invisible and unvalidated. The markers do not exist in the API schema: no OpenAPI documentation, no validation, typo or bad value silently falls back to a default (GNS3_IOL_MEMORY=notanumber → 2048).
  2. The shared-schema dilemma. Adding a vendor-specific field to the Docker template schema pollutes every Docker template with a field only one vendor reads; not adding it pushes everything into environment strings. The startup_config discussion (ended in the GNS3_IOL_STARTUP_CONFIG knob) is the canonical example — the tension exists because there is no legitimate discriminator dimension.
  3. Generic-feature applicability is implicit. Which generic Docker features apply to which image is only documented in code comments: mac_address is meaningless under unix-socket NIO, /etc/network seeding is dead weight for skip-init images, extra_configs targets under persisted volumes get a (correct-but-noisy) shadow warning.

Non-goal: new node types

A template_type/node_type per vendor ("iol", "srlinux", …) is explicitly rejected. node_type is a top-level concept: it drives compute module routing (/projects/{id}/{node_type}/nodes), capability reporting, GUI node types and link handling. N vendor types that are all Docker underneath would multiply routing and schema surface for zero mechanism — vendor images are content, not mechanism, and GNS3's appliance/template system is where content belongs.

Design

One discriminator field on Docker templates

{
    "template_type": "docker",
    "image_type": "iol-runner",
    "image": "iol-xe/iol-xe:17-18-02"
}

image_type is optional; absent (or "generic") keeps today's plain DockerVM behavior and marker sniffing. The profiles that exist today, mapped to their current mechanisms and archetypes:

Profile Current mechanism Archetype
FRR appliance plain DockerVM: init.sh /etc/network + start_command (frrinit.sh), console on PID 1 generic
XRd VendorDockerVM skip-init + GNS3_SHM_SIZE/GNS3_DEVICES, extra_configs, udev masking vendor skip-init
SR Linux (prototype) VendorDockerVM skip-init + docker_exec console + GNS3_INTERFACE_NAMES vendor skip-init
iol-runner IOLDockerVM: unix-socket NIO, per-start config generation, NVRAM startup-config iol-runner

Possible first values: generic, iol-runner, and a value for the skip-init vendor NOS family once its common shape settles (XRd and SR Linux may end up sharing it or splitting — that is exactly what the registry should decide with all four in front of us).

A compute-side registry

IMAGE_PROFILES = {
    "iol-runner": {
        "class": IOLDockerVM,
        "fields": ("iol_memory", "startup_config"),   # schema-gated
        "capabilities": {...},                          # see below
    },
    ...
}
  • Class selection moves from environment sniffing (Docker._select_node_class) to the field, with the existing markers kept as a fallback for templates created before the field existed (zero-migration compatibility).

  • Vendor parameters graduate into schema fields, gated by pydantic conditional validation (accepted — and validated — only when image_type matches). This resolves the shared-schema dilemma properly: the field exists, but only means something for its profile. The environment knobs remain as the wire-level compatibility entry.

  • Capability declaration makes generic-feature applicability data instead of comments:

    Capability plain Docker vendor skip-init iol-runner
    mac_address honored yes yes no (IOL derives MACs from the app id)
    /etc/network seeding yes no no
    extra_configs targets under volumes shadowed works (real-path binds) works
    console telnet/http/… docker_exec PID 1 stdio (telnet)
    startup config extra_configs injection image-specific nvram build (nvram_import)

    Consumers: error/warning quality (don't warn about inapplicable features), the WebUI (vendor-aware template forms — a bonus, not a driver), and documentation generation.

The existing class hierarchy (DockerVMVendorDockerVMIOLDockerVM) is unchanged — the registry only externalizes selection and declaration.

Migration & compatibility

  • Old templates (markers only) keep working via the fallback; a one-time optional converter can rewrite markers → image_type + fields.
  • The controller-side materialization conventions (GNS3_IOL_STARTUP_CONFIG file → startup_config_content, sent once) carry over unchanged — the field version references the same content pipeline.

Roadmap steps

  1. Add image_type to the Docker template schema (+ DB column + Alembic migration — see the three-place rule for template fields) and wire class selection through the registry, markers as fallback.
  2. Move iol-runner knobs to gated fields (iol_memory, startup_config), deprecating-but-supporting the env forms.
  3. Introduce the capability table and use it to silence inapplicable warnings (mac_address, /etc/network, extra-config shadowing).
  4. Revisit per-vendor schemas (a sub-model per profile) only if a profile grows more than a handful of fields — not before.

Open questions

  • Field name: image_type vs vendor vs runner — decide when the second profile lands; the value vocabulary should name the runtime contract, not the vendor.
  • Should appliances (.gns3a) carry image_type explicitly, or should installation keep deriving it from the appliance's environment block?
  • Where the gated vendor fields live long-term: flat on the Docker template (simple, gated) vs nested {"image_type": ..., "settings": {...}} (cleaner, more schema churn).