Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Kubernetes Basics: Deploy Your First App to a K8s Cluster

By Kokil Thapa | Last reviewed: August 2026

Most tutorials on Kubernetes basics: deploy your first app to a K8s cluster assume you already understand containers, networking, and declarative configuration. If you are coming from traditional LAMP stacks or single-server Laravel deployments, that gap causes immediate frustration. This guide bridges that divide by walking through a real deployment workflow using current 2026 tooling, focusing on the specific YAML resources and kubectl commands that actually work in production rather than toy examples.

Before attempting any cluster operations, ensure your local environment matches modern standards. In 2026, you should be running kubectl v1.30+ against a cluster running Kubernetes v1.29 or v1.30. For local development, DevOps automation practices increasingly favor lightweight distributions like k3d or Kind over the heavier Minikube, as they start faster and consume fewer resources on standard laptops. Understanding these foundational tools prevents the most common setup failures I see when developers transition from direct server management to orchestrated environments.

What Are the Core Kubernetes Basics Required Before Deployment?

You cannot effectively debug a deployment if you treat the cluster as a black box. Kubernetes operates on a declarative model: you define the desired state in YAML, and the control plane works continuously to reconcile the actual state with that definition. This differs fundamentally from the imperative scripts used in traditional sysadmin work where you SSH into a server and run commands manually.

Control PlaneAPI ServerSchedulerController Mgretcd StoreWorker NodeKubeletNode AgentKube-proxyNetwork RulesPod (App Instance)Container AContainer BAPI Calls
Kubernetes basics architecture: Control plane components communicate with worker nodes to manage Pod lifecycle and networking

Three primitives matter most for your first deployment:

  • Pods: The smallest deployable unit. A pod wraps one or more containers sharing storage and network namespace. You rarely create pods directly; higher-level controllers manage them.
  • Deployments: Declarative definitions for managing replica sets. They handle rolling updates, rollbacks, and scaling without downtime.
  • Services: Stable network endpoints abstracting dynamic pod IPs. Without a Service, your application is unreachable despite running successfully.

A common mistake among PHP developers accustomed to Apache/Nginx setups is assuming persistent connections or local filesystem writes survive restarts. Kubernetes pods are ephemeral by design. Any data written to the container filesystem vanishes when the pod terminates. For applications like Laravel or WordPress, this means externalizing sessions to Redis, storing uploads in S3-compatible object storage, and treating the database as the sole source of truth. If your Laravel application relies on local file caching or session storage, it will fail intermittently in Kubernetes until refactored.

How Do You Write Production-Ready Deployment and Service YAML?

The YAML manifest is your infrastructure code. Sloppy manifests cause silent failures. Below is a battle-tested template for a typical web application container, annotated with fields that prevent common production issues.

<!-- deployment.yaml -->
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  labels:
    app: web-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - name: app
        image: registry.example.com/web-app:v1.2.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 20
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
        env:
        - name: APP_ENV
          value: "production"
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: password

Several details here distinguish production configs from tutorial examples:

  1. Explicit resource requests and limits: Without these, the scheduler places pods randomly, causing noisy-neighbor problems. Requests guarantee minimum resources; limits cap maximum usage. Always set both.
  2. Liveness and readiness probes: Liveness probes restart hung containers. Readiness probes remove unready pods from service load balancers. Missing probes mean traffic routes to starting or crashed containers, causing user-visible errors.
  3. Immutable image tags: Never use :latest in production. Tag images with semantic versions or Git SHAs. :latest makes rollbacks impossible and creates non-deterministic deployments.
  4. Secrets via secretKeyRef: Never hardcode credentials in deployment YAML. Use Kubernetes Secrets or external secret managers. The manifest above references a pre-existing Secret object.

The corresponding Service manifest exposes the deployment internally:

<!-- service.yaml -->
apiVersion: v1
kind: Service
metadata:
  name: web-app-svc
spec:
  selector:
    app: web-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: ClusterIP

Use ClusterIP for internal services. Only use LoadBalancer or NodePort for ingress-facing services, and prefer Ingress controllers with TLS termination for public traffic. On projects where I've implemented CI/CD pipelines for Kubernetes, we typically commit these manifests to version control alongside application code, enabling audit trails and peer review of infrastructure changes.

