Package charts and manage dependencies
HelmSharp supports the traditional HTTP chart-repository workflow in managed code: package a chart, produce index.yaml, manage isolated repository state, pull an archive, and resolve dependencies. It does not require the Helm CLI at runtime.
Scope of this guide
OCI authentication and push/pull parity, provenance files, signing, and signature verification are not part of this workflow. See Compatibility before building a production repository service around those capabilities.
Package a chart
Use the request overload when the build needs metadata overrides or a dependency refresh:
public static Task<CommandResult> PackageChartAsync(
IHelmClient client,
string chartPath,
string outputDirectory,
CancellationToken cancellationToken)
=> client.PackageAsync(new HelmPackageRequest
{
ChartPath = chartPath,
Destination = outputDirectory,
Version = "1.3.2",
AppVersion = "2026.07",
DependencyUpdate = true
}, cancellationToken);Version and AppVersion change the packaged Chart.yaml, not the source file. The archive is named <chart-name>-<version>.tgz, has one chart root, includes nested charts and CRDs, and skips symbolic links. .helmignore supports file, directory, *, ?, character-class, rooted, and ! negation patterns; ** is rejected explicitly.
Produce repository metadata
Place the chart archives in a directory and create its index:
public static Task<CommandResult> GenerateRepositoryIndexAsync(
IHelmClient client,
string packageDirectory,
CancellationToken cancellationToken)
=> client.RepoIndexAsync(new HelmRepoIndexRequest
{
DirectoryPath = packageDirectory,
Url = "https://charts.example.com/stable",
MergeIndexPath = Path.Combine(packageDirectory, "previous-index.yaml"),
OutputPath = Path.Combine(packageDirectory, "index.yaml"),
FailOnInvalidPackage = true
}, cancellationToken);Url is the package base URL. MergeIndexPath retains historical entries that are no longer in the directory. Set FailOnInvalidPackage when an invalid archive must stop a publish; otherwise inspect diagnostics for skipped packages. OutputPath defaults to index.yaml under DirectoryPath.
Isolate repository state in a service
Do not let tenants, tests, or concurrent jobs share repository config and cache directories.
using var repository = new HelmChartRepository(new HelmRepositoryOptions
{
RepositoryConfigPath = Path.Combine(tenantRoot, "repositories.yaml"),
CacheDirectory = Path.Combine(tenantRoot, "cache")
});The repository config stores definitions; cached indexes use <repository-name>-index.yaml. If paths are omitted, Helm-compatible environment variables and platform defaults are used. The keyword-only SearchRepoAsync overload searches those configured caches without a network request. The overload that accepts a repository URL fetches and caches that repository index before searching.
Pull a chart safely
public static async Task<string> PullTraditionalChartAsync(
string repositoryConfigPath,
string repositoryCacheDirectory,
string outputDirectory,
CancellationToken cancellationToken)
{
using var repository = new HelmChartRepository(new HelmRepositoryOptions
{
RepositoryConfigPath = repositoryConfigPath,
CacheDirectory = repositoryCacheDirectory
});
await repository.AddRepositoryAsync(
"stable",
"https://charts.example.com/stable",
cancellationToken: cancellationToken);
var configured = (await repository.ListRepositoriesAsync(cancellationToken))
.Single(item => item.Name == "stable");
await repository.FetchRepoIndexAsync(configured, cancellationToken);
return await repository.PullChartAsync(new HelmPullRequest
{
ChartReference = "stable/app",
Version = "~1.3.2",
Destination = outputDirectory,
Untar = true,
UntarDirectory = Path.Combine(outputDirectory, "expanded"),
VerifyDigest = true
}, cancellationToken);
}The pull request accepts repo/chart, a chart name plus RepositoryUrl, or a direct https://…tgz URL. The downloaded archive is stored under Destination. When Untar is enabled, UntarDirectory selects the extraction root; otherwise Destination is the extraction root. Extraction rejects entries that escape the selected root. Credentials stay on the repository origin by default. Enable PassCredentialsAll only when a trusted repository intentionally redirects archives to another authenticated origin.
Make dependency builds reproducible
Declare aliases and local references in Chart.yaml as usual:
dependencies:
- name: redis
alias: cache
version: ~18.0.0
repository: "@stable"
- name: shared-templates
version: 1.2.3
repository: file://../shared-templatesAn alias changes both the subchart identity and its values key, so the first dependency receives values under cache:, not redis:. DependencyUpdateAsync resolves constraints, refreshes dependencies, and writes Chart.lock. DependencyBuildAsync is the CI path: it verifies the lock against Chart.yaml, restores the exact locked versions, and does not rewrite the lock.
public static Task<CommandResult> UpdateDependenciesAsync(
IHelmClient client,
string chartPath,
string repositoryConfigPath,
string repositoryCacheDirectory,
CancellationToken cancellationToken)
=> client.DependencyUpdateAsync(new HelmDependencyUpdateRequest
{
ChartPath = chartPath,
RepositoryConfigPath = repositoryConfigPath,
RepositoryCachePath = repositoryCacheDirectory,
SkipRepositoryRefresh = false
}, cancellationToken);public static Task<CommandResult> BuildDependenciesAsync(
IHelmClient client,
string chartPath,
string repositoryConfigPath,
string repositoryCacheDirectory,
CancellationToken cancellationToken)
=> client.DependencyBuildAsync(new HelmDependencyBuildRequest
{
ChartPath = chartPath,
RepositoryConfigPath = repositoryConfigPath,
RepositoryCachePath = repositoryCacheDirectory,
VerifyDigests = true
}, cancellationToken);Run DependencyListAsync before packaging when you need to surface missing, wrong-version, unpacked, or inconsistent dependencies to a user. High-level client methods return CommandResult; lower-level repository methods throw exceptions. Troubleshoot failures explains how to preserve both kinds of diagnostics.