Skip to content

Conversation

@scottrigby
Copy link
Member

@scottrigby scottrigby commented Dec 14, 2025

Summary

This HIP proposes bringing .helmignore file targeting semantics to full parity with .gitignore syntax and matching rules for Charts v3. The current implementation diverges from .gitignore in critical ways—most notably in rule evaluation order (first-match vs. last-match), negation pattern behavior, and lack of ** recursive glob support.

By scoping this change to Charts v3 (per HIP-0020), existing charts continue to work unchanged while v3 charts opt into consistent, predictable ignore behavior.

Motivation

Users familiar with .gitignore expect the same behavior from .helmignore. A comprehensive search of helm/helm issues reveals 27 directly related issues and 5 pull requests showing recurring user pain around pattern matching, negation, and documentation gaps.

References

helm/helm Issues Directly Addressed

helm/helm Issues (Context/Out of Scope)

helm-www Documentation Issues

Key Changes in This HIP

  • Last-match-wins semantics: All rules evaluated; final matching rule determines outcome
  • Negation behavior: !pattern re-includes files (matching .gitignore)
  • Recursive globs: Full ** support (**/foo, foo/**, a/**/b)
  • Escape sequences: \#, \!, \ supported
  • Charts v3 only: Gated behind apiVersion: v3; Charts v2 unchanged

/## Summary

This HIP proposes bringing `.helmignore` file targeting semantics to full
parity with `.gitignore` syntax and matching rules for Helm Charts v3.

/## Problem

Users expect `.helmignore` to work like `.gitignore`, but the current
implementation has critical divergences:

