Kubernetes

Deploying the User Service with Kubernetes

By the Domain India teamPublished 10 min read
Knowledge base article
Contents (9 sections)

Once your user (authentication) service runs in a Docker container, Kubernetes can run several copies of it, restart them when they fail, and roll out new versions without downtime. This guide takes the image from Containerizing the User Service with Docker and deploys it to a Kubernetes cluster you run yourself: a registry push, a Secret, a Deployment with health checks and a non-root security context, a Service, an Ingress, and how to update and roll back.

Key takeaways

Push the image to a registry, store MONGODB_URI and JWT_SECRET in a Kubernetes Secret, and describe the service in a Deployment with 2-3 replicas, CPU and memory requests, readiness and liveness probes on /health, and a security context that runs as the non-root node user. Expose it inside the cluster with a Service and to the internet with an Ingress, then apply everything with kubectl apply -f and watch it with kubectl rollout status. Kubernetes needs a server or cluster you control, such as k3s on a VPS; it can't run on shared hosting.

1. What you need

  • A Kubernetes cluster you control. For one server, k3s on a VPS is the simplest option; a managed Kubernetes service from a cloud provider works the same way.
  • kubectl on your computer or the server, configured for that cluster. kubectl get nodes should list your nodes as Ready.
  • The user-service image built from the Docker guide: node:24-slim, npm ci --omit=dev, running as the node user, listening on 0.0.0.0 and the port in PORT (8080), with a GET /health route.
  • A container registry the cluster can pull from, such as GitHub Container Registry or Docker Hub.
  • MongoDB the pods can reach: a managed MongoDB service, or a database you run yourself.
Kubernetes needs your own server or cluster

Kubernetes can't run on shared hosting (cPanel, DirectAdmin, Webuzo or Windows). It needs root access, its own container runtime and kernel features that shared hosting does not allow. Use a VPS, where you install and manage the cluster yourself, or skip Kubernetes and deploy a single service to the App Platform.

2. Push the image to a registry

Kubernetes nodes pull images from a registry, so tag the image with the registry address and push it. Use a version tag rather than latest, so every rollout points at an exact build and you can roll back.

bash
docker build -t ghcr.io/your-org/user-service:1.0.0 .
docker push ghcr.io/your-org/user-service:1.0.0

If the registry is private, give the cluster read-only credentials:

bash
kubectl create namespace user-service
kubectl -n user-service create secret docker-registry regcred \
  --docker-server=ghcr.io \
  --docker-username=YOUR_USER \
  --docker-password=YOUR_READ_ONLY_TOKEN

3. Store configuration and secrets

Keep the database address and signing keys out of the image and out of your manifests. Put them in a Secret, created from the same .env file you used with docker run --env-file:

bash
kubectl create namespace user-service   # skip if you created it in step 2
kubectl -n user-service create secret generic user-service-env \
  --from-env-file=.env

Kubernetes Secrets are only base64-encoded, not encrypted, in the manifest. Don't commit Secret YAML to Git; limit who can read Secrets with RBAC, and consider encryption at rest or a tool such as Sealed Secrets or External Secrets for production.

4. Write the Deployment

Save this as user-service.yaml. It runs three replicas, sends traffic only to pods that pass their health check, and locks each container down.

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
  namespace: user-service
  labels:
    app: user-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  template:
    metadata:
      labels:
        app: user-service
    spec:
      imagePullSecrets:
        - name: regcred          # remove if the image is public
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000          # the "node" user in official Node.js images
        runAsGroup: 1000
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: user-service
          image: ghcr.io/your-org/user-service:1.0.0
          ports:
            - name: http
              containerPort: 8080
          env:
            - name: PORT
              value: "8080"
            - name: NODE_ENV
              value: production
          envFrom:
            - secretRef:
                name: user-service-env
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              memory: 256Mi
          readinessProbe:
            httpGet:
              path: /health
              port: http
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: http
            initialDelaySeconds: 15
            periodSeconds: 20
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
          volumeMounts:
            - name: tmp
              mountPath: /tmp
      volumes:
        - name: tmp
          emptyDir: {}
SettingWhy it matters
replicas: 3 and maxUnavailable: 0New pods must be ready before old ones stop, so an update causes no downtime
readinessProbeA pod receives traffic only while /health answers, for example after it has connected to MongoDB
livenessProbeKubernetes restarts a container that stops answering
resourcesRequests let the scheduler place pods sensibly; the memory limit stops one leak from starving the node
runAsNonRoot and runAsUser 1000Matches the USER node line in the Dockerfile; the pod refuses to start as root
readOnlyRootFilesystem with /tmpThe app can't modify its own code; /tmp stays writable for libraries that need it

Size the requests and limits from real usage: kubectl top pods shows it once metrics-server is running (k3s includes it). The Docker image's own HEALTHCHECK is ignored by Kubernetes; the probes replace it.

5. Add a Service and an Ingress

A Service gives the pods one stable internal address and load-balances across them. An Ingress routes a public hostname to that Service. Add both to the same file, separated by ---:

yaml
---
apiVersion: v1
kind: Service
metadata:
  name: user-service
  namespace: user-service
spec:
  selector:
    app: user-service
  ports:
    - name: http
      port: 80
      targetPort: http
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: user-service
  namespace: user-service
spec:
  ingressClassName: traefik      # k3s default; use nginx or your controller's class
  rules:
    - host: api.yourdomain.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: user-service
                port:
                  name: http

Point an A record for api.yourdomain.com to your server's public IP wherever your domain's DNS is managed. For HTTPS, add a tls section with a certificate from cert-manager and Let's Encrypt, or use the Traefik certificate resolver shown in the k3s guide.

