Loading repository data…
Loading repository data…
AmrMadkour / repository
Small .NET + TypeScript app containerized with Docker and deployed to a local Kubernetes cluster (minikube) — a hands-on proof of core Docker and K8s concepts (Deployments, Services, ConfigMaps, readiness probes).
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
A hands-on POC for learning Docker and Kubernetes: a minimal .NET API and a plain TypeScript frontend, built to be containerized and deployed to a local Kubernetes cluster. See docs/docker-k8s-poc-spec.md for the full milestone plan.
Deliberate simplifications, to keep the focus on Docker/K8s rather than app complexity: no database/persistence, no frontend framework, no CI/CD, no Ingress, no production-grade security hardening.
API (from repo root):
dotnet run --project src/NotesApi
Listens on http://localhost:5142 (see src/NotesApi/Properties/launchSettings.json).
Frontend (from repo root):
cd frontend
npm install
npm run dev
Runs on http://localhost:5173 and calls the API at the URL in frontend/.env (VITE_API_URL).
Individually, per service:
docker build -t notesapi ./src/NotesApi
docker run --rm -p 8080:8080 notesapi
docker build --build-arg VITE_API_URL=http://localhost:8080 -t notes-frontend ./frontend
docker run --rm -p 8081:80 notes-frontend
Together, via docker-compose.yml (repo root):
docker compose up -d --build
docker compose ps
docker compose down
API is reachable at http://localhost:5000, frontend at http://localhost:3000. The frontend's VITE_API_URL is baked in at build time (see docker-compose.yml), so it must point to a host-reachable address, not a compose service name.
Manifests live in k8s/: a Deployment + Service per component, plus a ConfigMap for the frontend's API URL. Requires a local cluster (minikube or kind) — examples below use minikube.
minikube start
docker build -t notesapi:latest ./src/NotesApi
docker build --build-arg VITE_API_URL=http://placeholder:30080 -t notes-frontend:latest ./frontend
minikube image load notesapi:latest
minikube image load notes-frontend:latest
kubectl apply -f k8s/
The frontend reads its API URL from the ConfigMap at container startup (not from the build-time arg), so patch it with a real reachable address once the API Service is up:
kubectl create configmap notes-frontend-config \
--from-literal=API_URL=http://$(minikube ip):30080 \
--dry-run=client -o yaml | kubectl apply -f -
kubectl rollout restart deployment/notes-frontend
Windows/macOS with the
dockerdriver: the minikube IP above isn't reachable from the host — Docker Desktop runs the cluster inside an isolated VM. Useminikube service notesapi --url/minikube service notes-frontend --urlinstead to get host-reachable tunnel URLs (each opens a foreground process that must stay running), and use the API tunnel URL in the ConfigMap patch instead of$(minikube ip).
Teardown:
kubectl delete -f k8s/ # remove the deployed resources, keep the cluster
minikube stop # stop the cluster, preserve its state
minikube delete # fully remove the cluster
curl -i http://localhost:5142/health # local dotnet run
curl -i http://localhost:5000/health # docker compose
curl -i http://localhost:5000/notes
curl -i http://$(minikube ip):30080/health # k8s (or the minikube service --url tunnel on Windows/macOS)
Then open the frontend in a browser, confirm the notes list loads, and submit the "Add note" form.
The frontend's fetch calls always run in the browser, never inside a container/pod network. That single fact shapes every mode below: API_URL must always be an address the browser can reach, never a Docker service name or in-cluster DNS name (those only work for server-to-server calls, which this app doesn't make).
No containers (dotnet run / npm run dev)
http://localhost:5142 (Kestrel), frontend dev server on http://localhost:5173 (Vite). No routing layer — the browser calls the API directly using VITE_API_URL from frontend/.env. Cross-origin (different ports) works only because the API's CORS policy is wide open (AllowAnyOrigin).dotnet NotesApi.dll) behind a process manager and the built frontend (npm run build output) behind a static file server (nginx, etc.). Rebuild the frontend with VITE_API_URL pointing at the API's public address, and lock CORS down to that specific origin instead of AllowAnyOrigin.Standalone Docker (no compose, no k8s)
-p host:container mapping (8080 API, 8081 frontend). The containers don't need a shared network — the browser reaches the API purely through the host-published port, baked into the frontend image via the VITE_API_URL build arg.docker build/docker run commands work unchanged — just build the frontend with VITE_API_URL=http://<vm-public-ip-or-domain>:8080 instead of localhost, and open that port in the VM's firewall/security group.docker stop <container-id-or-name>
docker rm <container-id-or-name>
docker rmi notesapi notes-frontend # only if the images are no longer needed
Docker Compose
api and frontend share a compose-generated network and could reach each other by service name for server-to-server calls — but the frontend never makes those, so it still goes through the host-published port (http://localhost:5000), same as standalone Docker.docker compose up -d --build directly on the VM; set VITE_API_URL in frontend.build.args to the VM's public address, and open the published ports (5000, 3000) in the firewall.docker compose down # stop + remove containers and network, keep images
docker compose down --rmi local # also remove the images compose built
Kubernetes
NodePort Service, pinned to 30080. On Windows/macOS with the docker driver that NodePort isn't directly host-reachable (see the tunnel workaround above).image: fields updated accordingly, since minikube image load only works locally; (2) the frontend Service would typically be type: LoadBalancer for a real public IP/domain, and the ConfigMap's API_URL would point at that address instead of a minikube IP/tunnel.kubectl delete -f k8s/ # remove the deployed resources, keep the cluster running
minikube stop # stop the cluster (local only), preserve its state
minikube delete # fully remove the cluster (local only)
src/NotesApi/ ASP.NET Core minimal API (in-memory notes store)
frontend/ TypeScript + Vite frontend
docs/ Milestone spec for this POC
k8s/ Kubernetes manifests (Deployments, Services, ConfigMap)
docker-compose.yml API + frontend orchestration
All milestones in the spec doc are complete: local dev, Dockerfiles, docker-compose, Kubernetes manifests, and the hands-on cluster exercises (pod deletion, scaling, ConfigMap rollout, readiness-probe failure). This POC is intentionally scoped as-is — see the spec's non-goals (no database, no CI/CD, no Ingress) for what's deliberately out of scope.