Deploy with Raw YAML (Alternative)
Raw Kubernetes YAML is the alternative to Helm for cases where a full chart is overkill β one-off jobs, quick debugging, simple internal tools, or when learning the platform. For persistent production workloads, Helm + ArgoCD is always preferred.
When to Use Raw YAML vs Helmβ
| Use Case | Raw YAML | Helm |
|---|---|---|
| Production app, multiple envs | β | β |
| One-off batch job | β | β |
| Quick debug pod | β | β |
| Simple internal tool (1 env) | β | optional |
| App with DB + cache dependencies | β | β |
| Needs rollback history | β | β |
| Managed by ArgoCD | β (possible) | β (preferred) |
Complete Deployment Manifestβ
A single file covering all standard resources:
# myapp.yaml
---
apiVersion: v1
kind: Namespace
metadata:
name: myteam-staging
labels:
team: myteam
environment: staging
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: myteam-staging
labels:
app: myapp
version: "1.3.0"
spec:
replicas: 2
selector:
matchLabels:
app: myapp
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0 # zero-downtime deploy
template:
metadata:
labels:
app: myapp
version: "1.3.0"
spec:
imagePullSecrets:
- name: harbor-registry-secret
containers:
- name: myapp
image: harbor.local/myteam/myapp:1.3.0
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: staging
- name: LOG_LEVEL
value: debug
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health/live
port: 3000
initialDelaySeconds: 15
periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: myteam-staging
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 3000
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
namespace: myteam-staging
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: myapp.staging.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
number: 80
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp
namespace: myteam-staging
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 2
maxReplicas: 8
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Applyβ
kubectl apply -f myapp.yaml
# Watch rollout
kubectl rollout status deployment/myapp -n myteam-staging
# Verify
kubectl get all -n myteam-staging
Update Image Tagβ
kubectl set image deployment/myapp \
myapp=harbor.local/myteam/myapp:1.3.1 \
-n myteam-staging
kubectl rollout status deployment/myapp -n myteam-staging
Rollbackβ
# Rollback to previous version
kubectl rollout undo deployment/myapp -n myteam-staging
# Rollback to a specific revision
kubectl rollout history deployment/myapp -n myteam-staging
kubectl rollout undo deployment/myapp --to-revision=3 -n myteam-staging
Kustomize β Environment Overrides Without Helmβ
Kustomize is built into kubectl and allows YAML overlays per environment without a template engine:
k8s/
βββ base/
β βββ kustomization.yaml
β βββ deployment.yaml
β βββ service.yaml
β βββ ingress.yaml
βββ overlays/
βββ staging/
β βββ kustomization.yaml
β βββ patch-replicas.yaml
βββ prod/
βββ kustomization.yaml
βββ patch-replicas.yaml
base/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- ingress.yaml
overlays/staging/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../../base
namespace: myteam-staging
images:
- name: harbor.local/myteam/myapp
newTag: "1.3.0"
patches:
- path: patch-replicas.yaml
overlays/staging/patch-replicas.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 1
Deploy:
# Staging
kubectl apply -k k8s/overlays/staging
# Production
kubectl apply -k k8s/overlays/prod
Raw YAML Limitations vs Helmβ
β No release history (no helm history)
β No atomic rollback (manual undo)
β No dependency management (DB, Redis)
β No values templating for multiple envs
β No chart versioning in Harbor
β Simpler to understand
β No templating syntax to learn
β Good for one-off resources
β ArgoCD can manage raw YAML too (just point to the folder)
Harbor Registry Secret (required for both paths)β
Before pulling from Harbor, create the pull secret in each namespace:
kubectl create secret docker-registry harbor-registry-secret \
--docker-server=harbor.local \
--docker-username=myteam-robot \
--docker-password=<robot-token> \
--namespace myteam-staging
kubectl create secret docker-registry harbor-registry-secret \
--docker-server=harbor.local \
--docker-username=myteam-robot \
--docker-password=<robot-token> \
--namespace myteam-prod