Introduction: Why TypeScript and Kubernetes are the Perfect Pair
Modern web development is unthinkable without two things: static typing and scalable infrastructure. TypeScript provides code reliability at compile time, while Kubernetes (K8s) offers automatic container management, load balancing, and self-healing. In this guide, we will walk through the entire path from a simple TypeScript application to running it in a Kubernetes cluster. You will learn how to package your code into a Docker image, write manifests, and deploy a production-ready service.
Step 1: Preparing a TypeScript Application for Containerization
Before thinking about Kubernetes, you need to ensure your application is ready to run in a container. Let's create a simple Express application in TypeScript. Make sure the project root contains package.json, tsconfig.json, and an entry point src/index.ts.
// src/index.tsimport express, { Request, Response } from 'express';
const app = express();const PORT = process.env.PORT || 3000;
app.get('/health', (_req: Request, res: Response) => { res.json({ status: 'ok', timestamp: new Date().toISOString() });});
app.get('/', (_req: Request, res: Response) => { res.send('Hello from TypeScript on Kubernetes!');});
app.listen(PORT, () => { console.log(`Server running on port ${PORT}`);});An important nuance: in production, we do not use ts-node. Therefore, add a build script to package.json:
"scripts": { "build": "tsc", "start": "node dist/index.js"}After running npm run build, the compiled JavaScript will appear in the dist folder. This is the folder we will copy into the Docker image.
Step 2: Creating a Minimal-Sized Docker Image
For production deployment, image size is critically important. We use a multi-stage build: first compile TypeScript in a container with the full SDK, then copy the result into a lightweight image based on node:18-alpine.
# Dockerfile# Stage 1: BuildFROM node:18-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ci --only=productionCOPY tsconfig.json ./COPY src/ ./src/RUN npm run build
# Stage 2: ProductionFROM node:18-alpineWORKDIR /appCOPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesEXPOSE 3000CMD ["node", "dist/index.js"]Build the image and test it locally:
docker build -t my-ts-app:latest .docker run -p 3000:3000 my-ts-app:latestOpen http://localhost:3000 — you should see the greeting.
Step 3: Kubernetes Manifests — Deployment and Service
Now let's move our image into the cluster. For this, we need two basic manifests: Deployment (manages replicas and updates) and Service (provides network access).
deployment.yaml
apiVersion: apps/v1kind: Deploymentmetadata: name: ts-app-deploymentspec: replicas: 2 selector: matchLabels: app: ts-app template: metadata: labels: app: ts-app spec: containers: - name: ts-app image: my-ts-app:latest imagePullPolicy: IfNotPresent ports: - containerPort: 3000 env: - name: PORT value: "3000" livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 3 periodSeconds: 5service.yaml
apiVersion: v1kind: Servicemetadata: name: ts-app-servicespec: selector: app