Kubernetes·6 min read·

Deploying a Stateless Application to k3s with Manifests

Take a container image and turn it into a running workload on k3s using Deployment and Service manifests, then verify it end to end.

NB

Netbay Infrastructure Team

Netbay Engineering

On this page

Once your k3s cluster is up, the first real task is moving a container from "I have an image" to "a running, reachable workload." For a stateless app this is refreshingly simple: a Deployment describes the desired pods, a Service exposes them, and you apply two small YAML files. This walkthrough deploys an nginx webserver, but the pattern is identical for any stateless service — an API, a frontend, or a worker process.

Stateless means the app keeps no required local state; its pods can be deleted, recreated, and scaled freely. That is what makes the Deployment abstraction so clean here.

The Deployment manifest

Declare the desired state: three replicas of an nginx image, with a label the Service can match.

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:1.27
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: 100m
              memory: 64Mi

The Service manifest

A ClusterIP Service gives the pods a stable name and balances traffic across all replicas.

yaml
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

Applying and verifying

Now apply both files and inspect the result. Everything here uses the standard kubectl workflow.

bash
kubectl apply -f deploy.yaml -f service.yaml
kubectl get pods -o wide
kubectl get svc web
kubectl rollout status deployment/web

When the Deployment reaches the Ready state, you should see three running pods and a Service with three endpoints.

The request path

Once the Service exists, traffic flows through it to whichever pods match its selector.

manifest files kube-apiserver scheduler places pods pod web-1 pod web-2 pod web-3 Service web:80

Exposing it outside the cluster

ClusterIP is only reachable inside the cluster. For quick external testing on a single node, you can use a NodePort Service, which exposes the app on a high port of every node.

yaml
apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080

Then reach it at http://NODE_IP:30080. In later posts we will replace NodePort with a proper Ingress, but this is the fastest way to confirm the deployment works.

Takeaway

Deploying a stateless app is two manifests plus apply: a Deployment for the pods and a Service for routing. Because the app holds no state, scaling and replacing pods is trivial and safe. To practice hands-on, spin up a Linux VPS on Netbay, install k3s, and run through this exact workflow — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance