Operate Shared VyOS Image Build Pipeline (HyOps)¶
Build one canonical VyOS image and publish the state contract consumed by both the Proxmox and Hetzner seed paths.
Module:
core/shared/vyos-image-build
Purpose¶
This is the default product path for VyOS image production.
Use it when you want HybridOps to:
- build or validate one canonical image
- publish it to object storage
- publish a reusable HyOps state contract for downstream seed modules
Use Operate Shared VyOS Published Image Contract (HyOps)
only when the image already exists and you only need to register it into state.
Default product path¶
- Apply
core/shared/vyos-image-build. - Consume its
artifact_state_reffrom: core/onprem/vyos-template-seedcore/hetzner/vyos-image-seed- Let:
platform/onprem/vyos-edgeorg/hetzner/vyos-edge-foundationconsume the resulting template/image state.
This keeps the platform DRY:
- build once
- publish once
- seed many
Authoritative implementation note:
- the canonical VyOS cloud-init helper is
tools/build/vyos/assets/cc_vyos.py - packaged build and seed paths must consume that helper instead of carrying their own divergent copies
Recommended image shape¶
Recommended published output:
- one versioned
qcow2artifact published throughcore/shared/vyos-image-build
Why:
- Proxmox template seeding consumes the
qcow2directly - Hetzner can consume the same
qcow2through the built-in wrapper path
The Hetzner wrapper path still requires an execution host that:
- has
qemu-img - is publicly reachable for temporary artifact serving
- is intentionally acting as the wrapper/import host
If you want to bypass the wrapper on purpose, publish a direct raw.xz artifact and set image_source_url explicitly for the Hetzner seed module.
Typical validation¶
hyops validate --env dev --skip-preflight \
--module core/shared/vyos-image-build \
--inputs modules/core/shared/vyos-image-build/examples/inputs.gcs-state.yml
Typical apply¶
hyops apply --env dev \
--module core/shared/vyos-image-build \
--inputs modules/core/shared/vyos-image-build/examples/inputs.gcs-state.yml
Verified steady-state example¶
Validated on Sunday, March 16, 2026:
- shared build state:
core/shared/vyos-image-build#vyos_default_build - artifact version:
2026.03.03-0027-rolling-r3 - shared qcow2 URL:
https://storage.googleapis.com/hyops-dev-vyos-artifacts-gcp03-a1/network/vyos/vyos-1.5-2026.03.03-0027-rolling-r3.qcow2- on-prem template seed state:
core/onprem/vyos-template-seed#vyos_template template_source_urlnow tracks the same shared qcow2 artifact- Hetzner seed state can consume the same shared
qcow2artifact through the built-in wrapper path whenrequired_envincludesHYOPS_VYOS_GCS_SA_JSON
Verification commands:
jq '.status,.outputs' \
"$HOME/.hybridops/envs/dev/state/modules/core__shared__vyos-image-build/instances/vyos_default_build.json"
jq '.status,.outputs' \
"$HOME/.hybridops/envs/dev/state/modules/core__hetzner__vyos-image-seed/latest.json"
jq '.status,.outputs' \
"$HOME/.hybridops/envs/dev/state/modules/core__onprem__vyos-template-seed/latest.json"
Downstream consumption¶
Proxmox template seed:
artifact_state_ref: "core/shared/vyos-image-build#vyos_default_build"
artifact_key: "vyos-1.5"
Hetzner image seed:
- default: consume
artifact_state_refand declareHYOPS_VYOS_GCS_SA_JSONinrequired_envwhen the shared artifact is private GCS-backed - optional override: set a direct public
image_source_urlto a publishedraw.xzwhen you want to bypass the wrapper
Important behavior:
artifact_state_refis authoritative for downstream seed inputs- when that state resolves to a private GCS-backed shared artifact, the consuming seed module must also declare
HYOPS_VYOS_GCS_SA_JSON(orHYOPS_VYOS_GCS_SA_JSON_FILE) ininputs.required_env - stale rerun values such as
artifact_url,artifact_sha256,artifact_version,image_source_url, andseed_commandmust not override the upstream artifact contract image_state_refis authoritative fororg/hetzner/vyos-edge-foundation
Builder posture¶
- preferred ISO build method remains
vyos-vm-images - packaged Packer fallback remains available for controlled/debug use
- KVM-backed builders are the supported path for ISO/Packer builds
tcgis debug-only and must not be treated as the production build path
Fresh-user prerequisites¶
- Provision one object repository, for example:
org/gcp/object-repo#vyos_artifacts- Supply upload credentials outside Terraform state, for example:
HYOPS_VYOS_GCS_SA_JSONHYOPS_VYOS_GCS_SA_JSON_FILE- Declare the selected credential env key in
inputs.required_env. The standard vault-backed path usesHYOPS_VYOS_GCS_SA_JSON. - Apply the build module once.
- Consume the published state contract from the Proxmox and Hetzner seed modules.
Cleanup after failed build attempts¶
After the good image is verified, clean up obsolete artifacts deliberately.
Safe cleanup targets:
- superseded Hetzner snapshots that are no longer referenced by current state
- temporary local staging such as
/tmp/hyops-hetzner-seed - failed builder scratch disks or wrapper outputs
- stale Proxmox smoke artifacts from failed pre-fix runs:
smoke-*test VMs/var/lib/vz/snippets/hyops-vyos-smoke-*/var/tmp/hyops-vyos-smoke-mnt-*
Before deleting anything, confirm the active state points at the current image/template:
jq '.outputs.image_ref,.outputs.image_description' \
"$HOME/.hybridops/envs/dev/state/modules/core__hetzner__vyos-image-seed/latest.json"
jq '.outputs.template_vm_id,.outputs.template_name' \
"$HOME/.hybridops/envs/dev/state/modules/core__onprem__vyos-template-seed/latest.json"
Notes¶
- prefer a pinned, mirrored artifact URL over upstream
latest - do not publish mutable URLs into the shared contract
gs://...is not directly usable by Hetzner rescue; the HyOps wrapper downloads it first on the execution host and then exposes a temporary public raw image URL for Hetzner import- keep the shared build module as the default story; treat manual artifact registration as the exception