Automated deployment pipeline for Kubernetes homelab services.
Install
mkdir -p .claude/skills/deploy-app && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/12352" && unzip -o skill.zip -d .claude/skills/deploy-app && rm skill.zipInstalls to .claude/skills/deploy-app
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Deploy a new application to the homelab Kubernetes cluster using the bjw-s app-template Helm chart with Flux GitOps. Use this skill whenever the user wants to deploy a new service, container, app, or Docker image to the cluster. Also use when they say things like "add X to the cluster", "set up X", "run X in k8s", or mention deploying any container image. This covers the full scaffolding: directory structure, Flux Kustomization, HelmRelease, OCIRepository, ExternalSecrets, PVCs, HTTPRoutes, and registering the app in the namespace kustomization.Key capabilities
- →Deploy new applications to a Kubernetes cluster
- →Generate Kubernetes manifests for services
- →Manage secrets using ExternalSecrets and 1Password
- →Configure persistent storage with PVCs and VolSync
- →Set up HTTP routing with Gateway API
How it works
The skill generates Kubernetes manifests based on user input and specific cluster conventions, then registers the application by updating the namespace kustomization.
Inputs & outputs
When to use deploy-app
- →Deploy new service
- →Add container to cluster
- →Setup k8s app configuration
About this skill
Deploy App to Homelab Cluster
You are deploying a new application to Sean's homelab Kubernetes cluster. The cluster runs Flux CD for GitOps, uses the bjw-s app-template Helm chart for all apps, Gateway API with Envoy Gateway for routing, ExternalSecrets with 1Password for secrets, VolSync for backup, and Ceph for storage.
The repo is at /home/sean/homelab and all Kubernetes manifests live under kubernetes/.
Interview the User
Before generating any files, gather the following. The user may provide some of this upfront (e.g., "deploy nousresearch/hermes-agent:latest with 2gb ram, 1cpu, 5gb persistence on /opt/data"). Extract what you can and ask about the rest.
Required
- Image: container image repository and tag (e.g.,
ghcr.io/org/app:v1.0.0) - App name: short lowercase name for the app (infer from image name if not given)
- Port: the container's listening port
- Resources: CPU request and memory limit (e.g., "1 CPU, 2Gi memory")
Image Digest Pinning
All images in this cluster are pinned to their sha256 digest for immutability. After the user gives you an image + tag, look up the digest by running:
docker manifest inspect <repository>:<tag> -v 2>/dev/null | jq -r '.[0].Descriptor.digest // .Descriptor.digest' 2>/dev/null || \
skopeo inspect --raw "docker://<repository>:<tag>" | jq -r '.digest // empty' 2>/dev/null || \
crane digest "<repository>:<tag>" 2>/dev/null
Try each method in order until one succeeds (the user may have docker, skopeo, or crane installed). If none work, ask the user to provide the digest or leave a # TODO: pin digest comment.
The resulting tag format in the HelmRelease is: <tag>@sha256:<hex> (e.g., v1.0.0@sha256:abc123...).
If the user specifies latest as the tag, still pin the digest — latest is mutable and the digest ensures reproducibility.
Ask about these
- Command/args: custom container command or args? (e.g.,
gateway run) - Persistence: does the app need persistent storage? If yes:
- How much? (becomes
VOLSYNC_CAPACITY— default 5Gi) - What mount path? (default
/config) - Any additional volumes (cache, data dirs)?
- How much? (becomes
- NFS media mount: does it need access to the NAS media library at
172.20.0.1:/volume1/Media? - Secrets: does the app need secrets from 1Password? If yes:
- What env vars need to be secret?
- Does it need a Postgres database? (triggers the init-db pattern)
- What 1Password item name(s) to pull from?
- Plain env vars: any non-secret environment configuration?
- Route: does it need an HTTP route?
- Internal only (
envoy-internal) or public (envoy-external)? - Custom hostname or default
<appname>.mcgrath.nz?
- Internal only (
- Namespace: which namespace? (default:
default) - UID/GID: what user should the container run as? (default:
568:568— Linuxserver.io convention) - Probes: does the app have a health endpoint? (e.g.,
/health,/ping,/api/health)
Directory Structure
For an app called myapp in the default namespace, create:
kubernetes/apps/default/myapp/
ks.yaml
app/
kustomization.yaml
ocirepository.yaml
helmrelease.yaml
externalsecret.yaml # only if secrets are needed
pvc.yaml # only if extra PVCs beyond volsync are needed
Then register the app by editing the namespace kustomization at kubernetes/apps/<namespace>/kustomization.yaml — add - ./<appname>/ks.yaml to the resources list in alphabetical order. This is not optional; the app won't be deployed without it.
File Templates
Read the reference templates at references/templates.md for the exact YAML to generate for each file. The templates contain the precise patterns, schemas, annotations, and conventions used in this cluster.
Key conventions to follow exactly:
- YAML anchors:
&appfor the app name,&portfor the port,&probesfor probe reuse,&envFromfor shared secret refs - Container is always named
app - Controller name matches the app name
reloader.stakater.com/auto: "true"on every controller- Security context:
allowPrivilegeEscalation: false,readOnlyRootFilesystem: true,capabilities: { drop: ["ALL"] } - Pod security:
runAsNonRoot: true,fsGroupChangePolicy: OnRootMismatch,seccompProfile: { type: RuntimeDefault } - Resources: always set
requests.cpuandlimits.memory, never setlimits.cpu TZ: Pacific/Aucklandin every container's env- Every app gets an
emptyDirfor/tmp - Route
sectionName: httpsis always set - OCIRepository name matches the app name, always points to
oci://ghcr.io/bjw-s-labs/helm/app-templatetag4.6.2 - HelmRelease
chartRefreferences the OCIRepository by the app name - The
ks.yamlsourceRef isGitRepository/flux-clusterinflux-system - ExternalSecret target is always
<appname>-secret, secretStoreRef isClusterSecretStore/onepassword-connect
Persistence Patterns
Choose the right pattern based on what the app needs:
VolSync-backed config storage (most apps)
Add the volsync component to ks.yaml and reference the PVC in helmrelease:
ks.yaml: addcomponents: [../../../../components/volsync]and setVOLSYNC_CAPACITYin postBuild.substituteks.yaml: adddependsOnforrook-ceph-cluster(namespace:flux-system)- HelmRelease:
persistence.config.existingClaim: "{{ .Release.Name }}"
Extra PVCs (cache, data that doesn't need backup)
Create a separate pvc.yaml with storageClassName: ceph-block.
NFS media mount
persistence:
media:
type: nfs
server: 172.20.0.1
path: /volume1/Media
globalMounts:
- path: /media
emptyDir (always include for /tmp)
persistence:
tmp:
type: emptyDir
Secrets Pattern (ExternalSecrets + 1Password)
When the app needs secrets:
- Add
dependsOnforexternal-secrets-stores(namespace:kube-system) inks.yaml - Create
externalsecret.yamlpulling from theonepassword-connectClusterSecretStore - The Kubernetes Secret name is always
<appname>-secret - In HelmRelease, use
envFrom: [{secretRef: {name: "<appname>-secret"}}] - Use the
&envFrom/*envFromanchor pattern when init containers also need the secrets
Postgres database pattern
When the app needs Postgres, add an init container:
initContainers:
init-db:
image:
repository: ghcr.io/home-operations/postgres-init
tag: 18
envFrom: &envFrom
- secretRef:
name: <appname>-secret
And in the ExternalSecret, include the INIT_POSTGRES_* vars plus pull from the cloudnative-pg 1Password item. The Postgres host is postgres17-rw.database.svc.cluster.local.
Route Patterns
Routes always need explicit rules with backendRefs — don't rely on implicit routing. This ensures the Gateway API knows exactly which service and port to target.
Internal route (default for most apps)
route:
app:
parentRefs:
- name: envoy-internal
namespace: networking
sectionName: https
hostnames:
- "{{ .Release.Name }}.mcgrath.nz"
rules:
- backendRefs:
- identifier: app
port: http
External route (public-facing apps)
route:
app:
parentRefs:
- name: envoy-external
namespace: networking
sectionName: https
hostnames:
- "{{ .Release.Name }}.mcgrath.nz"
rules:
- backendRefs:
- identifier: app
port: http
After Generating Files
- Edit the namespace kustomization — this is not a suggestion, do it. Open
kubernetes/apps/<namespace>/kustomization.yamland add- ./<appname>/ks.yamlto theresourceslist in alphabetical order. - Remind the user to create the 1Password item if they're using ExternalSecrets
- If using Postgres, remind them the database will be auto-created by the init container
- Mention they can
git addand push to trigger Flux reconciliation - Offer to add Gatus monitoring annotations if desired
When not to use it
- →When deploying to a Kubernetes cluster not using Flux CD
- →When not using bjw-s app-template Helm chart
- →When not using Gateway API with Envoy Gateway for routing
Limitations
- →Specific to Sean's homelab Kubernetes cluster setup
- →Requires manual image digest pinning if Docker, Skopeo, or Crane are unavailable
- →Requires manual creation of 1Password items for secrets
How it compares
This automates the creation of a complete application deployment, including directory structure and various Kubernetes resources, rather than manual configuration.
Compared to similar skills
deploy-app side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| deploy-app (this skill) | 0 | 4mo | Review | Intermediate |
| deployment-engineer | 4 | 4mo | No flags | Advanced |
| devops | 2 | 6mo | Review | Intermediate |
| kcli-cluster-deployment | 2 | 4mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
deployment-engineer
sickn33
Expert deployment engineer specializing in modern CI/CD pipelines, GitOps workflows, and advanced deployment automation. Masters GitHub Actions, ArgoCD/Flux, progressive delivery, container security, and platform engineering. Handles zero-downtime deployments, security scanning, and developer experience optimization. Use PROACTIVELY for CI/CD design, GitOps implementation, or deployment automation.
devops
mrgoonie
Deploy to Cloudflare (Workers, R2, D1), Docker, GCP (Cloud Run, GKE), Kubernetes (kubectl, Helm). Use for serverless, containers, CI/CD, GitOps, security audit.
kcli-cluster-deployment
karmab
Guides deployment and management of Kubernetes clusters with kcli. Use when deploying OpenShift, k3s, kubeadm, or other Kubernetes distributions.
deploying-kafka-k8s
AbdullahMalik17
|
backstage-deployment
Ohorizons
Deploys the upstream open-source Backstage developer portal on Azure AKS or locally via Docker Desktop. USE FOR: deploy Backstage, Backstage on AKS, Backstage local Docker, Backstage Helm chart, Backstage PostgreSQL, Backstage ACR image, Backstage GitHub OAuth, Microsoft Entra ID auth, GitHub Enterp
deployment-pipeline-design
wshobson
Design multi-stage CI/CD pipelines with approval gates, security checks, and deployment orchestration. Use when architecting deployment workflows, setting up continuous delivery, or implementing GitOps practices.