What Is the Exact Workflow to Deploy Your First App to a K8s Cluster?

With manifests prepared, execute the deployment using kubectl. This sequence assumes you have cluster access configured and kubectl config current-context points to your target cluster.

1. Validatekubectl apply --dry-run2. Applykubectl apply -f3. Verifyget pods / rollout4. Debuglogs / describeRollback on Failurekubectl rollout undo deployment/web-app
Deployment workflow: validate manifests dry-run, apply to cluster, verify rollout status, and debug or rollback as needed

Follow this exact sequence:

  1. Dry-run validation: Run kubectl apply -f deployment.yaml --dry-run=client to catch syntax errors and schema violations before touching the cluster. Add --server-dry-run to also validate admission controller policies.
  2. Apply manifests: Execute kubectl apply -f deployment.yaml -f service.yaml. Kubernetes creates or updates resources idempotently. Re-running this command is safe and expected during iterative development.
  3. Monitor rollout: Run kubectl rollout status deployment/web-app. This blocks until the deployment succeeds or times out. A hanging rollout indicates probe failures, image pull errors, or resource constraints.
  4. Verify pod health: Check kubectl get pods -l app=web-app. All pods should show Running with ready containers matching your replica count. Statuses like CrashLoopBackOff, Pending, or ImagePullBackOff require investigation.
  5. Inspect logs: Use kubectl logs <pod-name> --previous to see why a container crashed. Current logs show runtime behavior; previous logs reveal startup failures.

If the rollout fails, immediately run kubectl describe deployment web-app and kubectl describe pod <pod-name>. The Events section at the bottom reveals scheduling failures, OOM kills, mount errors, and other issues not visible in logs. In my experience maintaining cloud-hosted applications, 80% of first-deployment failures trace to misconfigured probes, missing secrets, or insufficient resource limits—all diagnosable through describe output.

How Does Kubernetes Compare to Traditional Server Deployments for Web Apps?

Understanding trade-offs prevents adopting Kubernetes for wrong reasons. Many Nepali businesses and agencies ask whether K8s justifies its complexity versus familiar VPS setups.

CriteriaTraditional VPS (Apache/Nginx)Kubernetes Cluster
Setup ComplexityLow — SSH + package managerHigh — cluster provisioning, networking, RBAC
ScalingManual vertical scaling or scripted horizontalAutomatic horizontal pod autoscaling
Self-HealingRequires external monitoring + restart scriptsBuilt-in restart, rescheduling, node eviction
Resource EfficiencyReserved per-server overheadBin-packed across nodes, higher utilization
Operational OverheadLow for single appsSignificant — upgrades, security patches, observability
Cost at Small ScaleLower (Rs 1,500–3,000/month)Higher (managed K8s ~Rs 8,000+/month minimum)
Cost at Large ScaleLinear growth, manual optimizationEconomies of scale, auto-scaling savings

For most small-to-medium Nepali business websites, legal-tech portals, or e-commerce stores under moderate traffic, a well-configured VPS with Deployer or similar tooling remains more cost-effective and maintainable. Kubernetes pays off when you need automatic scaling across multiple services, zero-downtime deployments as a platform feature, or multi-region resilience. Don't adopt K8s because it's trendy; adopt it because your operational pain exceeds its learning curve.

That said, understanding Kubernetes basics: deploy your first app to a K8s cluster builds valuable mental models even if you stay on VPS today. Concepts like health checks, resource boundaries, and declarative configuration improve application design regardless of runtime. Developers who grasp these abstractions write more resilient code and troubleshoot production issues faster, whether on Docker Compose, systemd, or managed Kubernetes.

What Common Mistakes Derail First Kubernetes Deployments?

