Skip to main content
Back to Blog
10 min read

Kubernetes Deployment: A Practical Guide from Zero to Production

Jution Candra Kirana
Senior Tech Educator & Full-Stack Developer
KubernetesDevOpsDocker
Kubernetes Deployment: A Practical Guide from Zero to Production

Kubernetes Deployment: A Practical Guide from Zero to Production

Containers already solved the "it works on my machine but breaks on the server" problem. But once you have more than one container that needs to talk to each other, scale up and down, and stay alive even when something crashes, you need more than a plain docker run. That's where Kubernetes comes in.

This article walks through deploying an application to Kubernetes in practice: from the core concepts, to creating a Deployment, exposing it through a Service, managing environment variables and secrets, scaling, rolling updates, and the troubleshooting steps you'll actually use.

Why Kubernetes, not just Docker

Docker is great for running a single container. But as your application grows, a few questions come up that Docker alone can't answer:

  • If a container dies, who brings it back?
  • If traffic spikes, how do you add more instances without downtime?
  • How do containers find each other on the network?
  • How do you roll out a new version without killing the old one first?

Kubernetes answers all of this through a declarative model: you describe the desired state (for example, "I want 3 replicas of this app running at all times"), and Kubernetes continuously works to keep the actual state matching what you described.

Prerequisites

Before you start, make sure you have:

  • Docker — to build your application image
  • kubectl — the command-line tool for talking to a Kubernetes cluster
  • Access to a Kubernetes cluster (local, like Minikube or Kind, or managed, like GKE, EKS, or AKS)
  • A basic understanding of containers and images

Check your cluster connection with:

bash
kubectl cluster-info
kubectl get nodes

If this returns node information without errors, your cluster is ready to go.

Core concepts you need first

Before writing any YAML, a few terms will keep showing up:

TermWhat it means
PodThe smallest unit in Kubernetes — one or more containers running together
DeploymentManages a set of Pods, keeping the replica count correct and handling updates
ServiceGives a stable network address for reaching a set of Pods
ConfigMap / SecretStores configuration and sensitive data separately from the image
NamespaceA logical isolation boundary for grouping resources within a cluster

Creating a Deployment

A Deployment is the standard way to run stateless applications on Kubernetes. Here's a more complete example than a bare replica count:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: my-app:latest
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "256Mi"
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 20

A few things that make this different from just "run 3 replicas":

  • resources.requests and resources.limits tell the scheduler how much CPU and memory each Pod needs, and stop a single Pod from starving the rest of the node.
  • readinessProbe makes sure traffic is only routed to Pods that are actually ready to serve requests, not ones still starting up.
  • livenessProbe lets Kubernetes automatically restart a container that's hung or deadlocked, even if the process technically still looks "alive."

Apply the configuration to your cluster:

bash
kubectl apply -f deployment.yaml

Check its status:

bash
kubectl get deployments
kubectl get pods -l app=my-app

Exposing the app with a Service

A Deployment alone doesn't make your app reachable. Pods can die and get recreated with new IPs at any time, so you need a Service as a stable address in front of them:

yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  selector:
    app: my-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: ClusterIP

The three Service types you'll use most often:

  • ClusterIP (default) — only reachable from inside the cluster, ideal for internal service-to-service communication
  • NodePort — opens a static port on every node, typically used for testing
  • LoadBalancer — asks the cloud provider to provision an external load balancer, commonly used in production to expose an app to the internet

Managing configuration with ConfigMap and Secret

Same principle covered in an earlier article on Godotenv and Viper configuration shouldn't be baked into the image. In Kubernetes, this is handled through ConfigMap (for non-sensitive data) and Secret (for sensitive data like passwords or API keys):

bash
kubectl create configmap app-config --from-literal=APP_ENV=production
kubectl create secret generic app-secret --from-literal=DB_PASSWORD=supersecret

Then reference them in your Deployment:

yaml
env:
- name: APP_ENV
  valueFrom:
    configMapKeyRef:
      name: app-config
      key: APP_ENV
- name: DB_PASSWORD
  valueFrom:
    secretKeyRef:
      name: app-secret
      key: DB_PASSWORD

Scaling the application

One of Kubernetes' biggest strengths is how easy scaling is, both manually and automatically.

Manual scaling:

bash
kubectl scale deployment my-app --replicas=5

Automatic scaling based on CPU usage, via the Horizontal Pod Autoscaler (HPA):

bash
kubectl autoscale deployment my-app --cpu-percent=70 --min=3 --max=10

With this in place, Kubernetes automatically adds Pods once average CPU usage crosses 70%, and scales back down when traffic drops, no manual intervention needed.

Rolling updates and rollbacks

When you deploy a new version, Kubernetes performs a rolling update by default replacing old Pods with new ones gradually rather than all at once, so the application stays available throughout:

bash
kubectl set image deployment/my-app my-app=my-app:v2
kubectl rollout status deployment/my-app

If the new version turns out to be broken, rolling back is just as simple:

bash
kubectl rollout undo deployment/my-app

Kubernetes also keeps a rollout history you can inspect:

bash
kubectl rollout history deployment/my-app

Monitoring and debugging

A handful of kubectl commands cover most day-to-day monitoring needs:

bash
kubectl get pods
kubectl describe pod <pod-name>
kubectl logs deployment/my-app
kubectl logs deployment/my-app --previous
kubectl top pods

kubectl describe pod is especially useful when a Pod is stuck in Pending or CrashLoopBackOff, the Events section in the output usually points straight to the cause, like an image that failed to pull or insufficient resources on the node.

Common mistakes to avoid

  • Skipping resource requests/limits, one Pod can end up starving the rest of the node's resources.
  • No readiness probe, traffic can get routed to a Pod that isn't ready yet, causing errors right after deployment.
  • Storing secrets in a ConfigMap, ConfigMaps aren't encrypted, whereas Secrets (even though only base64-encoded by default) are at least access-separated and can be integrated with an external secret manager.
  • Using the latest tag in production, makes rollbacks harder since there's no clear version to go back to.

Conclusion

Kubernetes has a steep learning curve at first, but the underlying idea stays consistent: you describe the desired state through Deployments, Services, ConfigMaps, and Secrets, and Kubernetes keeps working to make the actual cluster state match that description.

Start simple a single Deployment with correct resource limits and health checks — before moving on to more advanced features like HPA, Ingress, or a service mesh. A solid foundation early on is worth far more than jumping straight into a complex setup.


Have you deployed to Kubernetes in production before? What was the biggest challenge, networking, resource management, or observability? Share it in the comments.