Claude-skill-registry app-template

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/app-template" ~/.claude/skills/majiayu000-claude-skill-registry-app-template && rm -rf "$T"
manifest: skills/data/app-template/SKILL.md
source content

app-template Helm Chart

The bjw-s/app-template chart deploys containerized applications without requiring a dedicated Helm chart. It provides a declarative interface for common Kubernetes resources.

Chart source:

oci://ghcr.io/bjw-s-labs/helm/app-template
Schema:
https://raw.githubusercontent.com/bjw-s-labs/helm-charts/app-template-4.6.0/charts/other/app-template/values.schema.json

Quick Start

Minimal values.yaml for a single-container deployment:

# yaml-language-server: $schema=https://raw.githubusercontent.com/bjw-s-labs/helm-charts/app-template-4.6.0/charts/other/app-template/values.schema.json
controllers:
  main:
    containers:
      main:
        image:
          repository: nginx
          tag: latest

service:
  main:
    controller: main
    ports:
      http:
        port: 80

Core Structure

Controllers

Controllers define workload types. Each controller creates one Pod spec.

controllers:
  main:                              # Controller identifier (arbitrary name)
    type: deployment                 # deployment|statefulset|daemonset|cronjob|job
    replicas: 1
    strategy: Recreate               # Recreate|RollingUpdate (deployment)

    # Pod-level settings
    pod:
      securityContext:
        fsGroup: 568
        fsGroupChangePolicy: OnRootMismatch

    containers:
      main:                          # Container identifier
        image:
          repository: ghcr.io/org/app
          tag: v1.0.0
        env:
          TZ: UTC
          CONFIG_PATH: /config

Multiple Controllers

Create separate deployments in one release:

controllers:
  web:
    containers:
      main:
        image:
          repository: nginx
          tag: latest

  worker:
    type: deployment
    replicas: 3
    containers:
      main:
        image:
          repository: myapp/worker
          tag: v1.0.0

Sidecar Containers

Add sidecars with

dependsOn
for ordering:

controllers:
  main:
    containers:
      main:
        image:
          repository: myapp
          tag: v1.0.0

      sidecar:
        dependsOn: main              # Start after main container
        image:
          repository: sidecar-image
          tag: latest
        args: ["--config", "/config/sidecar.yaml"]

Services

Services expose controller pods. Link via

controller
field.

service:
  main:
    controller: main                 # Links to controllers.main
    type: ClusterIP                  # ClusterIP|LoadBalancer|NodePort
    ports:
      http:
        port: 8080
      metrics:
        port: 9090

  websocket:
    controller: main
    ports:
      ws:
        port: 3012

Ingress

ingress:
  main:
    className: nginx
    hosts:
      - host: app.example.com
        paths:
          - path: /
            pathType: Prefix
            service:
              identifier: main       # References service.main
              port: http             # References port name
    tls:
      - hosts:
          - app.example.com
        secretName: app-tls

Multiple paths to different services:

ingress:
  main:
    hosts:
      - host: app.example.com
        paths:
          - path: /
            service:
              identifier: main
              port: http
          - path: /ws
            service:
              identifier: websocket
              port: ws

Persistence

PersistentVolumeClaim

persistence:
  config:
    type: persistentVolumeClaim
    accessMode: ReadWriteOnce
    size: 1Gi
    globalMounts:
      - path: /config

Existing PVC

persistence:
  config:
    existingClaim: my-existing-pvc
    globalMounts:
      - path: /config

NFS Mount

persistence:
  backup:
    type: nfs
    server: nas.local
    path: /volume/backups
    globalMounts:
      - path: /backup

EmptyDir (Shared Between Containers)

persistence:
  shared-data:
    type: emptyDir
    globalMounts:
      - path: /shared

Advanced Mounts (Per-Controller/Container)

persistence:
  config:
    existingClaim: app-config
    advancedMounts:
      main:                          # Controller identifier
        main:                        # Container identifier
          - path: /config
        sidecar:
          - path: /config
            readOnly: true

Environment Variables

Direct Values

controllers:
  main:
    containers:
      main:
        env:
          TZ: UTC
          LOG_LEVEL: info
          TEMPLATE_VAR: "{{ .Release.Name }}"

From Secrets/ConfigMaps

controllers:
  main:
    containers:
      main:
        env:
          DATABASE_URL:
            valueFrom:
              secretKeyRef:
                name: db-secret
                key: url
        envFrom:
          - secretRef:
              name: app-secrets
          - configMapRef:
              name: app-config

Security Context

Pod-Level

defaultPodOptions:
  securityContext:
    runAsUser: 568
    runAsGroup: 568
    fsGroup: 568
    fsGroupChangePolicy: OnRootMismatch

controllers:
  main:
    containers:
      main:
        image:
          repository: myapp
          tag: v1.0.0

Container-Level (Privileged Sidecar)

controllers:
  main:
    containers:
      main:
        securityContext:
          runAsUser: 568
          runAsGroup: 568

      vpn:
        image:
          repository: vpn-client
          tag: latest
        securityContext:
          capabilities:
            add:
              - NET_ADMIN

Probes

Default probes use TCP on the primary service port. Customize:

controllers:
  main:
    containers:
      main:
        probes:
          liveness:
            enabled: true
            custom: true
            spec:
              httpGet:
                path: /health
                port: 8080
              initialDelaySeconds: 10
              periodSeconds: 30
          readiness:
            enabled: true
            type: HTTP
            spec:
              path: /ready
              port: 8080
          startup:
            enabled: false

StatefulSet with VolumeClaimTemplates

controllers:
  main:
    type: statefulset
    statefulset:
      volumeClaimTemplates:
        - name: data
          accessMode: ReadWriteOnce
          size: 10Gi
          globalMounts:
            - path: /data

CronJob

controllers:
  backup:
    type: cronjob
    cronjob:
      schedule: "0 2 * * *"
      concurrencyPolicy: Forbid
      successfulJobsHistory: 3
      failedJobsHistory: 1
    containers:
      main:
        image:
          repository: backup-tool
          tag: v1.0.0
        args: ["--backup", "/data"]

ServiceMonitor (Prometheus)

serviceMonitor:
  main:
    enabled: true
    serviceName: main
    endpoints:
      - port: metrics
        scheme: http
        path: /metrics
        interval: 30s

Flux HelmRelease Integration

For this homelab, app-template deploys via Flux ResourceSet. Add to

kubernetes/platform/helm-charts.yaml
:

# In resourcesTemplate, the pattern generates HelmRelease automatically
# For app-template specifically, add to inputs:
- name: "my-app"
  namespace: "default"
  chart:
    name: "app-template"
    version: "4.6.0"
    url: "oci://ghcr.io/bjw-s-labs/helm"  # Note: OCI registry
  dependsOn: [cilium]

Values go in

kubernetes/platform/charts/my-app.yaml
.

Common Patterns

See references/patterns.md for:

  • VPN sidecar with gluetun
  • Code-server sidecar for config editing
  • Multi-service applications (websocket + http)
  • Init containers for setup tasks

See references/values-reference.md for complete values.yaml documentation.