6. Deploy and check

  1. Apply the manifests.
    kubectl apply -f user-service.yaml. Kubernetes creates or updates everything in the file; running it again is safe.
  2. Wait for the rollout.
    kubectl -n user-service rollout status deployment/user-service returns when all replicas are ready.
  3. Check the pods.
    kubectl -n user-service get pods -o wide should show three pods Running with READY 1/1.
  4. Read the logs.
    kubectl -n user-service logs deploy/user-service shows one pod's output; add -f to follow it.
  5. Test before going public.
    kubectl -n user-service port-forward svc/user-service 8080:80, then curl -i http://localhost:8080/health from the same machine should return 200 ok.
  6. Test the public route.
    Once DNS points at the server, curl -i https://api.yourdomain.com/health.

If a pod is stuck, kubectl -n user-service describe pod POD_NAME shows the reason at the bottom:

  • ImagePullBackOff: wrong image name or tag, or missing registry credentials.
  • CrashLoopBackOff: the app exits on start; read kubectl -n user-service logs POD_NAME --previous, often a missing variable in the Secret.
  • Running but not Ready: /health isn't answering on port 8080 at 0.0.0.0.
  • CreateContainerConfigError: the Secret named in envFrom doesn't exist in that namespace.

7. Update and roll back

Build and push a new tag, then point the Deployment at it:

bash
docker build -t ghcr.io/your-org/user-service:1.1.0 .
docker push ghcr.io/your-org/user-service:1.1.0
kubectl -n user-service set image deployment/user-service \
  user-service=ghcr.io/your-org/user-service:1.1.0
kubectl -n user-service rollout status deployment/user-service

If the new version misbehaves, kubectl -n user-service rollout undo deployment/user-service returns to the previous one. For a lasting record, change the tag in user-service.yaml and apply the file, so Git always matches what runs. When the secret values change, recreate the Secret and run kubectl -n user-service rollout restart deployment/user-service, because running pods don't reload environment variables.

For CI-driven deploys, see GitHub Actions CI/CD to a VPS; to package these manifests for reuse, see Helm charts for beginners.

8. Running this on Domain India

OptionKubernetes?Best for
Shared hosting (cPanel, DirectAdmin, Webuzo, Windows)NoWebsites and PHP apps; containers can't run here
App PlatformNo, it runs your app for youOne service deployed from GitHub or with a deploy token, without managing a cluster
VPSYes, you install itYour own self-managed k3s node or cluster with root access

A Domain India VPS is self-managed: you install k3s, and you are responsible for updates, the firewall and backups. A single-node k3s cluster with this service and its system components fits comfortably on a VPS with 4 GB of RAM; add more memory if you also run MongoDB, monitoring or other services on the same node. Open ports 80 and 443 in the VPS firewall for the Ingress, and keep the Kubernetes API (6443) closed to the internet or limited to your own IP.

VPS Basic
₹1,105.30/mo + GST
  • 2 vCPU
  • 4 GB DDR4 RAM
  • 128 GB NVMe SSD Storage
  • 3 TB Monthly Bandwidth
See plan details

If you only need to run this one service, the App Platform is simpler: Node.js apps are detected automatically, you deploy with Deploy Now or a deploy token, and there is no cluster to maintain. MongoDB is not included there, so use a hosted MongoDB service and test the connection from your app. See Getting started with the App Platform.

Frequently asked questions

Can I run Kubernetes on Domain India shared hosting?

No. Kubernetes needs root access, a container runtime and kernel features that shared hosting does not provide. Use a Domain India VPS, where you install a distribution such as k3s yourself, or deploy the service to the App Platform instead.

How much RAM does a small k3s cluster need?

k3s itself runs in well under 1 GB, but a practical single-node cluster running an API service, its system components and some headroom is comfortable on 4 GB of RAM. Running MongoDB, monitoring or several services on the same node needs more.

Why is my pod in ImagePullBackOff?

The node can't pull the image. Check that the image name and tag exist in the registry, and for a private registry that the imagePullSecrets entry names a docker-registry Secret in the same namespace with valid read-only credentials.

Why does my pod keep restarting with CrashLoopBackOff?

The container starts and exits. Run kubectl logs on the pod with --previous to see the last error. The usual causes are a missing environment variable in the Secret, a database the pod can't reach, or the app listening on the wrong port or address.

How do I pass MONGODB_URI and JWT_SECRET to the pods?

Create a Kubernetes Secret, for example with kubectl create secret generic user-service-env --from-env-file=.env, and reference it with envFrom in the Deployment. Never put secret values in the image or commit them to Git.

How do I roll back a bad deployment?

Run kubectl rollout undo deployment/user-service in the service's namespace. Kubernetes returns to the previous version using the same rolling update, so the service stays available while it switches back.

Do I need Kubernetes for one small service?

Usually not. Kubernetes pays off when you run several services or need automatic restarts and rolling updates across more than one copy. For a single Node.js service, Docker Compose on a VPS or the App Platform is simpler to run and maintain.

Ready to run your own cluster? Compare VPS plans, set up k3s on a VPS, look at the App Platform if you'd rather not manage a cluster, or open a support ticket if you're not sure which fits.

Run Kubernetes on your own VPS

Root access on a self-managed VPS, so you can install k3s and run your containers the way you want.

See VPS plans

Was this article helpful?

Your answer helps us decide what to improve next.

Still need help? Open a support ticket and our team will reply.

Prefer an app? Add this site to your home screen.Get the app