Skip to content
Unified Defense StackUnified Defense Stack

UDS CLI Next Mode

UDS CLI Next is the next-generation implementation of UDS CLI. It is available as an alpha preview behind the NextMode feature flag while Legacy mode remains the default.

UDS CLI Next is designed to support the next phase of UDS bundle workflows:

  • Better Bundles design
    • next-generation bundle authoring and artifact structure
    • self-contained OCI artifacts for registry-backed and offline workflows
  • HCL-based bundle definitions with bundle.uds.hcl
    • reusable locals for shared registries, versions, and package names
    • package dependency ordering with depends_on, allowing deployers to run independent packages in parallel for faster deploys
    • HCL functions that can be extended by UDS CLI, including the supported file() function for reading local file contents into bundle, defaults, and config values
    • clearer validation for required fields, invalid attributes, and invalid types
  • Zarf package values as a first-class citizen instead of bespoke bundle-level Helm overrides
  • a library-first design that other UDS tools can consume directly
  • stronger artifact integrity, signing, and verification paths

UDS CLI Next implements the Better Bundles design for the next generation of UDS bundles. The previous bundle design fell short as UDS workflows matured: bundle configuration relied on bespoke overrides, package contents were hidden behind indirect OCI blob references, bundles did not have a distinct media type for registries to identify, and the artifact shape was harder for downstream tools to parse consistently.

Better Bundles focuses on making bundle artifacts easier for humans, registries, and downstream deployment tools to reason about. Beyond the HCL authoring model above, it defines:

  • a bundle-specific OCI media type so registries and tooling can identify UDS bundles directly
  • an artifact layout where package manifests and layers are represented explicitly instead of hidden behind generic blob indirection
  • one bundle artifact that can contain OCI-hosted and local Zarf packages in a consistent internal OCI layout
  • build-time configuration that is sealed into the artifact, plus deploy-time variables for environment-specific values
  • an artifact shape that supports low-side build, high-side configuration, and high-side deployment workflows

When a bundle is pushed to an OCI registry, the tag resolves to a root OCI index. The root index selects the requested architecture and points at a child index for that architecture. Each child index is the canonical single-architecture bundle artifact and directly references the bundle definition plus the included Zarf package content:

OCI registry layout
oci://registry.example.com/my-org/my-app:1.0.0
└── root OCI index
├── linux/amd64 child index
│ └── artifactType: application/vnd.defenseunicorns.uds.bundle.v1
│ ├── bundle definition manifest
│ │ ├── bundle.uds.hcl
│ │ ├── defaults.uds.hcl
│ │ ├── values/package_id/0.yaml
│ │ ├── oci/oci-layout
│ │ └── oci/index.json
│ ├── Zarf package manifest: package-a
│ │ ├── package config blob
│ │ └── package layer blobs
│ └── Zarf package manifest: package-b
│ ├── package config blob
│ └── package layer blobs
└── linux/arm64 child index
└── artifactType: application/vnd.defenseunicorns.uds.bundle.v1
├── bundle definition manifest
├── Zarf package manifest: package-a
└── Zarf package manifest: package-b

This layout makes package content explicit and inspectable while keeping the bundle self-contained for registry-backed and offline workflows. Tags can point at multiple architecture-specific child indexes, while digest-pinned references can address a single child index directly. Registry-backed packages and local Zarf packages are normalized into the same internal OCI layout, so downstream tooling can consume one consistent artifact shape.

ModeStatusNotes
LegacyDefaultExisting uds-bundle.yaml workflows continue to work.
NextAlpha previewEnabled with NextMode; command coverage is still expanding.

Next mode is planned to become the default in beta. Legacy mode will be removed after the beta migration window.

Next mode currently supports the core bundle flows needed to exercise HCL bundles and artifact-based deployment:

CapabilityCurrent support
Bundle definition formatbundle.uds.hcl using the Next HCL bundle schema
Development deploymentDeploy directly from a bundle definition with uds bundle dev deploy
Bundle artifact creationCreate local bundle artifacts with uds bundle create
Bundle artifact deploymentDeploy created local artifacts or OCI references with uds bundle deploy
Package verificationVerify package signatures when configured by the bundle definition
Bundle signingCreate signed bundles when signing options are configured, or unsigned bundles for local alpha workflows
Bundle signature verificationVerify signed bundle artifacts during deploy, with an explicit skip flag available for unsigned local artifacts
Retained Legacy commandsNon-bundle commands that are still needed during migration remain available in Next mode

