In this guide, you'll build a simple three-service C++ application — an API Gateway, a Greeter Service, and a Time Service — and deploy it to a local Kubernetes cluster running on your Windows 11 laptop using Docker Desktop. No cloud account needed.
Architecture
Client → HTTP → API Gateway (port 8080, NodePort)
API Gateway → GET /greet → Greeter Service (port 9001, ClusterIP)
API Gateway → GET /time → Time Service (port 9002, ClusterIP)
Each service runs in its own Kubernetes Pod inside a local Docker Desktop cluster.
Step 1 — Install Prerequisites Install these three tools on your Windows 11 machine before writing any code.
wsl --install3. Verify everything works by opening PowerShell and running:
docker version kubectl get nodesYou should see one node named docker-desktop in Ready state.
Step 2 — Project Structure Create this folder layout at
C:\projects\microservices-demo:
microservices-demo/
├── gateway/
│ ├── main.cpp
│ └── Dockerfile
├── greeter/
│ ├── main.cpp
│ └── Dockerfile
├── time-svc/
│ ├── main.cpp
│ └── Dockerfile
└── k8s/
├── gateway.yaml
├── greeter.yaml
└── time-svc.yaml
time-svc/main.cpp
#include <iostream>
#include <string>
#include <ctime>
#include <cstring>
#include <netinet/in.h>
#include <unistd.h>
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in address{};
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(9002);
bind(server_fd, (sockaddr*)&address, sizeof(address));
listen(server_fd, 5);
std::cout << "Time service listening on port 9002\n";
while (true) {
int client_fd = accept(server_fd, nullptr, nullptr);
char buf[1024] = {};
read(client_fd, buf, sizeof(buf));
time_t now = time(nullptr);
std::string body = std::string("Current time: ") + ctime(&now);
std::string response =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: " + std::to_string(body.size()) + "\r\n"
"\r\n" + body;
write(client_fd, response.c_str(), response.size());
close(client_fd);
}
}
gateway/main.cpp
The gateway reads the URL path and forwards the request to the correct backend. It uses Kubernetes DNS — the service name greeter-svc resolves automatically inside the cluster.
#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
std::string forwardRequest(const std::string& host, int port) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_pton(AF_INET, host.c_str(), &addr.sin_addr);
connect(sock, (sockaddr*)&addr, sizeof(addr));
std::string req = "GET / HTTP/1.0\r\nHost: " + host + "\r\n\r\n";
write(sock, req.c_str(), req.size());
char buf[4096] = {};
read(sock, buf, sizeof(buf));
close(sock);
return std::string(buf);
}
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in address{};
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
bind(server_fd, (sockaddr*)&address, sizeof(address));
listen(server_fd, 5);
std::cout << "Gateway listening on port 8080\n";
while (true) {
int client_fd = accept(server_fd, nullptr, nullptr);
char buf[2048] = {};
read(client_fd, buf, sizeof(buf));
std::string request(buf);
std::string backend_response;
if (request.find("GET /greet") != std::string::npos) {
// "greeter-svc" resolves via Kubernetes DNS
backend_response = forwardRequest("greeter-svc", 9001);
} else if (request.find("GET /time") != std::string::npos) {
backend_response = forwardRequest("time-svc", 9002);
} else {
backend_response = "HTTP/1.1 200 OK\r\nContent-Length: 19\r\n\r\nTry /greet or /time";
}
write(client_fd, backend_response.c_str(), backend_response.size());
close(client_fd);
}
}
Step 4 — Dockerfiles Each service uses a two-stage Docker build: compile on a GCC image, then copy only the binary to a slim runtime image. This keeps the final container very small. greeter/Dockerfile
FROM gcc:13 AS builder WORKDIR /app COPY main.cpp . RUN g++ -O2 -o greeter main.cpp FROM debian:bookworm-slim WORKDIR /app COPY --from=builder /app/greeter . EXPOSE 9001 CMD ["./greeter"]For time-svc/Dockerfile: same file, change
greeter to time-svc and EXPOSE 9001 to EXPOSE 9002.For gateway/Dockerfile: same file, change
greeter to gateway and EXPOSE 9001 to EXPOSE 8080.
Step 5 — Build the Docker Images Open PowerShell inside
microservices-demo/ and build all three images:
docker build -t greeter:v1 ./greeter docker build -t time-svc:v1 ./time-svc docker build -t gateway:v1 ./gatewayThe images stay in Docker Desktop's local registry — Kubernetes can use them directly without pushing to Docker Hub.
Step 6 — Kubernetes YAML Manifests Each service needs two Kubernetes objects: a Deployment (keeps pods running, handles restarts) and a Service (gives pods a stable DNS name inside the cluster). k8s/greeter.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: greeter
spec:
replicas: 1
selector:
matchLabels:
app: greeter
template:
metadata:
labels:
app: greeter
spec:
containers:
- name: greeter
image: greeter:v1
imagePullPolicy: Never
ports:
- containerPort: 9001
---
apiVersion: v1
kind: Service
metadata:
name: greeter-svc
spec:
selector:
app: greeter
ports:
- port: 9001
targetPort: 9001
Create k8s/time-svc.yaml the same way — replace every greeter with time-svc and 9001 with 9002.
k8s/gateway.yaml — uses NodePort so your Windows host can reach it on localhost:30080:
apiVersion: apps/v1
kind: Deployment
metadata:
name: gateway
spec:
replicas: 1
selector:
matchLabels:
app: gateway
template:
metadata:
labels:
app: gateway
spec:
containers:
- name: gateway
image: gateway:v1
imagePullPolicy: Never
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: gateway-svc
spec:
type: NodePort
selector:
app: gateway
ports:
- port: 8080
targetPort: 8080
nodePort: 30080
Step 7 — Deploy to Kubernetes
kubectl apply -f k8s/greeter.yaml kubectl apply -f k8s/time-svc.yaml kubectl apply -f k8s/gateway.yamlCheck that all three pods are running:
kubectl get pods kubectl get servicesYou should see three pods all showing Running status.
Step 8 — Test It!
curl http://localhost:30080/greet # → Hello from the Greeter Service! curl http://localhost:30080/time # → Current time: Sun Apr 19 ... curl http://localhost:30080/ # → Try /greet or /time
Key Concepts You Just Learned
| Concept | What it is in your app |
|---|---|
| Microservice | Each of your 3 C++ programs — independent, single-purpose, separately deployable |
| Container | The Docker image packaging your binary and its Linux dependencies |
| Pod | Kubernetes' smallest unit — wraps your container and gives it an IP |
| Deployment | Ensures your pod keeps running; restarts it automatically if it crashes |
| Service (ClusterIP) | Gives greeter and time-svc a stable DNS name inside the cluster |
| Service (NodePort) | Exposes the gateway to your Windows host on localhost:30080 |
| API Gateway | Single entry point that routes requests to the right microservice |
What to Explore Next
Run
kubectl scale deployment greeter --replicas=3 and watch Kubernetes load balance across 3 pods. Run
kubectl delete pod <pod-name> — Kubernetes will immediately restart it. This is self-healing in action. Create a "joke" or "quote" service and wire it into the gateway on a new route such as
/joke. Add
livenessProbe and readinessProbe fields to your YAML. Kubernetes uses these to decide if a pod is healthy enough to receive traffic. Move hardcoded backend hostnames into Kubernetes ConfigMaps and inject them as environment variables — so your code doesn't need to change when config does.