DEV Community

Celso Nery
Celso Nery

Posted on

Building an On-Premise Kubernetes Cluster — Part 5: Deploying Your First Container

🇧🇷 Leia a versão em português aqui

In previous parts of this series, we built the cluster from scratch: prepared the environment (Part 1), installed containerd and Kubernetes (Part 2), initialized the control-plane (Part 3), and joined the workers (Part 4). With the cluster up and all nodes in Ready state, it's time to actually put it to work: let's deploy our first application.

In this article, we'll use Nginx as an example — a classic use case for validating that the cluster is working end to end, from pod creation to service exposure.

Organizing the files

First, create a directory to organize this deployment's manifests:

mkdir nginx
cd nginx
Enter fullscreen mode Exit fullscreen mode

Keeping Kubernetes manifests organized in per-application directories is a good practice that makes maintenance and versioning (e.g., with Git) easier as the cluster grows.

Creating the Deployment

A Deployment is the Kubernetes object responsible for managing pod replicas, ensuring the desired number of instances is always running — and handling things like rolling updates and automatic recovery in case of failure.

Create the file nginx-deployment.yaml with the following content:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.0
        ports:
        - containerPort: 80
Enter fullscreen mode Exit fullscreen mode

This manifest defines:

  • 2 replicas of the Nginx pod (replicas: 2), distributed across the available workers;
  • A selector that ties the Deployment to the pods via the app: nginx label;
  • The nginx:1.14.0 image, exposing container port 80.

Applying the Deployment

With the file saved, apply it to the cluster:

kubectl apply -f nginx-deployment.yaml
Enter fullscreen mode Exit fullscreen mode

kubectl will create the Deployment, and from there Kubernetes takes care of scheduling the 2 pods across the available workers.

Checking the Deployment

To confirm the Deployment was created and has the desired number of replicas running:

kubectl get deployments
Enter fullscreen mode Exit fullscreen mode

And, for more complete details (events, conditions, rollout strategy, etc.):

kubectl describe deployment nginx-deployment
Enter fullscreen mode Exit fullscreen mode

If everything's fine, you should see all 2 replicas ready (2/2) in the availability column.

Creating the Service

The Deployment alone isn't enough to access the application from outside the cluster — pods are ephemeral and their IPs change every time they're recreated. That's what the Service is for: a stable access point that routes traffic to the correct pods, based on labels.

Create the file nginx-service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  labels:
    run: nginx-service
spec:
  type: NodePort
  ports:
  - port: 80
    protocol: TCP
  selector:
    app: nginx
Enter fullscreen mode Exit fullscreen mode

Here, the NodePort type is used to expose the service on a port directly accessible through any node's IP in the cluster — a simple and practical option for on-premise environments, without relying on an external load balancer (unlike the LoadBalancer type, more common in cloud providers).

Applying the Service

kubectl apply -f nginx-service.yaml
Enter fullscreen mode Exit fullscreen mode

Checking the Service

kubectl get service
Enter fullscreen mode Exit fullscreen mode

This command shows, among other information, the external port (NodePort) automatically allocated by Kubernetes — usually in the range between 30000 and 32767.

For more details:

kubectl describe service nginx-service
Enter fullscreen mode Exit fullscreen mode

With the port in hand, Nginx can now be accessed through the IP of any node in the cluster (master or workers) on the indicated port — for example: http://10.0.10.100:<nodeport>.

What we validated here

If you managed to access Nginx's default page through the NodePort, this confirms the whole cluster is working correctly end to end:

  • The control-plane is scheduling pods normally;
  • The pod network (CNI) is allowing communication between components;
  • kube-proxy is routing the Service's traffic to the correct pods;
  • The workers are running containers without issues.

Wrap-up

With this, we've covered the essentials for building and validating an on-premise Kubernetes cluster from scratch: environment preparation, component installation, control-plane initialization, worker joining, and the first real application deployment.

From here, natural next steps include topics like persistent storage (Persistent Volumes), Ingress Controllers, configuration and secrets management (ConfigMaps and Secrets), monitoring (Prometheus/Grafana), and cluster backup strategies (like etcd backups). This is left as a suggestion for a possible continuation of this series.

If you've followed all five articles up to here, you now have a functional on-premise Kubernetes cluster, built and validated from scratch — without depending on any cloud provider.

Now, in Part 6 of this series, we’ll spin up the container with our application and understand how everything works.

Continued in Part 6.

Top comments (0)