Next mode can be enabled per command with the NextMode feature flag:

Terminal window
uds --features=NextMode=true version
CLI_FEATURES=NextMode=true uds version

All UDS CLI Next bundle definition files must be named bundle.uds.hcl.

This quickstart creates a local k3d cluster with the UDS k3d package, initializes Zarf, and deploys podinfo.

Prerequisites: Docker and k3d must be installed and running.

Create bundle.uds.hcl:

bundle.uds.hcl
uds {
bundle_api_version = "uds.dev/v1alpha1"
}
metadata {
name = "next-quickstart"
description = "Next mode quickstart bundle"
version = "0.1.0"
}
package "uds_k3d_dev" {
source = "oci://ghcr.io/defenseunicorns/packages/uds-k3d:0.20.2"
signature_verification { verify = false }
}
package "init" {
source = "oci://ghcr.io/zarf-dev/packages/init:v0.83.0"
signature_verification {
keyless {
certificate_identity_regexp = "https://github\\.com/zarf-dev/zarf/\\.github/workflows/release\\.yml@refs/tags/v\\d+\\.\\d+\\.\\d+"
certificate_oidc_issuer = "https://token.actions.githubusercontent.com"
}
}
depends_on = [package.uds_k3d_dev]
}
package "podinfo" {
source = "oci://ghcr.io/defenseunicorns/uds-cli/podinfo:0.0.2"
signature_verification { verify = false }
depends_on = [package.init]
}

Deploy directly from the bundle definition during development:

Terminal window
CLI_FEATURES=NextMode=true uds bundle dev deploy .

Or create an unsigned bundle artifact and deploy it:

Terminal window
CLI_FEATURES=NextMode=true uds bundle create --unsigned .
CLI_FEATURES=NextMode=true uds bundle deploy ./uds-bundle-next-quickstart-<ARCH>-0.1.0.tar.zst --skip-signature-verification

Replace ARCH with the bundle architecture, such as amd64 or arm64.

The --unsigned flag is required here because this quickstart does not configure bundle signing. Deploying the unsigned artifact requires --skip-signature-verification.

bundle.uds.hcl
uds {
bundle_api_version = "uds.dev/v1alpha1" # Required
}
metadata {
name = "my-bundle" # Required
description = "Description" # Optional
version = "1.0.0" # Optional
}
package "package_id" {
source = "oci://registry/package:tag" # Required
namespace = "custom-namespace" # Optional
depends_on = [package.other_package] # Optional
values_files = ["values/config.yaml"] # Optional
optional_components = ["component-name"] # Optional
signature_verification { # Required
verify = false # Explicit bypass
}
}

Package membership is implicit. Every package "<id>" block in bundle.uds.hcl is part of the bundle.

Every package passed to uds bundle create must declare its verification posture. Configure exactly one public_key or keyless verification method, or explicitly bypass verification with verify = false. A bypass creates the bundle with a prominent warning.

Keyless verification requires one certificate identity constraint and one OIDC issuer constraint. Exact and regular-expression variants are mutually exclusive. trusted_root is optional. When it is omitted, verification uses Zarf’s embedded Sigstore trusted root.

bundle.uds.hcl
signature_verification {
keyless {
# Choose exactly one certificate identity constraint:
certificate_identity = "https://github.com/my-org/api/.github/workflows/release.yml@refs/heads/main"
# certificate_identity_regexp = "https://github.com/my-org/.*/.github/workflows/release.yml@refs/heads/main"
# Choose exactly one OIDC issuer constraint:
certificate_oidc_issuer = "https://token.actions.githubusercontent.com"
# certificate_oidc_issuer_regexp = "https://.*token.actions.githubusercontent.com"
# Optional Sigstore trusted-root JSON. If omitted, use the embedded public Sigstore root.
# trusted_root = file("keys/trusted-root.json")
# Transparency-log and SCT checks are enabled by default. These settings
# are available for explicitly opting into reduced verification protections.
# insecure_ignore_tlog = true
# insecure_ignore_sct = true
# Enable RFC 3161 signed-timestamp verification when applicable.
# use_signed_timestamps = true
}
}

Transparency-log and SCT verification are enabled by default. Enable insecure_ignore_tlog or insecure_ignore_sct only when reduced verification protection is intentional.