- Broken negation: Whitelist patterns like `/*` + `!Chart.yaml` fail
  because negation logic is inverted (helm#3622, helm#8688)
- No `**` globs: Recursive patterns are explicitly unsupported
- First-match semantics: Helm stops at first match; Git uses last-match-wins

These issues have persisted for years (PR #12265 stalled since 2023) because
fixing them is a breaking change.

/## Solution

Scope the fix to Charts v3 (per HIP-0020), providing:

- Full `.gitignore` pattern syntax (`**`, `!`, escape sequences)
- Last-match-wins rule evaluation
- Clean opt-in boundary—v2 charts unchanged, v3 charts get correct behavior

/## References

- Depends on: HIP-0020 (Charts v3 Enablement)
- Implementation approach: Use go-git's gitignore (same as FluxCD's .sourceignore)

Signed-off-by: Scott Rigby <scott@r6by.com>
4. **Scope confusion**: Users expect `.helmignore` to only affect `helm package`, but it affects all commands including `template`, `install`, and `upgrade` ([#6075], [helm-www#1171]).

5. **Documentation gaps**: Current docs don't clearly explain limitations or differences from `.gitignore` ([#4638], [helm-www#1312]).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one more problem iirc -- today Helm only respects .helmignore for helm package. It will ignore .helmignore if the chart is unpacked/untarred

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.helmignore definitely ignores files during helm runtime operations too - on both a packed ann unpacked chart - including helm template, install, etc. Here is an unpacked example:

d=$(mktemp -d)
mkdir -p $d/templates $d/files

cat > $d/Chart.yaml <<'EOF'
apiVersion: v2
name: test
version: 0.1.0
EOF

cat > $d/templates/cm.yaml <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Chart.Name }}-cm
data:
{{- (.Files.Glob "files/*").AsConfig | nindent 2 }}
EOF

cat > $d/templates/pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: {{ .Chart.Name }}-pod
spec:
  containers:
  - name: nginx
    image: nginx:alpine
EOF

cat > $d/files/test.conf <<'EOF'
test including .conf file
EOF

cat > $d/files/test.txt <<'EOF'
test including .txt file
EOF

tree $d

echo '\n=== template WITHOUT .helmignore ==='
helm template $d

cat > $d/.helmignore <<'EOF'
*.txt
pod.yaml
EOF

echo '\n=== template WITH .helmignore ==='
helm template $d

echo '\n=== install WITH .helmignore ==='
helm install --dry-run test $d

should return

/var/folders/7j/bxcg16h177zfgfkzdlv8rb240000gn/T/tmp.5QdzSnmxHz
├── Chart.yaml
├── files
│   ├── test.conf
│   └── test.txt
└── templates
    ├── cm.yaml
    └── pod.yaml

3 directories, 5 files

=== template WITHOUT .helmignore ===
---
# Source: test/templates/cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: test-cm
data:
  test.conf: |
    test including .conf file
  test.txt: |
    test including .txt file
---
# Source: test/templates/pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - name: nginx
    image: nginx:alpine

=== template WITH .helmignore ===
---
# Source: test/templates/cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: test-cm
data:
  test.conf: |
    test including .conf file

=== install WITH .helmignore ===
level=WARN msg="--dry-run is deprecated and should be replaced with '--dry-run=client'"
NAME: test
LAST DEPLOYED: Wed Dec 17 22:14:13 2025
NAMESPACE: default
STATUS: pending-install
REVISION: 1
DESCRIPTION: Dry run complete
TEST SUITE: None
HOOKS:
MANIFEST:
---
# Source: test/templates/cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: test-cm
data:
  test.conf: |
    test including .conf file
  • Without .helmignore, both files are globbed into the configmap, and the pod template is rendered.
  • With .helmignore containing *.txt and pod.yaml only the non .txt file is globbed into the configmap, and the pod is not rendered.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, digging up my reference where I think we deemed .helmignore was not being used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I was mistaken: helm/helm#12592 (comment)

User was reporting .helmignore was not being respected during helm package. But this just seems like an outright bug.

@scottrigby scottrigby requested a review from gjenkins8 December 18, 2025 03:24
Co-authored-by: Andrew Block <andy.block@gmail.com>
Signed-off-by: Scott Rigby <scott@r6by.com>
@scottrigby scottrigby changed the title Bring .helmignore to parity with .gitignore for Charts v3 HIP: Bring .helmignore to parity with .gitignore for Charts v3 Dec 18, 2025
@scottrigby scottrigby requested a review from sabre1041 December 18, 2025 16:40
Copy link
Contributor

@banjoh banjoh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

hips/hip-9999.md Outdated

3. **Unexpected first-match behavior**: Git uses "last matching rule wins"; Helm stops at the first match. This breaks patterns that progressively refine what to exclude.

4. **Scope confusion**: Users expect `.helmignore` to only affect `helm package`, but it affects all commands including `template`, `install`, and `upgrade` ([#6075], [helm-www#1171]).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think users would expect .helmignore to affect all operations, no?

Easy example is helm install ./foo works locally, then helm package ./foo; helm install foo.tar.gz will fail, because a required file is not present in the chart (it was excluded by helm package)

Copy link
Member Author

@scottrigby scottrigby Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it should and already does affect all operations. I can see how this bullet as-is could imply otherwise.

I just reopened that linked issue and commented helm/helm-www#1312 (comment). I should probably move this scope confusion bullet to the list of Documentation issues, since this HIP isn't proposing changing any of the behavior discussed in this scope confusion issues - and I think all of those would be addressed with better documentation.

Copy link
Member Author

@scottrigby scottrigby Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I see what you mean - the wording here implied otherwise. I removed the example from this motivation point entirely; the documentation gaps (about syntax, not scope) are sufficient motivation. Thanks for catching this!

hips/hip-9999.md Outdated

3. **Unexpected first-match behavior**: Git uses "last matching rule wins"; Helm stops at the first match. This breaks patterns that progressively refine what to exclude.

4. **Documentation gaps**: Current docs don't clearly explain limitations or differences from `.gitignore` ([#4638], [helm-www#1312]). Users expect `.helmignore` to only affect `helm package`, but it affects all commands including `template`, `install`, and `upgrade` ([#6075], [helm-www#1171]).
Copy link
Member

@gjenkins8 gjenkins8 Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, .helmignore should be used for all operations. Why would we expect/want unpacked charts to work differently to packed charts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Below, in the "Integration points" section, there is the opposite language:

Apply .gitignore-parity matching in all chart file operations:

Copy link
Member Author

@scottrigby scottrigby Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it does and should continue to be used for all operations. Thanks for catching the wording inconsistency. I clarified the Integration Points language to be clear this describes where the new matching logic applies, not a change in scope. .helmignore already affects all these operations; only the matching semantics change.

hips/hip-9999.md Outdated

3. **Independent evolution**: Chart changes are decoupled from Helm version, allowing adequate time for testing and adoption.

This is the appropriate scope because changing `.helmignore` semantics would break charts that depend on current (albeit buggy) behavior.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This is the appropriate scope because changing `.helmignore` semantics would break charts that depend on current (albeit buggy) behavior.
This is the appropriate scope because changing `.helmignore` semantics would break charts that depend on current (albeit surprising) behavior.

nitpick: .helmignore today is working as designed. So I wouldn't say it is "buggy"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right, "buggy" implies implementation defects when this is working as originally designed (just differently than users expect). Changed to "surprising" in both places.

hips/hip-9999.md Outdated
| `?` | Matches any single character except `/` |
| `[a-z]` | Character class matching |
| `**` | Recursive glob (see below) |
| `\ ` | Escaped trailing space (preserved) |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\ escapes any character (not just spaces and exclamation marks (below))

A backslash ("") can be used to escape any character. E.g., "*" matches a literal asterisk (and "\a" matches "a", even though there is no need for escaping there). As with fnmatch(3), a backslash at the end of a pattern is an invalid pattern that never matches.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point - the table was incomplete and would require duplicating gitignore docs to fix. Restructured the HIP to avoid re-documenting gitignore syntax:

  1. Removed the Pattern Types table
  2. Added a "Design Intent" section clarifying that Charts v3 follows gitignore semantics (with link to docs)
  3. Kept focused subsections on key behavioral changes (recursive globs, rule evaluation, negation)
  4. The Backwards Compatibility table shows the complete v2→v3 delta

This makes the HIP cleaner and avoids incomplete enumerations.

hips/hip-9999.md Outdated
Comment on lines 96 to 97
# Exclude everything
/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Exclude everything
/*
# Exclude everything
/**

We should be precise

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The /* pattern is intentional here - it's a Helm-specific version of Example 2 from gitignore docs. Using /** would match recursively, which breaks the whitelist pattern because you can't re-include files whose parent directories were excluded (noted in Negation Behavior section).

Instead I updated the comment to "Exclude all top-level entries" to be more precise with language about what /* matches.

hips/hip-9999.md Outdated

3. **Unexpected first-match behavior**: Git uses "last matching rule wins"; Helm stops at the first match. This breaks patterns that progressively refine what to exclude.

4. **Documentation gaps**: Current docs don't clearly explain limitations or differences from `.gitignore` ([#4638], [helm-www#1312]). Users expect `.helmignore` to only affect `helm package`, but it affects all commands including `template`, `install`, and `upgrade` ([#6075], [helm-www#1171]).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Below, in the "Integration points" section, there is the opposite language:

Apply .gitignore-parity matching in all chart file operations:


Helm has no concept of staging or committing files. This proposal addresses **file targeting syntax and semantics only**—specifically, which files are included/excluded during chart operations. Git's behavior around staged/committed files does not apply.

### Technical Requirements
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to specify that we (presumably) will only support a single .helmignore file (in the root of the chart). Or, we will match git's recusive .gitignore behavior:

Patterns read from a .gitignore file in the same directory as the path, or in any parent directory (up to the top-level of the working tree), with patterns in the higher level files being overridden by those in lower level files down to the directory containing the file. These patterns match relative to the location of the .gitignore file.

(and iirc, there is text which specifys/assumes .helmignore is in the root of the chart filesystem layout)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call-out. Added a "File Location" subsection clarifying that .helmignore loads only from the chart root (existing intended behavior).

For subchart .helmignore files specifically, added this as an Open Issue - unclear if the current behavior (ignoring them) is intended, a bug, or an oversight. I'll investigate and raise at the next dev meeting. For now, this HIP preserves existing behavior.

hips/hip-9999.md Outdated

4. **Directory short-circuit**: Once a directory is excluded, skip traversing its contents (matches Git's performance optimization).

5. **Self-exclusion**: `.helmignore` MAY be automatically excluded (unlike current behavior per [#9436]).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do we mean here by "MAY"? We should be specific as to if we are excluding or not. And we should also specify that this point means .helmignore won't be included in packaged archives (if we retain this).


I'm also on the fence as to whether we should do this. The only direct functional effect of excluding I think is to save a few bytes in a packaged archive. But it is another rule that people need to discover and understand (if they are impacted). #9436 doesn't really give any specific rationale either.

On the other hand, once a chart is packaged, .helmignore is spurious. The only reason it might become useful again, is if a user "mirrors" a chart (back into source) in order to modify a vendors chart (a relatively common pattern). But they could of course re-create the .helmignore file as well.

Overall, given a user could exclude .helmignore from within .helmignore if they really want to save those bytes.

I think we default to the explicit behavior (don't exclude, user can exclude via .helmignore if wanted). Unless we can come up with better reason(s). Thoughts?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - this was accidentally left in from an earlier draft. Removed. The explicit behavior (user excludes .helmignore in their own .helmignore if wanted) is better than magic auto-exclusion. This is already documented as intentional in the Out of Scope section.

Comment on lines +134 to +135
- `.Files.Get` / `.Files.Glob` (file access in templates)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `.Files.Get` / `.Files.Glob` (file access in templates)
- `.Files.Get` / `.Files.Glob` (file access in templates)
- etc

nitpick: This list might not be exhaustive

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch - added: "This list is representative; the matching logic applies wherever chart files are loaded or accessed."

hips/hip-9999.md Outdated
| Trailing spaces stripped | Preserved with `\ ` | Unlikely to affect real charts |
| No escape sequences | `\#`, `\!`, `\ ` supported | No breakage (feature addition) |

### Migration Guidance
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like AI generated extras? And not relevant to a HIP, unless we wanted to include this in the "How to teach this section".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point - removed this section. The backwards compatibility specifics are already in the table above, and "Migration guide" is listed under "How to Teach This". No need to repeat.

hips/hip-9999.md Outdated

1. **Primary statement**: "Charts v3 `.helmignore` uses identical pattern rules to `.gitignore`."

2. **Link to Git documentation**: Reference [git-scm.com/docs/gitignore][git-gitignore] as the canonical spec.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't want to do this. We need to define our own spec, which we could refer to Git's as our inspiration. e.g. if Git changes its spec, we would be on the hook to modify our behavior as a bug.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call-out. Clarified in the Specification section:

  • .helmignore intentionally mirrors Git's .gitignore matching semantics as documented at the time of this HIP
  • Divergence from current Git behavior should be treated as a bug
  • Future Git changes require explicit Helm evaluation—we don't implicitly inherit them

Also updated the documentation guidance to reference Git docs as "background documentation" rather than "canonical spec."

hips/hip-9999.md Outdated

- **Pattern matching logic**: [#8688], [#3622], [#1776], [#12592]
- **Scope/behavior confusion**: [#6075], [#3050], [#10764]
- **Feature requests for global ignore**: [#1674], [#5675]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not addressing global ignores as part of this HIP (as far as I can tell). We shouldn't refer to these issues if we are not, and perhaps add a section to "Rejected ideas".

And IMHO, a global ignore can be a follow up HIP / behavior extension.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - removed the global ignore feature request references from Evidence of User Pain. They're properly listed in the Feature Requests (Out of Scope) section already. Thanks for catching this!

Signed-off-by: Scott Rigby <scott@r6by.com>
… scope item

Signed-off-by: Scott Rigby <scott@r6by.com>
Signed-off-by: Scott Rigby <scott@r6by.com>
@scottrigby scottrigby requested a review from gjenkins8 January 27, 2026 00:34
Link to proof-of-concept at github.com/scottrigby/helmignore-ref
which validates go-git's gitignore library as the recommended
implementation approach. Update implementation plan to reference
go-git directly and add library to references section.

Signed-off-by: Scott Rigby <scott@r6by.com>
@scottrigby
Copy link
Member Author

Just pushed a commit linking to a Go library reference implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants