This project is a hands-on example of how to run a Flask API with a Postgres database using Kubernetes locally, with Kind.
We’ll see how to create the cluster, what each manifest is for, and how to use the API with curl (or Python, if you prefer).
- Build the Docker image locally:
docker build -t hello-api:latest api/- Load the image into the Kind cluster:
kind load docker-image hello-api:latest --name project-testTip:
The cluster name (project-test) should be the same as the one you used when creating Kind.
If you don’t remember, runkind get clusters.
Done! Now Kind’s Kubernetes will use your local image instead of trying to pull it from Docker Hub.
After that, just apply the manifests as usual:
kubectl apply -f k8s/manifests/First, let’s create the local Kubernetes cluster using Kind.
There’s a script ready for that:
./k8s/start-cluster.shWhat does this script do?
- Deletes the old cluster (if it exists)
- Creates a new cluster with the right ports mapped (including 8000 and 30080)
- Shows if the cluster started up fine
Tip: The file
k8s/kind-config.yamlis already set up to expose the necessary ports.
This project comes with ArgoCD set up for GitOps.
ArgoCD keeps an eye on your GitHub repo and automatically applies any changes you make to the Kubernetes manifests.
How it works here:
- The folder
argocd/has the ArgoCD Application manifest (app.yaml). - When you run
start-cluster.sh, ArgoCD is installed and configured. - Any change you push to the manifests in this repo will be picked up and applied by ArgoCD automatically.
To access the ArgoCD UI locally:
kubectl port-forward svc/argocd-server -n argocd 8081:443Then open https://localhost:8081 in your browser.
Login: admin
Password: (the script will print the initial password in the terminal, highlighted between asterisks)
The initial ArgoCD admin password is generated automatically and will be shown in your terminal after running
start-cluster.sh.
ArgoCD Image Updater is a tool that automates the process of updating container image tags in your Kubernetes manifests.
It watches your container registries (like Docker Hub) for new image versions and, when it finds a new tag (for example, a new release of your API), it automatically updates your manifests in the Git repository and triggers a new deployment via ArgoCD.
In short:
You push a new image to Docker Hub → Image Updater detects it → updates the manifest in Git → ArgoCD deploys the new version automatically.
This is true GitOps: your cluster state always matches what’s in Git, and your images are always up-to-date.
-
The folder
argocd/contains:app.yaml: The ArgoCD Application manifest, with annotations for the Image Updater.image-updater.secret.yaml: Secret with configuration for the Image Updater (template, see below).repo-credentials.yaml: Secret with credentials for ArgoCD to access your GitHub repo (template, see below).
-
The
app.yamlmanifest is annotated to tell Image Updater which image to watch and how to update it. -
The Image Updater is installed and runs in the
argocdnamespace, watching for new image tags and updating the manifests in your GitHub repo automatically.
Never commit your real GitHub token in the repository!
Instead, version only the template files and inject the real values at deploy time.
apiVersion: v1
kind: Secret
metadata:
name: argocd-image-updater-config
namespace: argocd
stringData:
log.level: debug
registries.conf: |
registries:
- name: Docker Hub
prefix: docker.io
api_url: https://registry-1.docker.io
git.config: |
commit_message_template: "chore: update image to {{.NewTag}}"
author_name: "ArgoCD Image Updater"
author_email: "[email protected]"
git-credentials: |
https://bodanesemateus:[email protected]apiVersion: v1
kind: Secret
metadata:
name: repo-github-k8s-credentials
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
stringData:
type: git
url: https://github.com/bodanesemateus/k8s-kind-cluster.git
username: $YOUR_GITHUB_USERNAME
password: $YOUR_GITHUB_TOKEN
log.level: debugReplace $YOUR_GITHUB_TOKEN and $YOUR_GITHUB_USERNAME.
- Check the logs of the Image Updater pod:
kubectl logs -n argocd deployment/argocd-image-updater- When you push a new image to Docker Hub, the Image Updater should detect it, update the manifest in your repo, and ArgoCD will deploy the new version.
This manifest spins up your Flask API container in the cluster.
It makes sure there’s always at least 1 pod running the API.
There’s also an initContainer that waits for the database to be ready before starting the API (avoids connection errors).
This one spins up the Postgres container.
It also makes sure the database is always available in the cluster.
This service is of type NodePort, meaning it exposes the API outside the cluster, mapping port 8000 from the pod to port 30080 on your computer (host).
So you can access the API at http://localhost:30080/ or, if you set up hostPort, directly at http://localhost:8000/.
This one is ClusterIP, so it’s only accessible inside the cluster.
The API uses this service to talk to the database via DNS (postgres-service:5432).
Once everything’s running, you can interact with the API using curl or any HTTP client.
curl http://localhost:8000/healthExpected response:
{"status": "healthy", "database": "connected"}curl http://localhost:8000/Response:
{"message": "Hello World from Kubernetes!"}curl -X POST http://localhost:8000/clients \
-H "Content-Type: application/json" \
-d '{"name": "João Silva", "email": "[email protected]"}'Response:
{"message": "Client successfully created", "id": 1}curl http://localhost:8000/clientsResponse:
{
"clients": [
{
"id": 1,
"name": "João Silva",
"email": "[email protected]",
"created_at": "2025-05-30T12:34:15.123456"
}
// ...other clients
]
}curl -X POST http://localhost:8000/clients \
-H "Content-Type: application/json" \
-d '{"name": "João Silva", "email": "[email protected]"}'Response:
{"error": "Email already exists"}curl -X POST http://localhost:8000/clients \
-H "Content-Type: application/json" \
-d '{"name": "No Email"}'Response:
{"error": "Invalid input: name and email are required"}If you want to run all the tests at once, there’s a Python script ready:
Just run:
python3 test_api.pyThis script does:
- Health check
- Hello World
- Creates several clients
- Lists all clients
- Tests duplicate email error
- Tests invalid data error
- If you change
kind-config.yaml, always runstart-cluster.sh! - If you change the API code, rebuild the image, do the
kind load docker-image ...and re-apply the deployment. - If you get a database connection error, check if the database pod is running and if the
initContaineris in the API deployment.
Any questions, just open an issue or DM me!
Happy testing and happy APIs!