Direct development deploys from a bundle directory or bundle.uds.hcl attempt the configured package verification and warn instead of stopping when the same policy would fail uds bundle create. Deploying an already-built bundle artifact does not reverify its contained packages.

Use locals to reduce duplication:

bundle.uds.hcl
locals {
repo = "ghcr.io/my-org"
version = "1.0.0"
}
package "app" {
source = "oci://${local.repo}/my-app:${local.version}"
signature_verification { verify = false }
}

Use file(path) to read the contents of a regular UTF-8 text file into an HCL expression. It is available in bundle.uds.hcl, defaults.uds.hcl, and config.uds.hcl. Relative paths are resolved from the directory containing the HCL file.

bundle.uds.hcl
locals {
description = file("metadata/description.txt")
}
metadata {
name = "my-bundle"
description = local.description
}

The function can also be used directly in variable values and templates:

config.uds.hcl
variables = {
tls_cert = file("my_tls_cert_file")
}

When uds bundle create and uds bundle reconfigure build an artifact, they replace file() expressions in bundle.uds.hcl and defaults.uds.hcl with their evaluated values. File paths are evaluated relative to the HCL file itself. Any file() calls in config.uds.hcl are evaluated when the config is read.

CommandDescription
uds bundle create [directory]Create a bundle artifact from a directory containing bundle.uds.hcl.
uds bundle inspect <bundle-reference>Display metadata and packages from a local .tar.zst or OCI bundle artifact.
uds bundle push <bundle-tarball> <oci-reference>Push a bundle artifact to an OCI registry.
uds bundle pull <ref>Pull a bundle artifact from an OCI registry.
uds bundle dev deploy [path]Deploy directly from a bundle definition or its directory. Defaults to the current directory.
uds bundle deploy <artifact>Deploy a created local .tar.zst or OCI bundle artifact.
uds bundle remove [bundle-definition]Remove a bundle from Kubernetes. Defaults to the current directory.
uds bundle sign <bundle-artifact>Sign a created bundle artifact.
uds bundle verify <bundle-artifact>Verify a created bundle artifact signature.
uds bundle reconfigure <source> --defaults <defaults-file>Reconfigure bundle defaults from a local artifact or OCI reference and produce a derivative artifact.

These flags apply to all uds bundle subcommands:

FlagDefaultDescription
-a, --architecturehost archTarget CPU architecture, such as amd64 or arm64.
-o, --outputtextOutput format: text, json, or yaml.
--plain-httpfalseAllow plain HTTP for a registry only when HTTPS is unavailable.
--skip-tls-verifyfalseSkip TLS certificate verification.
--tmp-dirsystem tempDirectory for temporary working files.
--concurrency10Degree of parallelism for concurrent operations. For deploy, applies within a dependency level.
--confignonePath to config.uds.hcl for deploy-time variables and options.
--promptfalseEnable interactive confirmation before package deployment or removal begins.

Both deploy commands are non-interactive by default and deploy packages within a dependency level in parallel. Levels run sequentially: level N must finish before level N+1 starts.

Use uds bundle dev deploy to deploy directly from a bundle definition while authoring a bundle. It does not create an intermediate artifact, so bundle provenance and bundle-signature verification are unavailable.

Terminal window
# Deploy from the bundle definition in the current directory
CLI_FEATURES=NextMode=true uds bundle dev deploy
# Deploy from a bundle definition in a specific directory
CLI_FEATURES=NextMode=true uds bundle dev deploy ./my-bundle
# Deploy with an interactive confirmation prompt
CLI_FEATURES=NextMode=true uds bundle dev deploy ./my-bundle --prompt
# Deploy serially without a prompt
CLI_FEATURES=NextMode=true uds bundle dev deploy ./my-bundle --concurrency 1
# Deploy with deploy-time configuration
CLI_FEATURES=NextMode=true uds bundle dev deploy ./my-bundle --config config.uds.hcl
# Deploy with custom package-registry settings
CLI_FEATURES=NextMode=true uds bundle dev deploy ./my-bundle --architecture amd64 --plain-http --concurrency 5
# Deploy only a subset of packages
CLI_FEATURES=NextMode=true uds bundle dev deploy ./my-bundle --packages nginx,podinfo
# Deploy a package out of dependency order. Use with caution.
CLI_FEATURES=NextMode=true uds bundle dev deploy ./my-bundle --packages podinfo --force