After watching dozens of developers attempt their first cluster deployment, certain failure patterns recur consistently:

  • Missing resource limits: Pods without limits can consume entire node memory, triggering OOM kills across unrelated workloads. Always define requests and limits based on profiling, not guesses.
  • Incorrect probe paths: Probes hitting authenticated endpoints or heavy database queries cause false negatives. Create dedicated lightweight health endpoints returning 200 OK without dependencies.
  • Ignoring pod disruption budgets: During node maintenance or cluster upgrades, all replicas may terminate simultaneously without PDBs. Define minAvailable or maxUnavailable to preserve availability.
  • Hardcoded configuration: Environment-specific values baked into manifests prevent promotion across dev/staging/prod. Use Kustomize overlays or Helm charts to parameterize configurations cleanly.
  • Neglecting namespace isolation: Deploying everything to default namespace risks accidental cross-environment interference. Create dedicated namespaces with RBAC boundaries per team or environment.
Pod Not Running?Check Pod StatusCrashLoopBackOffPendingImagePullBackOffApp Error / Probe Fail→ kubectl logs→ Fix app or probeScheduling Issue→ kubectl describe→ Check resources/taintsRegistry/Auth Error→ Verify image tag→ Check imagePullSecrets
Debugging decision tree: identify pod failure state and apply targeted diagnostic commands for resolution

When troubleshooting, resist the urge to delete and recreate resources blindly. Kubernetes retains event history and previous container logs precisely for diagnosis. Deleting pods resets this context and often reproduces the same failure. Instead, gather evidence systematically: check events, inspect logs, verify configurations against actual cluster state using kubectl get <resource> -o yaml, then apply targeted fixes.

Moving Beyond Your First Kubernetes Deployment

Successfully completing Kubernetes basics: deploy your first app to a K8s cluster proves you can navigate the toolchain, but production readiness requires additional layers. Implement structured logging with JSON output for aggregation, add Prometheus metrics endpoints for observability, configure network policies to restrict pod-to-pod communication, and establish backup strategies for etcd and persistent volumes. These concerns compound quickly, which is why managed Kubernetes services exist—they abstract control plane operations while leaving workload management to you.

Start simple. Get comfortable with core primitives before adopting service meshes, operators, or GitOps workflows. Each abstraction adds debugging surface area. Master kubectl, understand YAML semantics deeply, and build intuition for how the scheduler and controller manager behave under stress. That foundation makes advanced topics tractable rather than overwhelming.

If you're evaluating whether Kubernetes fits your project's operational reality or need help architecting containerized deployments for Laravel, e-commerce, or legal-tech platforms, reach out to discuss your specific requirements. Practical experience beats theoretical best practices every time.

Frequently Asked Questions

4GB RAM, 2 vCPUs, and 50GB SSD storage.

Managed control planes are often free; you pay only for worker nodes. A basic 2-node setup costs roughly Rs 3,500 to Rs 6,000 per month (USD 25–45) on major cloud providers, excluding egress fees.

Minikube simulates multi-node clusters best for testing complex networking. Use k3s when your production target is lightweight or edge-based, as it matches that runtime environment more closely than Docker Desktop's default Kubernetes implementation.

Deployments manage stateless applications where pods are interchangeable and can be replaced without data loss. StatefulSets maintain stable network identities and persistent storage bindings for each pod replica. In my experience deploying Laravel apps, standard Deployments with external databases suffice for web workloads. Reserve StatefulSets for actual database clusters or message brokers running inside K8s, not for typical PHP application containers that treat storage as ephemeral.

Create an Ingress resource paired with an Ingress Controller like NGINX or Traefik rather than using LoadBalancer Services directly. Configure cert-manager to automatically provision Let's Encrypt TLS certificates via ACME challenges. This keeps SSL termination at the cluster edge while routing traffic internally over HTTP. On production client projects, I always enforce HTTPS redirects at the Ingress level and store certificates as Kubernetes Secrets, avoiding manual renewal workflows entirely.

This indicates the container process exits repeatedly before passing readiness checks. Check logs with kubectl logs --previous to see why prior instances failed. Common causes include missing environment variables, incorrect file permissions on mounted volumes, or application startup errors. For PHP-FPM containers specifically, verify the socket path matches your Nginx configuration and that the www-data user owns the storage directory. Debugging requires inspecting both current and previous container states systematically.

Use ConfigMaps for non-sensitive settings and Secrets for credentials, injecting them as environment variables or volume mounts. Never bake .env files into container images. In Laravel deployments, I mount a shared ConfigMap containing APP_NAME and CACHE_DRIVER while referencing Secrets for DB_PASSWORD and API keys. This allows identical images across staging and production environments. Update configurations via kubectl apply without triggering redeployment, though pods must restart to pick up env var changes.

