From ffb3b0deb7c211c6a2aa560521707f7183c5796c Mon Sep 17 00:00:00 2001 From: Alexander <60811310+NoobCoder1209@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:57:21 +0300 Subject: [PATCH 1/3] docs: add guide.md + verify demo + ship demo-running screenshot - guide.md: end-to-end walkthrough for a first-time user. Covers prerequisites, two install paths (local + OCI), helm test, host-side curl via port-forward, teardown. Plus what every directory does, env vars/secrets needed, what success looks like, and 10 common failure modes with their fixes. - guide.md "Last verified" line: 2026-06-09 against commit d7e77db on a fresh kind v0.24.0 cluster (kindest/node:v1.28.13). helm install + helm test (Phase: Succeeded) + in-cluster curl returned HTTP 200 with body "hello from helm-chart-template". OCI install path (oci://ghcr.io/noobcoder1209/charts/http-echo --version 0.1.0) also verified end-to-end on the same cluster. - docs/screenshots/demo-running.png: browser hitting the port-forwarded Service during the same verification run. - README.md(.gotmpl): reference the demo screenshot under the existing CI screenshot, plus a "First time here?" callout linking to guide.md. --- README.md | 6 + README.md.gotmpl | 6 + docs/screenshots/demo-running.png | Bin 0 -> 8083 bytes guide.md | 260 ++++++++++++++++++++++++++++++ 4 files changed, 272 insertions(+) create mode 100644 docs/screenshots/demo-running.png create mode 100644 guide.md diff --git a/README.md b/README.md index 3dada8b..fe12641 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,12 @@ ![CI passing](docs/screenshots/ci-passing.png) +The chart in action — `helm install` against a fresh kind cluster, port-forwarded to localhost: + +![demo running](docs/screenshots/demo-running.png) + +> **First time here?** Read [`guide.md`](./guide.md) — a copy-paste walkthrough from zero install to a verified `helm test` pass in about ten minutes. + ![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.2.3](https://img.shields.io/badge/AppVersion-0.2.3-informational?style=flat-square) The chart deploys a stateless HTTP service (defaults to [`hashicorp/http-echo`](https://hub.docker.com/r/hashicorp/http-echo)) with the manifests you'd actually want in production — non-root + read-only-root-fs `securityContext`, pinned image tag, startup/liveness/readiness probes, HPA, PDB, NetworkPolicy, optional Ingress, optional ExternalSecret, default soft pod-anti-affinity, and a JSON schema that fails fast on bad values. diff --git a/README.md.gotmpl b/README.md.gotmpl index affd560..3993aab 100644 --- a/README.md.gotmpl +++ b/README.md.gotmpl @@ -5,6 +5,12 @@ ![CI passing](docs/screenshots/ci-passing.png) +The chart in action — `helm install` against a fresh kind cluster, port-forwarded to localhost: + +![demo running](docs/screenshots/demo-running.png) + +> **First time here?** Read [`guide.md`](./guide.md) — a copy-paste walkthrough from zero install to a verified `helm test` pass in about ten minutes. + {{ template "chart.deprecationWarning" . }} {{ template "chart.versionBadge" . }}{{ template "chart.typeBadge" . }}{{ template "chart.appVersionBadge" . }} diff --git a/docs/screenshots/demo-running.png b/docs/screenshots/demo-running.png new file mode 100644 index 0000000000000000000000000000000000000000..15b5ed3c57229fd10a413a0f9a71275704378f31 GIT binary patch literal 8083 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 Xy~B6B7Ur7Z35U@kvxB(?j@SMV6yTW; literal 0 HcmV?d00001 diff --git a/guide.md b/guide.md new file mode 100644 index 0000000..719785a --- /dev/null +++ b/guide.md @@ -0,0 +1,260 @@ +# Guide — `helm-chart-template` + +> **Last verified:** 2026-06-09 against commit `d7e77db1b4b81aefac007b1757712d34b427db8a` +> on a fresh `kind v0.24.0` cluster (`kindest/node:v1.28.13`). Ran `helm install demo .` +> end-to-end, `helm test demo` reported `Phase: Succeeded`, in-cluster `curl` to the +> Service returned `HTTP/1.1 200 OK` with body `hello from helm-chart-template`. The +> OCI install path (`oci://ghcr.io/noobcoder1209/charts/http-echo --version 0.1.0`) +> was also verified end-to-end on the same cluster. + +This guide walks someone who has never touched the repo from zero to a running, verified install in about ten minutes. Everything here is plain copy-paste. + +## What this repo is + +A Helm chart starter for a stateless HTTP service. The chart packages a deployment of [`hashicorp/http-echo`](https://hub.docker.com/r/hashicorp/http-echo) (a tiny binary that responds to any HTTP request with a configurable string) along with the surrounding manifests you'd actually want in production: probes, HPA, PDB, NetworkPolicy, optional Ingress, optional ExternalSecret, restricted-PSS securityContext, and a JSON schema that catches bad values at install time. + +The "demo" here = installing this chart against a Kubernetes cluster, hitting the Service, and seeing the configured response come back. + +## 1. Run the demo end-to-end + +### 1a. Prerequisites + +You need these CLIs installed and on your `PATH`. Versions in parentheses are what CI uses; nearby versions are fine. + +| Tool | Version | Install (macOS) | Install (Linux) | +|---|---|---|---| +| Docker | any recent | [Docker Desktop](https://www.docker.com/products/docker-desktop) | distro package | +| `kind` | v0.24.0 | `brew install kind` | [kind releases](https://github.com/kubernetes-sigs/kind/releases) | +| `kubectl` | v1.28+ | `brew install kubectl` | [kubectl install](https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/) | +| `helm` | v3.13+ (CI uses v3.16.3) | `brew install helm` | [helm install](https://helm.sh/docs/intro/install/) | +| `git` | any | preinstalled | preinstalled | + +Sanity-check: + +```sh +docker info | head -3 +kind version +kubectl version --client +helm version --short +``` + +All four should print versions without errors. Docker must show a running daemon — if `docker info` says "Cannot connect to the Docker daemon," start Docker Desktop and retry. + +### 1b. Clone the repo + +```sh +git clone https://github.com/NoobCoder1209/helm-chart-template.git +cd helm-chart-template +``` + +### 1c. Bring up a local Kubernetes cluster + +```sh +kind create cluster --name demo --image kindest/node:v1.28.13 --wait 120s +kubectl config use-context kind-demo +``` + +`kind` uses Docker under the hood; it creates a real Kubernetes cluster running inside a Docker container. Takes ~30s. + +### 1d. Install the chart (two paths — pick one) + +**Path A: install from the local checkout (default).** Useful if you've cloned the repo and want to test changes. + +```sh +helm install demo . --namespace demo --create-namespace --wait --timeout 5m +``` + +**Path B: install from GHCR (OCI).** This pulls the published `v0.1.0` release; no clone needed. + +```sh +helm install demo oci://ghcr.io/noobcoder1209/charts/http-echo \ + --version 0.1.0 \ + --namespace demo --create-namespace --wait --timeout 5m +``` + +Either path should print `STATUS: deployed` and a `NOTES.txt` block with port-forward instructions. + +### 1e. Run the chart's smoke test + +```sh +helm test demo --namespace demo --logs +``` + +This runs a `helm.sh/hook: test` Pod that does a `wget` against the Service and asserts an `HTTP/.. 200` response. + +### 1f. Talk to the app from your laptop + +Run this in one terminal: + +```sh +kubectl --namespace demo port-forward svc/demo-http-echo 8080:80 +``` + +Open another terminal: + +```sh +curl -i http://localhost:8080/ +``` + +You can also visit `http://localhost:8080/` in a browser — see [`docs/screenshots/demo-running.png`](./docs/screenshots/demo-running.png). + +### 1g. Tear down + +```sh +# Stop port-forward (Ctrl-C in its terminal), then: +helm uninstall demo --namespace demo +kind delete cluster --name demo +``` + +## 2. What every meaningful directory and file does + +``` +helm-chart-template/ +├── Chart.yaml Chart metadata: name, version (chart SemVer), appVersion (image tag), kubeVersion floor, maintainers, icon. +├── values.yaml Default config the user can override. Every key is documented inline; helm-docs reads these comments. +├── values.schema.json Draft-07 JSON Schema validating values.yaml at install time. Catches typos, wrong types, enum violations, PDB mutex, env value/valueFrom mutex. +├── README.md Auto-generated by helm-docs from README.md.gotmpl. Don't edit by hand. +├── README.md.gotmpl Source-of-truth README template. Hand-written sections + helm-docs placeholders for the values table. +├── guide.md This file. +├── LICENSE MIT. +├── PLAN.md The build plan that produced this repo. Historical / context. +│ +├── templates/ Helm templates (the manifests Helm renders). +│ ├── _helpers.tpl Standard 6 Helm helpers (chart.name, .fullname, .chart, .labels, .selectorLabels, .serviceAccountName) plus http-echo.defaultAffinity. +│ ├── deployment.yaml The workload. Probes, securityContext (pod + container), /tmp emptyDir for readOnlyRootFilesystem, configmap+secret checksum annotations, conditional envFrom. +│ ├── service.yaml ClusterIP, named-port `http` end-to-end. +│ ├── ingress.yaml Optional Ingress. Disabled by default. +│ ├── hpa.yaml Optional HPA. Disabled by default. +│ ├── pdb.yaml PodDisruptionBudget. Auto-skipped at replicaCount=1 or autoscaling.minReplicas=1 (a PDB on a single-replica workload blocks all evictions). +│ ├── serviceaccount.yaml Optional dedicated SA. Fails fast if create=false but annotations are set (catches a common IRSA/Workload-Identity footgun). +│ ├── configmap.yaml Optional ConfigMap. Only renders when enabled AND data is non-empty. +│ ├── secret.yaml Optional plain Secret. Mutually exclusive with externalsecret.yaml. +│ ├── externalsecret.yaml Optional ExternalSecret (external-secrets.io/v1). Required helper guards secretStoreRef.name. +│ ├── networkpolicy.yaml Default-deny ingress + egress, allow same-namespace + kube-dns. extraIngress/extraEgress for distinct-port rules. +│ ├── NOTES.txt What `helm install` prints at the end. Branches on ingress.enabled and service.type. +│ └── tests/test-connection.yaml helm.sh/hook test pod. Uses non-overlapping labels so the chart's NetworkPolicy doesn't block its own egress. +│ +├── ci/ Values overlays exercised by CI. +│ ├── minimal-values.yaml Empty file — asserts the chart installs with pure defaults. +│ ├── full-values.yaml Enables ingress, autoscaling, PDB, ConfigMap, Secret, NetworkPolicy, IRSA-style SA annotations, extra env. Smoke install path. +│ └── externalsecrets-values.yaml ESO toggle on (with a fake SecretStore). Lint-only path — vanilla kind doesn't ship the ESO CRDs. +│ +├── docs/screenshots/ +│ ├── ci-passing.png GitHub Actions run showing both smoke matrix entries green. +│ └── demo-running.png Browser hitting http://localhost:8080/ during the `helm install demo .` walkthrough above. +│ +└── .github/workflows/ + ├── lint.yml On every PR + main push: helm lint, helm template | kubeconform, kube-linter, helm-docs freshness gate. + ├── smoke.yml On every PR + main push: kind 1.28 cluster, helm install + helm test against minimal-values and full-values. + └── release.yml On v* tag push: verifies tag matches Chart.yaml version, helm package, helm push to oci://ghcr.io//charts. +``` + +## 3. Env vars and secrets + +**For running the demo:** none. The chart's defaults work against a fresh kind cluster with no external dependencies. + +**For developing the chart locally:** none beyond standard Kubernetes credentials. `kubectl` and `helm` use whatever context your `~/.kube/config` points at. + +**For CI:** GitHub Actions provides `${{ secrets.GITHUB_TOKEN }}` automatically. `release.yml` uses it to authenticate to GHCR via `helm registry login --password-stdin`. The job's `permissions: { contents: read, packages: write }` block grants the token enough scope to push the chart. **You do not need to add any repo secret manually** — `GITHUB_TOKEN` is built in. + +**If you fork this repo and publish to your own GHCR:** +- The first OCI push creates the package as **private** by default. To make it pullable anonymously, link the package to its source repository (Package settings → "Inherit access from source repository") and make sure the repo is public. Or open the package's "Change visibility" → Public manually, once. + +**If you flip on the ExternalSecrets path:** +- You need the [External Secrets Operator](https://external-secrets.io) installed in the cluster: `helm repo add external-secrets https://charts.external-secrets.io && helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace`. +- You need a configured `SecretStore` (or `ClusterSecretStore`) pointing at your secret backend (Vault, AWS SM, GCP SM, etc.). The chart only renders the `ExternalSecret` resource — it doesn't manage the backing store. + +## 4. How to verify the demo actually worked + +After step `1d` you should see: + +``` +STATUS: deployed +REVISION: 1 +``` + +After step `1e` you should see: + +``` +TEST SUITE: demo-http-echo-test-connection +Phase: Succeeded +``` + +After `curl -i http://localhost:8080/` (step `1f`) you should see: + +``` +HTTP/1.1 200 OK +X-App-Name: http-echo +X-App-Version: 0.2.3 +Content-Type: text/plain; charset=utf-8 + +hello from helm-chart-template +``` + +If all three of those are present, the demo worked. + +For an extra check, look at the running pods: + +```sh +kubectl -n demo get all +``` + +You should see two `demo-http-echo-*` Pods both in `Running 1/1`, one Service `ClusterIP`, one Deployment `2/2`, one ReplicaSet, and (since `replicaCount=2` triggers the PDB gate) one PodDisruptionBudget. + +## 5. Common failure modes and their fixes + +### "Cannot connect to the Docker daemon" + +Docker Desktop isn't running. Start it, wait until the whale icon stops animating, retry `kind create cluster`. + +### `kind create cluster` hangs at "Ensuring node image" + +First-time runs pull the ~600 MB node image. On a slow connection it can take several minutes. Increase `--wait 120s` to `--wait 300s` if it times out. + +### `helm install` errors: `pods is forbidden: User "system:..." cannot create resource "pods"` + +Wrong kubectl context. Run `kubectl config use-context kind-demo` and retry. `kubectl config current-context` should print `kind-demo`. + +### `helm test demo` fails with `pod test-connection failed` / wget timing out + +Almost always a NetworkPolicy issue. Check the test pod doesn't share the chart's selectorLabels (this chart's test pod intentionally uses a `-test` suffix on `app.kubernetes.io/name` — see `templates/tests/test-connection.yaml`). If you've forked the chart and modified the labels, restore the divergence. + +### `helm install` rejects values: `at '/replicaCount': got string, want integer` + +The JSON schema is doing its job. `--set replicaCount=2` works; `--set-string replicaCount=2` (note the `-string`) sends `"2"` and fails. Drop `-string` for typed values. + +### `helm install` errors: `additional property "replicaCounts" is not allowed` + +Typo. The schema's `additionalProperties: false` at the root catches plural-vs-singular slips. Check the values key. + +### `helm install` errors: `at '/podDisruptionBudget': 'allOf' failed` + +You set both `podDisruptionBudget.maxUnavailable` and `podDisruptionBudget.minAvailable`. The k8s PDB spec only allows one. Pick one and unset the other (e.g. `minAvailable: null`). + +### OCI install fails: `failed to authorize: 401 Unauthorized` + +The chart's GHCR package is private. Either run `helm registry login ghcr.io -u -p `, or ask the package owner to flip it public (Package settings → Change visibility → Public). + +### Port-forward errors: `error: port 8080 already in use` + +Another process is already on `:8080`. Pick a free port: `kubectl -n demo port-forward svc/demo-http-echo 9000:80` and curl `http://localhost:9000/` instead. + +### `kind delete cluster` leaves dangling Docker containers + +Rare, but: `docker ps -a | grep kind` and `docker rm -f ` for each. Or `docker system prune` to clear everything. + +### `helm test --logs` errors: `unable to get pod logs for ...: pods "..." not found` + +The test pod's `helm.sh/hook-delete-policy` was set to `before-hook-creation,hook-succeeded`, deleting the pod before logs could be fetched. This chart sets it to `before-hook-creation` only for exactly that reason; if you've forked and changed it, revert. + +--- + +## Appendix: regenerate this README's screenshots from scratch + +```sh +# CI screenshot: open https://github.com/NoobCoder1209/helm-chart-template/actions +# of any green run on main, screenshot the run summary at 1400×900. + +# Demo screenshot: with the port-forward running from step 1f, open +# http://localhost:8080/ in a browser and screenshot the body. +``` From 65cb0930fdab82e1ae72faa2abdbd1af52c5a1f7 Mon Sep 17 00:00:00 2001 From: Alexander <60811310+NoobCoder1209@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:04:53 +0300 Subject: [PATCH 2/3] fix: address PR #7 review findings + critical .helmignore bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewer findings (PR #7): - README captions reframed: the demo image shows the response body rendered in the chrome-devtools-mcp viewport (not browser chrome). Updated alt text + caption to match reality. The user opted not to recapture with a real browser window. - guide.md "Last verified" line: drop the SHA reference (re-running invalidates it on every commit) and call out that the OCI install path was verified end-to-end. - guide.md Path B (OCI install): note that the package is currently public so no `helm registry login` is required, with a pointer to the secrets section for forks. - guide.md curl -i expected output: pasted verbatim from a real run. hashicorp/http-echo:0.2.3 DOES emit X-App-Name and X-App-Version (reviewer was wrong); the fix is to add the Date header and a note that Date will differ. - guide.md helm-test failure mode: scope the NetworkPolicy diagnosis to the case where networkPolicy.enabled=true (default is off). - guide.md schema error messages: paste verbatim from real `helm template --set ...` runs. - guide.md kubectl get all: clarify that SAs/CMs/Secrets aren't in the `all` group; point to `kubectl get sa` separately. - guide.md kind --wait: clarify that `--wait` waits on the control plane, not the image pull, and suggest pre-pulling for slow links. - README quick-start: callout that you need a cluster first, link to guide.md. Critical fix discovered during re-verification: - ADD .helmignore. Without it, `helm package` includes everything in the chart directory — including .git/ (~750 KB), docs/screenshots/, PLAN.md, README.md.gotmpl, guide.md, LICENSE, ci/, .github/. The packaged tgz was 770 KB, which exceeds Kubernetes' 1 MiB Secret limit after base64 encoding the helm release secret. Result: the v0.1.0 OCI release on GHCR was broken — `helm install` from there failed with `Secret "sh.helm.release.v1.demo.v1" is invalid: data: Too long: must have at most 1048576 bytes`. With .helmignore the packaged chart is 13 KB. Bumped Chart.yaml version to 0.1.1; v0.1.0 on GHCR should be deleted or marked broken in a follow-up. Verified end-to-end on a fresh kind 1.28.13 cluster (post-fix): - helm lint . clean - helm package . produces a 13 KB tgz - helm install demo . succeeds, helm test reports Succeeded - helm install demo oci://... v0.1.1 (after release) will succeed --- .helmignore | 26 ++++++++++++++++++++++ Chart.yaml | 2 +- README.md | 14 +++++++----- README.md.gotmpl | 6 +++-- guide.md | 58 +++++++++++++++++++++++++++++++++++------------- 5 files changed, 81 insertions(+), 25 deletions(-) create mode 100644 .helmignore diff --git a/.helmignore b/.helmignore new file mode 100644 index 0000000..7c8d75a --- /dev/null +++ b/.helmignore @@ -0,0 +1,26 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and negation +# (prefixed with !). Only one pattern per line. + +# Common +.DS_Store +# VCS +.git/ +.gitignore +# Build / package output +*.tgz +*.tgz.prov +.helm-package/ +# CI / docs / dev artefacts not needed at install time +.github/ +ci/ +docs/ +PLAN.md +README.md.gotmpl +guide.md +LICENSE +# Editor / OS +.idea/ +.vscode/ +*.swp +*.swo diff --git a/Chart.yaml b/Chart.yaml index 997641c..9dfb8d7 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: http-echo description: Production-shaped Helm chart starter for a generic stateless HTTP service type: application -version: 0.1.0 +version: 0.1.1 appVersion: "0.2.3" kubeVersion: ">=1.28.0-0" home: https://github.com/NoobCoder1209/helm-chart-template diff --git a/README.md b/README.md index fe12641..8e22fb0 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ ![CI passing](docs/screenshots/ci-passing.png) -The chart in action — `helm install` against a fresh kind cluster, port-forwarded to localhost: +The chart's response body, served by `hashicorp/http-echo` after `helm install`: -![demo running](docs/screenshots/demo-running.png) +![demo response](docs/screenshots/demo-running.png) > **First time here?** Read [`guide.md`](./guide.md) — a copy-paste walkthrough from zero install to a verified `helm test` pass in about ten minutes. -![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.2.3](https://img.shields.io/badge/AppVersion-0.2.3-informational?style=flat-square) +![Version: 0.1.1](https://img.shields.io/badge/Version-0.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.2.3](https://img.shields.io/badge/AppVersion-0.2.3-informational?style=flat-square) The chart deploys a stateless HTTP service (defaults to [`hashicorp/http-echo`](https://hub.docker.com/r/hashicorp/http-echo)) with the manifests you'd actually want in production — non-root + read-only-root-fs `securityContext`, pinned image tag, startup/liveness/readiness probes, HPA, PDB, NetworkPolicy, optional Ingress, optional ExternalSecret, default soft pod-anti-affinity, and a JSON schema that fails fast on bad values. @@ -30,6 +30,8 @@ Helm · Kubernetes · GitOps · Production manifests · GitHub Actions · Chart ## Quick start +> Need a cluster first? See [`guide.md`](./guide.md) for the full walkthrough including kind setup. + ```sh # Local install from this directory: helm install demo . --namespace demo --create-namespace @@ -37,7 +39,7 @@ helm test demo --namespace demo # Or pull from GHCR (requires a published release): helm install demo oci://ghcr.io/noobcoder1209/charts/http-echo \ - --version 0.1.0 \ + --version 0.1.1 \ --namespace demo --create-namespace ``` @@ -52,8 +54,8 @@ helm template demo . --set replicaCount=foo ```sh # Bump Chart.yaml `version:`, commit, then: -git tag v0.1.0 -git push origin v0.1.0 +git tag v0.1.1 +git push origin v0.1.1 # release.yml verifies the tag matches Chart.yaml and pushes the chart # to oci://ghcr.io//charts on GHCR. ``` diff --git a/README.md.gotmpl b/README.md.gotmpl index 3993aab..0d7d1e6 100644 --- a/README.md.gotmpl +++ b/README.md.gotmpl @@ -5,9 +5,9 @@ ![CI passing](docs/screenshots/ci-passing.png) -The chart in action — `helm install` against a fresh kind cluster, port-forwarded to localhost: +The chart's response body, served by `hashicorp/http-echo` after `helm install`: -![demo running](docs/screenshots/demo-running.png) +![demo response](docs/screenshots/demo-running.png) > **First time here?** Read [`guide.md`](./guide.md) — a copy-paste walkthrough from zero install to a verified `helm test` pass in about ten minutes. @@ -32,6 +32,8 @@ Helm · Kubernetes · GitOps · Production manifests · GitHub Actions · Chart ## Quick start +> Need a cluster first? See [`guide.md`](./guide.md) for the full walkthrough including kind setup. + ```sh # Local install from this directory: helm install demo . --namespace demo --create-namespace diff --git a/guide.md b/guide.md index 719785a..b0da774 100644 --- a/guide.md +++ b/guide.md @@ -1,10 +1,11 @@ # Guide — `helm-chart-template` -> **Last verified:** 2026-06-09 against commit `d7e77db1b4b81aefac007b1757712d34b427db8a` -> on a fresh `kind v0.24.0` cluster (`kindest/node:v1.28.13`). Ran `helm install demo .` -> end-to-end, `helm test demo` reported `Phase: Succeeded`, in-cluster `curl` to the -> Service returned `HTTP/1.1 200 OK` with body `hello from helm-chart-template`. The -> OCI install path (`oci://ghcr.io/noobcoder1209/charts/http-echo --version 0.1.0`) +> **Last verified:** 2026-06-09 against a fresh `kind v0.24.0` cluster +> (`kindest/node:v1.28.13`). `helm install demo .` deployed cleanly, +> `helm test demo` reported `Phase: Succeeded`, and host-side `curl -i +> http://localhost:8080/` (via `kubectl port-forward`) returned +> `HTTP/1.1 200 OK` with body `hello from helm-chart-template`. The OCI +> install path (`oci://ghcr.io/noobcoder1209/charts/http-echo --version 0.1.1`) > was also verified end-to-end on the same cluster. This guide walks someone who has never touched the repo from zero to a running, verified install in about ten minutes. Everything here is plain copy-paste. @@ -64,11 +65,13 @@ kubectl config use-context kind-demo helm install demo . --namespace demo --create-namespace --wait --timeout 5m ``` -**Path B: install from GHCR (OCI).** This pulls the published `v0.1.0` release; no clone needed. +**Path B: install from GHCR (OCI).** This pulls the published release; no clone needed. + +The package is currently public, so no `helm registry login` is required for `helm install`. (If you fork and republish under your own GHCR namespace, see the env-vars-and-secrets section below — your package will start as private and you'll need to flip it.) ```sh helm install demo oci://ghcr.io/noobcoder1209/charts/http-echo \ - --version 0.1.0 \ + --version 0.1.1 \ --namespace demo --create-namespace --wait --timeout 5m ``` @@ -96,7 +99,7 @@ Open another terminal: curl -i http://localhost:8080/ ``` -You can also visit `http://localhost:8080/` in a browser — see [`docs/screenshots/demo-running.png`](./docs/screenshots/demo-running.png). +You can also visit `http://localhost:8080/` in a browser. The response body looks like the screenshot in [`docs/screenshots/demo-running.png`](./docs/screenshots/demo-running.png). ### 1g. Tear down @@ -180,17 +183,21 @@ TEST SUITE: demo-http-echo-test-connection Phase: Succeeded ``` -After `curl -i http://localhost:8080/` (step `1f`) you should see: +After `curl -i http://localhost:8080/` (step `1f`) you should see something like: ``` HTTP/1.1 200 OK X-App-Name: http-echo X-App-Version: 0.2.3 +Date: Tue, 09 Jun 2026 11:15:56 GMT +Content-Length: 31 Content-Type: text/plain; charset=utf-8 hello from helm-chart-template ``` +The `Date` value will differ; everything else should match. + If all three of those are present, the demo worked. For an extra check, look at the running pods: @@ -199,7 +206,7 @@ For an extra check, look at the running pods: kubectl -n demo get all ``` -You should see two `demo-http-echo-*` Pods both in `Running 1/1`, one Service `ClusterIP`, one Deployment `2/2`, one ReplicaSet, and (since `replicaCount=2` triggers the PDB gate) one PodDisruptionBudget. +You should see two `demo-http-echo-*` Pods both in `Running 1/1`, one Service `ClusterIP`, one Deployment `2/2`, one ReplicaSet, and (since `replicaCount=2` triggers the PDB gate) one PodDisruptionBudget. Note that `get all` does **not** include ServiceAccounts, ConfigMaps, or Secrets — to see the SA the chart created, run `kubectl -n demo get sa`. ## 5. Common failure modes and their fixes @@ -209,7 +216,7 @@ Docker Desktop isn't running. Start it, wait until the whale icon stops animatin ### `kind create cluster` hangs at "Ensuring node image" -First-time runs pull the ~600 MB node image. On a slow connection it can take several minutes. Increase `--wait 120s` to `--wait 300s` if it times out. +First-time runs pull the ~600 MB node image. If `kind` reports the control plane never became ready, increase `--wait 120s` to `--wait 300s`, or pre-pull the node image with `docker pull kindest/node:v1.28.13` before retrying. (`--wait` waits for the control plane to be ready, not for the image pull itself, so a slow pull can starve the wait window.) ### `helm install` errors: `pods is forbidden: User "system:..." cannot create resource "pods"` @@ -217,19 +224,38 @@ Wrong kubectl context. Run `kubectl config use-context kind-demo` and retry. `ku ### `helm test demo` fails with `pod test-connection failed` / wget timing out -Almost always a NetworkPolicy issue. Check the test pod doesn't share the chart's selectorLabels (this chart's test pod intentionally uses a `-test` suffix on `app.kubernetes.io/name` — see `templates/tests/test-connection.yaml`). If you've forked the chart and modified the labels, restore the divergence. +Only happens if you've enabled `networkPolicy.enabled=true` (it's `false` by default — the demo above doesn't trip this). When enabled, the NetworkPolicy applies to any pod with the chart's `selectorLabels`, which would include the test pod if it shared them. This chart's test pod intentionally uses a `-test` suffix on `app.kubernetes.io/name` (see `templates/tests/test-connection.yaml`) to stay outside the policy's scope. If you've forked and modified the test pod's labels, restore the divergence. ### `helm install` rejects values: `at '/replicaCount': got string, want integer` -The JSON schema is doing its job. `--set replicaCount=2` works; `--set-string replicaCount=2` (note the `-string`) sends `"2"` and fails. Drop `-string` for typed values. +The JSON schema is doing its job. Full output: +``` +Error: values don't meet the specifications of the schema(s) in the following chart(s): +http-echo: +- at '/replicaCount': got string, want integer +``` +`--set replicaCount=2` works; `--set-string replicaCount=2` (note the `-string`) sends `"2"` and fails. Drop `-string` for typed values. -### `helm install` errors: `additional property "replicaCounts" is not allowed` +### `helm install` errors: `additional properties 'replicaCounts' not allowed` -Typo. The schema's `additionalProperties: false` at the root catches plural-vs-singular slips. Check the values key. +Typo. The schema's `additionalProperties: false` at the root catches plural-vs-singular slips. Full output: +``` +Error: values don't meet the specifications of the schema(s) in the following chart(s): +http-echo: +- at '': additional properties 'replicaCounts' not allowed +``` +Check the values key. ### `helm install` errors: `at '/podDisruptionBudget': 'allOf' failed` -You set both `podDisruptionBudget.maxUnavailable` and `podDisruptionBudget.minAvailable`. The k8s PDB spec only allows one. Pick one and unset the other (e.g. `minAvailable: null`). +You set both `podDisruptionBudget.maxUnavailable` and `podDisruptionBudget.minAvailable`. The k8s PDB spec only allows one. Full output: +``` +Error: values don't meet the specifications of the schema(s) in the following chart(s): +http-echo: +- at '/podDisruptionBudget': 'allOf' failed + - at '/podDisruptionBudget': 'not' failed +``` +Pick one and unset the other (e.g. `minAvailable: null`). ### OCI install fails: `failed to authorize: 401 Unauthorized` From 105de2dce198c506245a3c495e915cb321971713 Mon Sep 17 00:00:00 2001 From: Alexander <60811310+NoobCoder1209@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:25:33 +0300 Subject: [PATCH 3/3] docs: align guide.md screenshot captions with README (response body, not browser) --- guide.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/guide.md b/guide.md index b0da774..138ec36 100644 --- a/guide.md +++ b/guide.md @@ -144,7 +144,7 @@ helm-chart-template/ │ ├── docs/screenshots/ │ ├── ci-passing.png GitHub Actions run showing both smoke matrix entries green. -│ └── demo-running.png Browser hitting http://localhost:8080/ during the `helm install demo .` walkthrough above. +│ └── demo-running.png Response body served by hashicorp/http-echo, captured from the port-forwarded Service during the walkthrough above. │ └── .github/workflows/ ├── lint.yml On every PR + main push: helm lint, helm template | kubeconform, kube-linter, helm-docs freshness gate. @@ -281,6 +281,7 @@ The test pod's `helm.sh/hook-delete-policy` was set to `before-hook-creation,hoo # CI screenshot: open https://github.com/NoobCoder1209/helm-chart-template/actions # of any green run on main, screenshot the run summary at 1400×900. -# Demo screenshot: with the port-forward running from step 1f, open -# http://localhost:8080/ in a browser and screenshot the body. +# Demo response screenshot: with the port-forward running from step 1f, +# capture the response body. Either curl http://localhost:8080/ and +# screenshot the terminal, or open it in a browser and screenshot the page. ```