Use uds bundle deploy to deploy a created artifact.

Terminal window
# Deploy a local unsigned artifact
CLI_FEATURES=NextMode=true uds bundle deploy uds-bundle-my-app-amd64-1.0.0.tar.zst --skip-signature-verification
# Pull and deploy a tagged unsigned OCI artifact
CLI_FEATURES=NextMode=true uds bundle deploy oci://registry.example.com/my-org/my-app:1.0.0 --skip-signature-verification
# Deploy a digest-pinned unsigned OCI artifact
CLI_FEATURES=NextMode=true uds bundle deploy oci://registry.example.com/my-org/my-app@sha256:<digest> --skip-signature-verification

Deploy-specific flags:

FlagDefaultDescription
--promptfalseEnable interactive confirmation before package deployment begins.
-p, --packagesallComma-separated list of specific packages to deploy. When a selected package’s dependencies are not also selected, the deploy is rejected.
-f, --forcefalseDeploy packages even if their dependencies are not selected. Use with caution.
Terminal window
# Remove all packages in current directory bundle
CLI_FEATURES=NextMode=true uds bundle remove
# Remove bundle from a specific directory
CLI_FEATURES=NextMode=true uds bundle remove ./my-bundle
# Remove only specific packages
CLI_FEATURES=NextMode=true uds bundle remove --packages uds_k3d_dev,init
# Remove with an interactive confirmation prompt
CLI_FEATURES=NextMode=true uds bundle remove --prompt

Packages are removed in reverse order to respect dependency ordering. Packages not currently deployed on the cluster are skipped with a warning.

Remove-specific flags:

FlagDefaultDescription
-p, --packagesallComma-separated list of specific packages to remove.
-f, --forcefalseRemove packages even if other bundle packages depend on them.

A config.uds.hcl file lets you provide variables for Helm value templating and Zarf package variable substitution without modifying the bundle itself. Pass it with --config:

Terminal window
CLI_FEATURES=NextMode=true uds bundle dev deploy --config ./config.uds.hcl

Example config.uds.hcl:

config.uds.hcl
variables = {
cluster_name = "my-production-cluster"
replica_count = 3
enable_metrics = true
}

Variables are available in values_files through Go template syntax:

values/app.yaml
clusterName: {{ .vars.cluster_name }}
replicaCount: {{ .vars.replica_count }}

Top-level scalar variables are also passed to Zarf as ###ZARF_PKG_VAR_<NAME>### substitutions. Variable names are automatically uppercased.

A defaults.uds.hcl file placed alongside bundle.uds.hcl provides the lowest-priority layer of variable configuration. It is auto-discovered when a bundle is created. No flag is required. When present in the same directory, uds bundle create adds defaults.uds.hcl to the bundle artifact so those defaults travel with the artifact.

At deploy time, defaults stored in the artifact are applied first, then overridden by values in config.uds.hcl or future variable sources.

Example defaults.uds.hcl:

defaults.uds.hcl
variables = {
cluster_name = "my-default-cluster"
replica_count = 1
enable_metrics = false
}

Use uds bundle reconfigure to replace the defaults in an existing bundle artifact and produce a new derivative artifact. This is useful when the package content stays the same, but environment-specific defaults need to change before publishing or deploying.

Reconfigure is efficient because it updates bundle defaults without rebuilding package content. The derivative artifact can reuse the existing package blobs and only change the bundle metadata/defaults layers that need to change.

Reconfigure requires a source bundle artifact or OCI reference and a new defaults.uds.hcl file:

Terminal window
# Create an unsigned derivative artifact from a local unsigned bundle.
# Efficient: package content is reused; only the defaults-bearing bundle layers change.
CLI_FEATURES=NextMode=true uds bundle reconfigure ./uds-bundle-my-app-amd64-1.0.0.tar.zst \
--defaults ./defaults.prod.uds.hcl \
--skip-signature-verification \
--unsigned
# Reconfigure from an unsigned OCI bundle reference.
# Efficient: use an existing published artifact as the source for a new environment-specific derivative.
# OCI reconfigure publishes a derivative tag based on --suffix, so --output-dir is not used.
CLI_FEATURES=NextMode=true uds bundle reconfigure oci://registry.example.com/my-org/my-app:1.0.0 \
--defaults ./defaults.prod.uds.hcl \
--skip-signature-verification \
--suffix -prod \
--unsigned

