From Zero to Local Kubernetes: Your First App Deployment
This guide takes you from an empty directory to a running application within a local Kubernetes cluster. It's the first step in the "DevOps 101" series, focusing on building a robust understanding of essential DevOps tools like Kubernetes, Helm, and ArgoCD. We'll use a simple Node.js Todo API, todo-ops, as our foundation, with subsequent parts adding databases, configuration management, ingress, and GitOps workflows.
By the end of this article, you will have:
- Developed a basic Node.js + Express Todo API.
- Containerized the API into a Docker image.
- Set up a local Kubernetes cluster using
kind. - Deployed the application using explicit YAML manifests to understand Kubernetes mechanics.
- Accessed the deployed application from your local machine.
1. Building the Node.js Todo API
We start by creating a minimal web application. This app will expose endpoints for managing a list of todo items. For simplicity, we'll store the todos in memory, which will be sufficient for demonstrating containerization and Kubernetes deployment. Later parts of the series will introduce persistent storage.
First, initialize a new Node.js project:
mkdir todo-ops && cd todo-ops
npm init -y
Install the necessary dependencies:
npm install express
Create a file named server.js with the following content:
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
let todos = [];
let nextId = 1;
// GET all todos
app.get('/todos', (req, res) => {
res.json(todos);
});
// POST a new todo
app.post('/todos', (req, res) => {
const { text } = req.body;
if (!text) {
return res.status(400).json({ error: 'Todo text is required' });
}
const newTodo = { id: nextId++, text, completed: false };
todos.push(newTodo);
res.status(201).json(newTodo);
});
// GET a single todo by ID
app.get('/todos/:id', (req, res) => {
const id = parseInt(req.params.id);
const todo = todos.find(t => t.id === id);
if (!todo) {
return res.status(404).json({ error: 'Todo not found' });
}
res.json(todo);
});
// PUT (update) a todo by ID
app.put('/todos/:id', (req, res) => {
const id = parseInt(req.params.id);
const todo = todos.find(t => t.id === id);
if (!todo) {
return res.status(404).json({ error: 'Todo not found' });
}
const { text, completed } = req.body;
if (text !== undefined) todo.text = text;
if (completed !== undefined) todo.completed = completed;
res.json(todo);
});
// DELETE a todo by ID
app.delete('/todos/:id', (req, res) => {
const id = parseInt(req.params.id);
const index = todos.findIndex(t => t.id === id);
if (index === -1) {
return res.status(404).json({ error: 'Todo not found' });
}
todos.splice(index, 1);
res.status(204).send();
});
app.listen(port, () => {
console.log(`Todo API listening at http://localhost:${port}`);
});
To run this locally, execute:
node server.js
You can then test it using tools like curl or Postman. For example, to add a todo:
curl -X POST -H "Content-Type: application/json" -d '{"text": "Learn Kubernetes"}' http://localhost:3000/todos
2. Containerizing the Application with Docker
To run our Node.js app in Kubernetes, it first needs to be packaged into a Docker image. This image will contain our application code, the Node.js runtime, and all its dependencies. This ensures that the application runs consistently across different environments.
Create a file named Dockerfile in the root of your project directory:
# Use an official Node.js runtime as a parent image
FROM node:18-alpine
# Set the working directory in the container
WORKDIR /usr/src/app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install app dependencies
RUN npm install
# Bundle app source
COPY . .
# Make port 3000 available to the world outside this container
EXPOSE 3000
# Define environment variable
ENV NODE_ENV=production
# Run app.js when the container launches
CMD [ "node", "server.js" ]
Now, build the Docker image. Replace your-dockerhub-username with your actual Docker Hub username or any registry you intend to use. For local testing, it's common to prefix with your username.
docker build -t your-dockerhub-username/todo-ops:v1 .
You can test this image locally by running a container:
docker run -p 3000:3000 your-dockerhub-username/todo-ops:v1
This command maps port 3000 on your host machine to port 3000 inside the container, allowing you to interact with the API as before.
3. Setting Up a Local Kubernetes Cluster with kind
kind (Kubernetes IN Docker) is a tool for running local Kubernetes clusters using Docker containers as
