Helm chart development patterns for packaging and deploying Kubernetes applications. Use when creating reusable Helm charts, managing multi-environment deployments, or building application catalogs for Kubernetes.
/plugin marketplace add NickCrew/claude-cortex/plugin install nickcrew-claude-ctx-2@NickCrew/claude-cortexThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Expert guidance for developing production-grade Helm charts covering chart structure, templating patterns, multi-environment configuration, dependency management, testing strategies, and distribution workflows for Kubernetes application packaging.
my-app/
├── Chart.yaml # Chart metadata (required)
├── Chart.lock # Dependency lock file (generated)
├── values.yaml # Default configuration (required)
├── values.schema.json # Values validation schema
├── README.md # Chart documentation
├── .helmignore # Packaging exclusions
├── charts/ # Dependency charts
│ └── postgresql-12.0.0.tgz
├── crds/ # Custom Resource Definitions
│ └── my-crd.yaml
├── templates/ # K8s manifest templates (required)
│ ├── NOTES.txt # Post-install instructions
│ ├── _helpers.tpl # Template functions
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── serviceaccount.yaml
│ ├── hpa.yaml
│ └── tests/
│ └── test-connection.yaml
└── files/ # Static files to include
└── config/
└── app.conf
apiVersion: v2
name: my-application
version: 1.2.3 # Chart version (SemVer)
appVersion: "2.5.0" # Application version
description: Production-ready web application chart
type: application # application or library
keywords:
- web
- api
- microservices
home: https://example.com
sources:
- https://github.com/example/my-app
maintainers:
- name: Platform Team
email: platform@example.com
icon: https://example.com/icon.png
kubeVersion: ">=1.24.0-0" # Compatible K8s versions
dependencies:
- name: postgresql
version: "~12.0.0" # Semver range
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
tags:
- database
import-values:
- child: auth
parent: postgresql.auth
annotations:
category: ApplicationServer
licenses: Apache-2.0
Chart types:
application: Standard deployable chartslibrary: Reusable template helpers (not installable)Version constraints:
~1.2.3 (>=1.2.3, <1.3.0), ^1.2.3 (>=1.2.3, <2.0.0)# values.yaml - Production-ready defaults
# Global values (shared with all subcharts)
global:
imageRegistry: docker.io
imagePullSecrets: []
storageClass: ""
# Common labels applied to all resources
commonLabels:
team: platform
cost-center: engineering
# Image configuration
image:
registry: docker.io
repository: mycompany/app
tag: "" # Defaults to .Chart.AppVersion if empty
pullPolicy: IfNotPresent
pullSecrets: []
# Deployment configuration
replicaCount: 3
revisionHistoryLimit: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
# Pod configuration
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
podLabels: {}
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
# Container security context
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
# Service configuration
service:
enabled: true
type: ClusterIP
port: 80
targetPort: http
annotations: {}
sessionAffinity: None
# Ingress configuration
ingress:
enabled: false
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: app.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: app-tls
hosts:
- app.example.com
# Resource management
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
# Autoscaling
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 75
targetMemoryUtilizationPercentage: 80
# Health checks
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
startupProbe:
httpGet:
path: /startup
port: http
initialDelaySeconds: 0
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30
# Persistence
persistence:
enabled: false
storageClass: ""
accessMode: ReadWriteOnce
size: 8Gi
annotations: {}
existingClaim: ""
# Node selection
nodeSelector: {}
tolerations: []
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: my-app
topologyKey: kubernetes.io/hostname
# Service Account
serviceAccount:
create: true
annotations: {}
name: ""
automountServiceAccountToken: false
# Configuration
config:
LOG_LEVEL: info
DATABASE_POOL_SIZE: "10"
CACHE_TTL: "3600"
# Secrets (use external secret management in production)
secrets: {}
# Monitoring
metrics:
enabled: false
serviceMonitor:
enabled: false
interval: 30s
scrapeTimeout: 10s
# Pod Disruption Budget
podDisruptionBudget:
enabled: true
minAvailable: 1
# maxUnavailable: 1
# Network Policy
networkPolicy:
enabled: false
policyTypes:
- Ingress
- Egress
ingress: []
egress: []
values-dev.yaml:
replicaCount: 1
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
config:
LOG_LEVEL: debug
values-production.yaml:
replicaCount: 5
autoscaling:
enabled: true
minReplicas: 5
maxReplicas: 20
resources:
limits:
cpu: 2000m
memory: 2Gi
requests:
cpu: 1000m
memory: 1Gi
config:
LOG_LEVEL: warn
ingress:
enabled: true
podDisruptionBudget:
enabled: true
minAvailable: 2
{
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["image", "service"],
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"description": "Number of pod replicas"
},
"image": {
"type": "object",
"required": ["repository"],
"properties": {
"repository": {
"type": "string",
"pattern": "^[a-z0-9-./]+$"
},
"tag": {
"type": "string"
},
"pullPolicy": {
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"]
}
}
},
"service": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["ClusterIP", "NodePort", "LoadBalancer"]
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
}
}
},
"resources": {
"type": "object",
"properties": {
"limits": {
"type": "object",
"properties": {
"cpu": {"type": "string"},
"memory": {"type": "string"}
}
},
"requests": {
"type": "object",
"required": ["cpu", "memory"],
"properties": {
"cpu": {"type": "string"},
"memory": {"type": "string"}
}
}
}
}
}
}
{{/*
Expand the name of the chart.
*/}}
{{- define "my-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a fully qualified app name.
*/}}
{{- define "my-app.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Chart name and version label.
*/}}
{{- define "my-app.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Common labels
*/}}
{{- define "my-app.labels" -}}
helm.sh/chart: {{ include "my-app.chart" . }}
{{ include "my-app.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- with .Values.commonLabels }}
{{ toYaml . }}
{{- end }}
{{- end -}}
{{/*
Selector labels
*/}}
{{- define "my-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
{{/*
Service account name
*/}}
{{- define "my-app.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{- default (include "my-app.fullname" .) .Values.serviceAccount.name -}}
{{- else -}}
{{- default "default" .Values.serviceAccount.name -}}
{{- end -}}
{{- end -}}
{{/*
Image reference
*/}}
{{- define "my-app.image" -}}
{{- $registry := .Values.global.imageRegistry | default .Values.image.registry -}}
{{- $repository := .Values.image.repository -}}
{{- $tag := .Values.image.tag | default .Chart.AppVersion -}}
{{- if $registry -}}
{{- printf "%s/%s:%s" $registry $repository $tag -}}
{{- else -}}
{{- printf "%s:%s" $repository $tag -}}
{{- end -}}
{{- end -}}
{{/*
Image pull secrets
*/}}
{{- define "my-app.imagePullSecrets" -}}
{{- $secrets := concat (.Values.global.imagePullSecrets | default list) (.Values.image.pullSecrets | default list) -}}
{{- if $secrets }}
imagePullSecrets:
{{- range $secrets }}
- name: {{ . }}
{{- end }}
{{- end }}
{{- end -}}
{{/*
Return true if a ConfigMap should be created
*/}}
{{- define "my-app.createConfigMap" -}}
{{- if or .Values.config .Values.extraConfig -}}
true
{{- end -}}
{{- end -}}
Conditional resource creation:
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "my-app.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}
Multiple conditions:
{{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "my-app.fullname" . }}
spec:
endpoints:
- port: metrics
interval: {{ .Values.metrics.serviceMonitor.interval }}
{{- end }}
if-else chains:
resources:
{{- if .Values.resources }}
{{- toYaml .Values.resources | nindent 2 }}
{{- else if eq .Values.environment "production" }}
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
{{- else }}
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
{{- end }}
Range over lists:
{{- range .Values.extraEnvVars }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- range $key, $value := .Values.config }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
Creating multiple resources:
{{- range .Values.services }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "my-app.fullname" $ }}-{{ .name }}
labels:
{{- include "my-app.labels" $ | nindent 4 }}
service: {{ .name }}
spec:
type: {{ .type | default "ClusterIP" }}
ports:
- port: {{ .port }}
targetPort: {{ .targetPort }}
protocol: TCP
name: {{ .name }}
selector:
{{- include "my-app.selectorLabels" $ | nindent 4 }}
{{- end }}
Indexed loops:
{{- range $index, $replica := until (int .Values.replicaCount) }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "my-app.fullname" $ }}-{{ $index }}
data:
replica-id: {{ $index | quote }}
{{- end }}
String manipulation:
# Quotes
name: {{ .Values.name | quote }}
name: {{ .Values.name | squote }} # Single quotes
# Case conversion
name: {{ .Values.name | upper }}
name: {{ .Values.name | lower }}
name: {{ .Values.name | title }}
# Trimming
name: {{ .Values.name | trim }}
name: {{ .Values.name | trimPrefix "-" }}
name: {{ .Values.name | trimSuffix "-" }}
name: {{ .Values.name | trunc 63 }}
# Replacement
name: {{ .Values.name | replace "." "-" }}
Encoding and hashing:
# Base64 encoding
data:
config: {{ .Values.config | b64enc }}
# SHA256 checksum (for triggering updates)
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
Type conversion:
# Defaults and coalesce
value: {{ .Values.custom | default "default-value" }}
value: {{ coalesce .Values.a .Values.b .Values.c "fallback" }}
# Type assertions
replicas: {{ .Values.replicaCount | int }}
enabled: {{ .Values.enabled | ternary "yes" "no" }}
Logical operators:
{{- if and .Values.enabled (eq .Values.type "web") }}
{{- if or .Values.devMode (eq .Values.env "development") }}
{{- if not .Values.disabled }}
# Chart.yaml
dependencies:
- name: postgresql
version: "~12.0.0"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
tags:
- database
import-values:
- child: auth
parent: postgresql.auth
- name: redis
version: "^17.0.0"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
tags:
- cache
# Update and download dependencies
helm dependency update
# List dependencies
helm dependency list
# Build dependencies from charts/ directory
helm dependency build
Parent values.yaml:
# Configure subchart directly
postgresql:
enabled: true
auth:
username: myapp
password: secret123
database: myapp
primary:
persistence:
size: 10Gi
# Import values from subchart
postgresql.auth: {} # Will receive imported values
# Global values shared with all subcharts
global:
imageRegistry: docker.io
storageClass: fast-ssd
Parent's _helpers.tpl:
{{- define "my-app.postgresql.host" -}}
{{- if .Values.postgresql.enabled -}}
{{- printf "%s-postgresql" (include "my-app.fullname" .) -}}
{{- else -}}
{{- .Values.externalDatabase.host -}}
{{- end -}}
{{- end -}}
Creating a library chart:
# library-chart/Chart.yaml
apiVersion: v2
name: common-templates
version: 1.0.0
type: library
library-chart/templates/_deployment.tpl:
{{- define "common.deployment" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "common.fullname" . }}
labels:
{{- include "common.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "common.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "common.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: {{ .Values.image }}
ports:
- containerPort: {{ .Values.port }}
{{- end -}}
Using library chart:
# Chart.yaml
dependencies:
- name: common-templates
version: "1.0.0"
repository: "https://charts.example.com"
# templates/deployment.yaml
{{- include "common.deployment" . }}
Pre-install hook (database migration):
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "my-app.fullname" . }}-migration
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
metadata:
name: {{ include "my-app.fullname" . }}-migration
spec:
restartPolicy: Never
containers:
- name: migration
image: {{ include "my-app.image" . }}
command:
- /bin/sh
- -c
- |
echo "Running database migrations..."
npm run migrate
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "my-app.fullname" . }}
key: database-url
Post-install hook (smoke tests):
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "my-app.fullname" . }}-smoke-test
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "5"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
backoffLimit: 3
template:
spec:
restartPolicy: Never
containers:
- name: test
image: curlimages/curl:latest
command:
- sh
- -c
- |
until curl -f http://{{ include "my-app.fullname" . }}:{{ .Values.service.port }}/health; do
echo "Waiting for service..."
sleep 5
done
echo "Service is healthy!"
Pre-delete hook (backup):
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "my-app.fullname" . }}-backup
annotations:
"helm.sh/hook": pre-delete
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: backup
image: {{ include "my-app.image" . }}
command: ["/scripts/backup.sh"]
Available hooks:
pre-install, post-installpre-delete, post-deletepre-upgrade, post-upgradepre-rollback, post-rollbacktest (run with helm test)Hook weights: Control execution order (-2147483648 to 2147483647, lower first)
Deletion policies:
before-hook-creation: Delete previous hook before new onehook-succeeded: Delete after successful executionhook-failed: Delete if hook fails# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "my-app.fullname" . }}-test-connection"
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
containers:
- name: wget
image: busybox:latest
command: ['wget']
args: ['{{ include "my-app.fullname" . }}:{{ .Values.service.port }}']
# templates/tests/test-authentication.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "my-app.fullname" . }}-test-auth"
annotations:
"helm.sh/hook": test
spec:
restartPolicy: Never
containers:
- name: test
image: curlimages/curl:latest
command:
- sh
- -c
- |
TOKEN=$(curl -s -X POST {{ include "my-app.fullname" . }}/auth/token -d '{"user":"test"}' | jq -r .token)
curl -f -H "Authorization: Bearer $TOKEN" {{ include "my-app.fullname" . }}/api/protected
# Install and run tests
helm install my-app ./my-app
helm test my-app
# Show test logs
helm test my-app --logs
# Cleanup after tests
helm test my-app --cleanup
# Lint chart for issues
helm lint ./my-app
# Lint with custom values
helm lint ./my-app -f values-production.yaml
# Template rendering (dry-run)
helm template my-app ./my-app
# Template with specific values
helm template my-app ./my-app \
--set replicaCount=5 \
-f values-production.yaml
# Validate against cluster
helm install my-app ./my-app --dry-run --debug
# Schema validation
helm lint ./my-app --strict
# Package chart
helm package ./my-app
# Package with specific version
helm package ./my-app --version 1.2.3
# Package with dependency update
helm package ./my-app --dependency-update
# Sign package
helm package ./my-app --sign --key 'my-key' --keyring ~/.gnupg/secring.gpg
Creating repository index:
# Create index.yaml
helm repo index . --url https://charts.example.com
# Update existing index
helm repo index . --url https://charts.example.com --merge index.yaml
index.yaml structure:
apiVersion: v1
entries:
my-app:
- apiVersion: v2
appVersion: "2.5.0"
created: "2024-01-01T00:00:00Z"
description: Production-ready web application chart
digest: sha256:abcd1234...
name: my-app
urls:
- https://charts.example.com/my-app-1.2.3.tgz
version: 1.2.3
Using repositories:
# Add repository
helm repo add myrepo https://charts.example.com
# Update repository cache
helm repo update
# Search repository
helm repo search myrepo/
# Install from repository
helm install my-app myrepo/my-app --version 1.2.3
# Login to OCI registry
helm registry login registry.example.com
# Package and push
helm package ./my-app
helm push my-app-1.2.3.tgz oci://registry.example.com/charts
# Install from OCI
helm install my-app oci://registry.example.com/charts/my-app --version 1.2.3
# Pull chart
helm pull oci://registry.example.com/charts/my-app --version 1.2.3
# helmfile.yaml
repositories:
- name: bitnami
url: https://charts.bitnami.com/bitnami
- name: ingress-nginx
url: https://kubernetes.github.io/ingress-nginx
# Default values for all releases
helmDefaults:
createNamespace: true
wait: true
timeout: 600
force: false
atomic: true
# Global values
commonLabels:
managed-by: helmfile
environment: {{ .Environment.Name }}
releases:
# PostgreSQL database
- name: postgresql
namespace: database
chart: bitnami/postgresql
version: ~12.0.0
values:
- auth:
username: myapp
database: myapp
existingSecret: postgresql-secret
- primary:
persistence:
size: 50Gi
hooks:
- events: ["presync"]
command: kubectl
args: ["create", "namespace", "database", "--dry-run=client", "-o", "yaml"]
# Application
- name: my-app
namespace: {{ .Environment.Name }}
chart: ./charts/my-app
values:
- ./charts/my-app/values.yaml
- ./environments/{{ .Environment.Name }}/my-app-values.yaml
secrets:
- ./environments/{{ .Environment.Name }}/secrets.yaml
needs:
- database/postgresql
set:
- name: image.tag
value: {{ requiredEnv "IMAGE_TAG" }}
hooks:
- events: ["postsync"]
command: kubectl
args: ["rollout", "status", "deployment/my-app", "-n", "{{ .Environment.Name }}"]
# Ingress controller
- name: ingress-nginx
namespace: ingress
chart: ingress-nginx/ingress-nginx
version: ~4.0.0
condition: ingress.enabled
environments.yaml:
environments:
development:
values:
- environment: development
- ingress.enabled: false
staging:
values:
- environment: staging
- ingress.enabled: true
- replicaCount: 2
production:
values:
- environment: production
- ingress.enabled: true
- replicaCount: 5
- autoscaling.enabled: true
Using environments:
# Deploy to development
helmfile -e development apply
# Deploy to production
helmfile -e production apply
# Diff before applying
helmfile -e staging diff
# Sync specific release
helmfile -e production -l name=my-app sync
{{ .Values.name | quote }}nindent for proper YAML formatting{{- if .Values.optional }} before accessing nested valuesrequired: {{ required "message" .Values.critical }}capabilities.drop: [ALL]runAsNonRoot: trueseccompProfile.type: RuntimeDefaulthelm linthelm template --debughelm testhelm upgrade --dry-runCreating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.