By default, reconfigure verifies the source bundle signature before creating the derivative artifact. Use --skip-signature-verification only when intentionally working with an unsigned or untrusted artifact. The new artifact must also be signed, or created with --unsigned for local alpha workflows.

Reconfigure-specific flags:

FlagDefaultDescription
--defaultsrequiredPath to the replacement defaults.uds.hcl file.
--output-dircurrent directoryDirectory for the reconfigured local tarball.
--suffix-reconfiguredSuffix for the output artifact name.
--unsignedfalseCreate an unsigned reconfigured bundle.
--skip-signature-verificationfalseSkip verification of the source bundle signature.

Use uds bundle sign to add signature evidence to a created bundle artifact, and uds bundle verify to verify that signature evidence before trusting an artifact.

Terminal window
# Sign with keyless identity
CLI_FEATURES=NextMode=true uds bundle sign ./uds-bundle-my-app-amd64-1.0.0.tar.zst --keyless
# Sign with a local key or KMS URI
CLI_FEATURES=NextMode=true uds bundle sign ./uds-bundle-my-app-amd64-1.0.0.tar.zst --signing-key ./cosign.key
# Verify with a public key
CLI_FEATURES=NextMode=true uds bundle verify ./uds-bundle-my-app-amd64-1.0.0.tar.zst --public-key ./cosign.pub
# Verify with keyless certificate constraints
CLI_FEATURES=NextMode=true uds bundle verify ./uds-bundle-my-app-amd64-1.0.0.tar.zst \
--certificate-identity-regexp 'https://github\.com/my-org/.*/.github/workflows/release\.yml@refs/heads/main' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com'

uds bundle create can also sign during creation when signing flags are provided, or create an unsigned artifact with --unsigned.

Use uds bundle push to publish a local bundle artifact to an OCI registry, and uds bundle pull to retrieve one.

Terminal window
# Publish to a registry
CLI_FEATURES=NextMode=true uds bundle push ./uds-bundle-my-app-amd64-1.0.0.tar.zst oci://registry.example.com/my-org/my-app:1.0.0
# Pull an unsigned artifact to the current directory
CLI_FEATURES=NextMode=true uds bundle pull oci://registry.example.com/my-org/my-app:1.0.0 --skip-signature-verification
# Pull an unsigned artifact to a specific existing directory
mkdir -p ./downloads
CLI_FEATURES=NextMode=true uds bundle pull oci://registry.example.com/my-org/my-app:1.0.0 -d ./downloads --skip-signature-verification

By default, pull verifies the bundle signature. Use --skip-signature-verification only when intentionally pulling an unsigned or untrusted artifact.

The inspect command shows metadata from a built bundle artifact. Provide a local .tar.zst artifact or an OCI bundle reference. Source directories and standalone bundle.uds.hcl files are not accepted.

Terminal window
# Inspect a local bundle artifact
CLI_FEATURES=NextMode=true uds bundle inspect ./my-bundle.tar.zst
# Inspect an OCI bundle artifact
CLI_FEATURES=NextMode=true uds bundle inspect oci://registry.example.com/my-bundle:1.0.0

Package verification status describes verification performed during bundle creation. Inspect output does not replace bundle signature verification for integrity decisions.

Example output:

Name: my-app
Version: 1.0.0
Packages (2)
Name: database
Source: oci://ghcr.io/my-org/packages/postgres:15.0.0
Name: api
Source: oci://ghcr.io/my-org/packages/my-api:2.0.0
DependsOn: database

Bundle commands that emit result objects support structured output through --output or -o, including create, inspect, pull, and reconfigure:

FormatFlagDescription
text-o textHuman-readable output.
json-o jsonJSON for scripting and pipelines.
yaml-o yamlYAML for configuration tooling.
Terminal window
# Pipe inspect output to jq
CLI_FEATURES=NextMode=true uds bundle inspect ./my-bundle.tar.zst -o json | jq '.packages[].name'
# Suppress logs for pure structured output
CLI_FEATURES=NextMode=true uds bundle inspect ./my-bundle.tar.zst -o json 2>/dev/null
# YAML output for tooling
CLI_FEATURES=NextMode=true uds bundle create --unsigned -o yaml

Logs always go to stderr. Structured output always goes to stdout. Use 2>/dev/null to suppress logs when piping stdout.