Use hostPath volumes or the local-path-provisioner StorageClass for simplicity during initial learning. These bind directly to node filesystem directories without requiring NFS or cloud block storage setup. Be aware that hostPath ties data to specific nodes, making it unsuitable for multi-node production. When transitioning to real infrastructure, migrate to CSI drivers like AWS EBS or Longhorn. For practicing Kubernetes basics, avoiding distributed storage complexity lets you focus on core orchestration concepts first.

Implement rolling updates with proper readiness and liveness probes configured. Set maxUnavailable to 0 and maxSurge to 1 in your Deployment strategy to ensure new pods pass health checks before old ones terminate. Define readinessProbe endpoints that verify actual application functionality, not just port availability. For Laravel apps, this means checking /up or a dedicated health route confirming database connectivity. Without accurate probes, Kubernetes routes traffic to containers still initializing caches or warming opcache, causing intermittent 502 errors during deploys.

Technically yes, but I recommend external managed databases for most application teams. Running stateful databases in K8s adds operational complexity around backups, replication, and storage performance that distracts from application deployment goals. If you must self-host, use operators like CloudNativePG or Vitess rather than raw StatefulSets. For learning Kubernetes basics, connecting to an external RDS instance or local Docker database lets you practice pod lifecycle management without risking data corruption during node failures or storage misconfigurations.

Verify NetworkPolicies aren't blocking inter-namespace traffic by temporarily removing restrictions and testing connectivity. Use kubectl exec to run curl or wget from source pods targeting destination services via DNS names like service.namespace.svc.cluster.local. Check CoreDNS logs if name resolution fails. Confirm both pods share compatible label selectors in their respective policies. In troubleshooting production clusters, I've found namespace isolation mistakes cause most cross-service communication failures after initial setup, especially when copying manifests between environments without updating policy references.

Start with 256Mi memory request and 512Mi limit for typical Laravel containers, adjusting based on actual usage from metrics-server or Prometheus. Set CPU requests to 250m and limits to 1000m initially. Requests guarantee scheduling resources while limits prevent noisy neighbors. Monitor OOMKilled events indicating insufficient memory limits. In practice, PHP-FPM workers consume predictable memory per process, so calculate limits based on pm.max_children multiplied by average per-worker usage plus base overhead. Undersized requests cause eviction under node pressure.

Deploy Metrics Server first, then create HorizontalPodAutoscaler resources targeting CPU or custom metrics. Configure scale-up and scale-down stabilization windows to prevent flapping during traffic spikes. For PHP applications, CPU correlates better with request load than memory. Set minReplicas to handle baseline traffic and maxReplicas within node capacity bounds. Test scaling behavior under synthetic load before relying on it in production. Remember that HPA reacts to averaged metrics across replicas, so individual pod hotspots won't trigger scaling until the aggregate threshold crosses defined targets.

Running containers as root, mounting Docker sockets, granting excessive RBAC permissions, and storing secrets unencrypted top the list. Always specify runAsNonRoot and readOnlyRootFilesystem in pod security contexts. Use ServiceAccounts with minimal permissions instead of default tokens. Enable encryption at rest for etcd secrets. Scan images with Trivy before deployment. On client projects, I enforce Pod Security Standards at namespace level to prevent privileged containers accidentally. Treat cluster access like production database credentials—restrict broadly, audit regularly, and rotate tokens periodically.

Map compose services to Deployments, networks to Namespaces or NetworkPolicies, and volumes to PersistentVolumeClaims. Use Kompose for initial manifest generation but refactor output manually afterward. Replace depends_on with init containers or readiness gates since K8s lacks declarative startup ordering. Externalize configuration into ConfigMaps. Expect networking differences—Docker Compose uses bridge networks while K8s relies on CNI plugins with flat pod networking. Validate each service independently before integrating. Migration reveals implicit assumptions in Compose setups that K8s makes explicit, improving long-term maintainability despite higher initial translation effort.

Share this article

Quick Contact Options
Choose how you want to connect me: