Deploying an application to Kubernetes involves creating a set of resource objects that describe the desired state of the application — Kubernetes continuously works to make the actual state match this desired state. The core resources for most applications are: a Deployment (manages the desired number of running pod replicas and handles rolling updates), a Service (provides stable DNS and load-balanced access to pods), a ConfigMap (stores non-sensitive configuration data), and a Secret (stores sensitive credentials). This guide covers deploying a full-stack web application to Kubernetes on RHEL 9 using these resources, configuring health checks, performing rolling updates, and scaling workloads.

Prerequisites

  • Kubernetes cluster (kubeadm or k3s) running on RHEL 9
  • kubectl configured with cluster access

Step 1 — Create a Namespace and Secret

# Create an isolated namespace for the application
kubectl create namespace myapp

# Create a Secret for database credentials (base64-encoded automatically)
kubectl create secret generic db-credentials 
    --from-literal=DB_PASSWORD='SecurePass123!' 
    --from-literal=DB_USER='appuser' 
    -n myapp

Step 2 — Deployment Manifest

# /tmp/myapp-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # Extra pods during update
      maxUnavailable: 0  # Zero downtime
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myapp:1.0.0
        ports:
        - containerPort: 3000
        envFrom:
        - secretRef:
            name: db-credentials
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        readinessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 10
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10

Step 3 — Service Manifest

# /tmp/myapp-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: myapp
spec:
  selector:
    app: myapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 3000
  type: ClusterIP  # Use LoadBalancer for cloud, NodePort for bare-metal

Step 4 — Apply and Monitor the Deployment

kubectl apply -f /tmp/myapp-deployment.yaml
kubectl apply -f /tmp/myapp-service.yaml

# Monitor rollout
kubectl rollout status deployment/myapp -n myapp

# View running pods
kubectl get pods -n myapp -o wide

# View logs
kubectl logs -l app=myapp -n myapp --tail=50

Step 5 — Scale and Update

# Scale horizontally
kubectl scale deployment myapp --replicas=5 -n myapp

# Rolling update — change image version
kubectl set image deployment/myapp myapp=myapp:1.1.0 -n myapp
kubectl rollout status deployment/myapp -n myapp

# Roll back if the new version has issues
kubectl rollout undo deployment/myapp -n myapp
kubectl rollout history deployment/myapp -n myapp

Conclusion

Deploying to Kubernetes on RHEL 9 requires understanding three key concepts: Deployments manage pod lifecycles and rolling updates; Services provide stable network access to pods regardless of which physical pods are running; and resource requests/limits prevent a single misbehaving application from starving other workloads on the same cluster. The readinessProbe is critical — without it, Kubernetes sends traffic to pods that have started but are not yet ready to serve requests (during app initialisation), causing request failures during rolling updates.

Next steps: How to Install Helm on RHEL 9, How to Configure Kubernetes Persistent Volumes on RHEL 9, and How to Set Up Kubernetes Ingress on RHEL 9.