Create a UDS Package
What you’ll accomplish
Section titled “What you’ll accomplish”You’ll take an existing Helm chart and package it as a UDS Package, complete with network policies, SSO integration, and monitoring, ready to deploy on UDS Core. This guide uses the UDS Package Template as the starting point, which uses a standard format for UDS Packages.
All examples reference the Reference Package, a working UDS Package that demonstrates every integration point covered here.
Prerequisites
Section titled “Prerequisites”- UDS CLI installed
- Docker Desktop or Lima (for local k3d cluster creation via
uds run default) - The Helm chart you want to package (repository URL, chart name, and version)
- Familiarity with Helm values and Zarf packages
Before you begin
Section titled “Before you begin”A UDS Package wraps a Helm chart with platform integration (networking, SSO, monitoring, and security policies) declared through the UDS Package custom resource. The UDS Operator watches for this CR and automatically provisions Istio ingress, Keycloak clients, Prometheus monitors, Istio Authorization policies, network policies, etc…
The template repository provides the standard directory structure:
| File / Directory | Purpose |
|---|---|
bundle/ | Dev/test bundle for local development and CI |
chart/ | Helm chart containing the UDS Package CR and integration templates |
common/ | Base zarf.yaml shared across all flavors |
tasks/ | Package-specific task definitions included by tasks.yaml |
tests/ | Integration tests (Playwright, Jest, or custom scripts) |
values/ | Helm values files: common-values.yaml for shared config, <flavor>-values.yaml per flavor |
tasks.yaml | Root UDS Runner task file, entry point for uds run commands |
zarf.yaml | Root package definition: metadata, flavors, images, and variable declarations |
-
Clone the template repository
Clone the template locally:
Terminal window git clone https://github.com/uds-packages/template.gitFind & Replace all template placeholders throughout the repository. These are the values you’ll substitute:
Placeholder Replace with Example #TEMPLATE_APPLICATION_NAME#Lowercase app identifier (used in filenames, namespaces, resource names) reference-package#TEMPLATE_APPLICATION_DISPLAY_NAME#Human-readable name Reference Package#TEMPLATE_CHART_REPO#Helm chart OCI or HTTPS repository URL oci://ghcr.io/uds-packages/reference-package/helm/reference-package#UDS_PACKAGE_REPO#Your package’s GitHub repository URL https://github.com/uds-packages/reference-packageUpdate
CODEOWNERSfollowing the guidance inCODEOWNERS-template.md, then removeCODEOWNERS-template.md. -
Configure the common Zarf package definition
The
common/zarf.yamldefines what’s shared across all flavors: the config chart, the upstream Helm chart reference, and shared values. Update it to point to your application’s upstream chart:common/zarf.yaml kind: ZarfPackageConfigmetadata:name: reference-package-commondescription: "UDS Reference Package Common Package"components:- name: reference-packagerequired: truecharts:- name: uds-reference-package-confignamespace: reference-packageversion: 0.1.0localPath: ../chart- name: reference-packagenamespace: reference-packageversion: 0.1.0url: oci://ghcr.io/uds-packages/reference-package/helm/reference-package # upstream application helm chartvaluesFiles:- ../values/common-values.yaml -
Configure the root Zarf package definition
The root
zarf.yamldefines package metadata and per-flavor components. Each flavor imports fromcommon/zarf.yamland adds its own values file and container images:The
variablesblock declares Zarf package variables that deployers can set at deploy time viauds-config.yamlor--setflags. They are injected into Helm values using the###ZARF_VAR_<NAME>###syntax; you can see this inchart/values.yamlwheredomain: "###ZARF_VAR_DOMAIN###"picks up the deployer-supplied domain at deploy time. Usesensitive: trueon variables that contain secrets so their values are never logged. See the Zarf variables reference for all available options.zarf.yaml kind: ZarfPackageConfigmetadata:name: reference-packagedescription: "UDS Reference Package package"version: "dev"variables:- name: DOMAINdefault: "uds.dev"components:- name: reference-packagerequired: truedescription: "Deploy Upstream Reference Package"import:path: commononly:flavor: upstreamcharts:- name: reference-packagevaluesFiles:- values/upstream-values.yamlimages:- ghcr.io/uds-packages/reference-package/container/reference-package:v0.1.0The
imageslist must include every container image the application needs. Zarf pulls these images during package creation and pushes them to the in-cluster registry during deployment. -
Update the flavor values
Create
values/upstream-values.yamlfor flavor-specific overrides (primarily image references). The structure here must match your upstream chart’svalues.yaml; check the chart’s documentation or inspect itsvalues.yamlto find the correct keys for the image repository, tag, and pull policy:values/upstream-values.yaml image:repository: ghcr.io/uds-packages/reference-package/container/reference-packagetag: v0.1.0pullPolicy: Always -
Define the UDS
PackageCRThe
PackageCR inchart/templates/uds-package.yamltells the UDS Operator what your application needs from the platform. Configure the three main integration sections:Networking: expose services through Istio gateways and declare allowed traffic.
The
exposeblock creates an Istio VirtualService that routes external traffic through a gateway to your service. Theselectormust match the labels on your application’s pods; if it doesn’t, traffic won’t reach the right pods. Thehostbecomes the subdomain (e.g.,reference-package.uds.dev). See Expose Apps on Gateways for detailed configuration options.chart/templates/uds-package.yaml apiVersion: uds.dev/v1alpha1kind: Packagemetadata:name: reference-packagenamespace: {{ .Release.Namespace }}spec:network:serviceMesh:mode: ambientexpose:- service: reference-packageselector:app: reference-package # must match your pod labelsgateway: tenanthost: reference-packageport: 8080uptime:checks:paths:- "/" # e2e uptime monitoring metrics for this path on your appThe
allowblock creates NetworkPolicies following the principle of least privilege. Only permit traffic your application actually needs:chart/templates/uds-package.yaml (continued) allow:- direction: IngressremoteGenerated: IntraNamespace- direction: EgressremoteGenerated: IntraNamespace- direction: Egressselector:app: reference-package{{- if .Values.postgres.internal }}remoteNamespace: {{ .Values.postgres.namespace | quote }}remoteSelector:{{ .Values.postgres.selector | toYaml | nindent 10 }}port: {{ .Values.postgres.port }}{{- else }}remoteGenerated: Anywhere{{- end }}description: "Reference Package Postgres"- direction: EgressremoteNamespace: keycloakremoteSelector:app.kubernetes.io/name: keycloakselector:app: reference-packageport: 8080description: "SSO Internal"- direction: EgressremoteNamespace: istio-tenant-gatewayremoteSelector:app: tenant-ingressgatewayselector:app: reference-packageport: 443description: "SSO External"# Custom rules for unanticipated scenarios{{- with .Values.additionalNetworkAllow }}{{ toYaml . | nindent 6 }}{{- end }}The reference package declares exactly what it needs:
- Intra-namespace traffic for pod-to-pod communication
- Egress to the PostgreSQL database (templated for internal vs. external)
- Egress to Keycloak for SSO token validation (both internal service and external gateway)
- An escape hatch (
additionalNetworkAllow) for deployers to add custom rules via bundle overrides
SSO: register a Keycloak client if your app has a user login. If your application has no native OIDC/SSO support, Authservice is available as an alternative.
chart/templates/uds-package.yaml (continued) {{- if .Values.sso.enabled }}sso:- name: Reference Package Loginprotocol: openid-connectclientId: uds-reference-packagesecretName: {{ .Values.sso.secretName }}redirectUris:- "https://reference-package.{{ .Values.domain }}/callback"- "https://reference-package.{{ .Values.domain }}"secretTemplate:KEYCLOAK_URL: "https://sso.{{ .Values.domain }}/realms/uds"KEYCLOAK_CLIENT_ID: "clientField(clientId)"KEYCLOAK_CLIENT_SECRET: "clientField(secret)"APP_CALLBACK_URL: "https://reference-package.{{ .Values.domain }}/callback"{{- end }}The
secretTemplategenerates a Kubernetes secret with the exact fields your application expects for its SSO configuration. The keys and values vary by application; check your upstream chart’s documentation orvalues.yamlfor the environment variables it uses to configure its OIDC/Keycloak connection.Monitoring: declare metrics endpoints for Prometheus to scrape, if your app supports metrics. See Capture Application Metrics for more detail.
chart/templates/uds-package.yaml (continued) monitor:- selector:app: reference-packagetargetPort: 8080portName: httppath: /metricskind: ServiceMonitordescription: Metrics scraping for Reference Package -
Configure the chart values
The config chart’s
chart/values.yamldefines the inputs consumed by yourPackageCR templates. Bundle deployers can override them viaoverridesinuds-bundle.yaml:chart/values.yaml domain: "###ZARF_VAR_DOMAIN###"sso:enabled: truesecretName: reference-package-ssopostgres:username: "reference"password: ""existingSecret:name: "reference-package.reference-package.pg-cluster.credentials.postgresql.acid.zalan.do"passwordKey: passwordusernameKey: usernamehost: "pg-cluster.postgres.svc.cluster.local"dbName: "reference"connectionOptions: "?sslmode=disable"internal: trueselector:cluster-name: pg-clusternamespace: postgresport: 5432additionalNetworkAllow: []monitoring:enabled: truevalues/common-values.yamlcontains Helm values passed to the upstream application chart across all flavors. Use it for security hardening and shared defaults that every deployment should have. Use bundleoverridesfor anything deployment-specific:values/common-values.yaml # Pod-level securitypodSecurityContext:runAsUser: 1000runAsGroup: 1000fsGroup: 1000# Container-level securitysecurityContext:capabilities:drop:- ALLreadOnlyRootFilesystem: truerunAsNonRoot: trueallowPrivilegeEscalation: false -
Set up the dev/test bundle
A UDS Bundle composes multiple Zarf packages into a single deployable unit. The dev bundle in
bundle/uds-bundle.yamlwires your package together with its dependencies (like a database) so you can develop and test locally without needing a full environment. It also serves as the bundle used in CI to validate your package end-to-end.The reference package includes a PostgreSQL operator as a dependency:
bundle/uds-bundle.yaml kind: UDSBundlemetadata:name: reference-package-testdescription: A UDS bundle for deploying Reference Package and its dependencies on a development clusterversion: devpackages:- name: postgres-operatorrepository: ghcr.io/uds-packages/postgres-operatorref: 1.14.0-uds.13-upstreamoverrides:postgres-operator:uds-postgres-config:values:- path: postgresqlvalue:enabled: trueteamId: "uds"volume:size: "10Gi"numberOfInstances: 2users:reference-package.reference-package: []databases:reference: reference-package.reference-packageversion: "15"ingress:- remoteNamespace: reference-package- name: reference-packagepath: ../ref: devoverrides:reference-package:reference-package:values:- path: databasevalue:secretName: "reference-package-postgres"secretKey: "PASSWORD"- path: ssovalue:enabled: truesecretName: reference-package-sso- path: monitoringvalue:enabled: trueThe bundle uses
overridesto wire up dependencies: connecting the database secret, enabling SSO, and enabling monitoring. This is how deployers configure packages without modifying the package itself. -
Build and deploy your package
The template ships with a UDS Runner task file that handles the full workflow. Use these tasks rather than running Zarf and UDS commands manually:
Terminal window # Spin up a local k3d cluster, build, deployuds run default# Iterate on an existing cluster (skips cluster & SBOM creation, faster inner loop)uds run dev
Verification
Section titled “Verification”Confirm the UDS Operator processed your Package CR:
uds zarf tools kubectl get package -n reference-packageYou can also monitor resource status interactively with K9s or uds zarf tools monitor.
NAME STATUS SSO CLIENTS ENDPOINTS MONITORS NETWORK POLICIES AGEreference-package Ready ["uds-reference-package"] ["reference-package.uds.dev"] ["reference-package-..."] 7 2mReady confirms all platform integrations were provisioned. Then verify the individual resources:
# Verify network policies were createduds zarf tools kubectl get networkpolicies -n reference-package
# Verify the VirtualService was created for ingress routinguds zarf tools kubectl get virtualservices -n reference-package
# Verify the service is accessible through the gatewaycurl -sI https://reference-package.uds.dev | head -1
# Verify monitors were createduds zarf tools kubectl get servicemonitors,podmonitors -n reference-packageFor web applications, you can also navigate directly to https://reference-package.uds.dev in your browser to verify the application is accessible and SSO login works.
Troubleshooting
Section titled “Troubleshooting”Problem: Pepr policy violations blocking deployment
Section titled “Problem: Pepr policy violations blocking deployment”Symptom: Pods fail to start and namespace events show admission webhook denials:
uds zarf tools kubectl get events -n <namespace>LAST SEEN TYPE REASON OBJECT MESSAGE8m26s Warning FailedCreate replicaset/reference-package-674cc4c88b Error creating: admission webhook "pepr-uds-core.pepr.dev" denied the request: Pod level securityContext does not meet the non-root user requirement.You can also watch for violations in real time using uds monitor pepr denied.
Solution: Update the security context in your values file so the pod runs as non-root:
podSecurityContext: runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000
securityContext: capabilities: drop: - ALL readOnlyRootFilesystem: true runAsNonRoot: true allowPrivilegeEscalation: falseFor more guidance on diagnosing and resolving policy violations, see the Policy Violations runbook.
Related documentation
Section titled “Related documentation”PackageCR Reference- Define Network Access
- Identity & Authorization
- Bundles
- UDS Package Requirements
- Package testing - Set up journey and upgrade tests for your package.