This code sets up a web app with at least 2 replicas and serve its content behind a load balancer via HTTPS.
- ensure you are on a linux-based system with docker available; preferably ubuntu 24.04;
- ensure you can use
sudoand you havemakeinstalled as well; - run
make startand wait until the details of the load balancer show up.
One-liner? curl -fsSL https://get.docker.com | sudo bash -; sudo apt install -y make; make start
This setup and all its deployments were tested on the following environment:
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.4 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo$ uname -a
Linux my-instance 6.8.0-57-generic #59-Ubuntu SMP PREEMPT_DYNAMIC Sat Mar 15 17:40:59 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux$ docker version
Client: Docker Engine - Community
Version: 29.2.1
API version: 1.53
Go version: go1.25.6
Git commit: a5c7197
Built: Mon Feb 2 17:17:26 2026
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 29.2.1
API version: 1.53 (minimum version 1.44)
Go version: go1.25.6
Git commit: 6bc6209
Built: Mon Feb 2 17:17:26 2026
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: v2.2.1
GitCommit: dea7da592f5d1d2b7755e3a161be07f43fad8f75
runc:
Version: 1.3.4
GitCommit: v1.3.4-0-gd6d73eb8
docker-init:
Version: 0.19.0
GitCommit: de40ad0$ minikube version
minikube version: v1.38.1
commit: c93a4cb9311efc66b90d33ea03f75f2c4120e9b0It's not the scope of this repository to setup your NTP, but please consider doing it (though, it's not the scope here; explore yourself). Curious to hear how to setup it securely? Try this article and adapt it to your necessities.
The hardware, virtual or not, should be similar to the following:
- CPU: 2
- DISK: 16 GB
- NETWORK: 1 NIC
- RAM: 8 GB
The resources used by
minikubewill be restricted to not use 100% of server's hardware.
- docker
- haproxy
- jq
- kubectl
- minikube
- mkcert
- nginx
- openssh
To begin with, this code was signed using GPG and its authenticity can be prooved.
- Why creating a SSH tunnel to the minikube? - That could be running in a remote machine; the channel is definitely safer over SSH.
- TLS is not necessay on both ends, right? - That's correct, though we would advocate for a clear and safer traffic between the proxy and the "origin servers" (similar to what cloudflare and other CDN providers offer).
- I can not access the resources with TLSv1.1 or even 1.2! - It was a requirement here to server the state of the art for the security of this deployment and its channels, and TLSv1.3 is obligatory to use it.
- To build and setup the containers we picked LTS (long-term support) releases, correct? - Yes, one would like to follow stable and maintained versions of upstream softwares to keep thigns sharp (they receive CVE patches too).
- Can the NTP mentioned before be set and configured in a secure way? - Yes, the openntpd project allows it easily; it relies on HTTPS from any desired reference and you can set the clock via TLS.
- We are setting a CPU and memory limit to minikube; cpu=2, mem=4gb;
- The pod is also limited to use resources in a similar way, inferiror to what we gave to minikube, of course;
- Containers are not running with root privileges;
- Pod Security Standards are in place with baseline policy, providing user friendly audit annotation logging of what the namespace of the web app is running;
- System and filesystem capabilities are restricted - same for privilege escalation;
- AppArmor is running and filtering out (denying) restricted accesses based on profiles;
- Kubernetes sets by default a NoSwap usage for its pods - we are leaving this as is;, though disabling the system's swap usage in parallel while we set the environment up;
- Unnecessary kernel modules (e.g.: dccp, and sctp) are disabled from loading and blocked permanently via modprobe's config file;
- It's not extrictly necessary to expose the server to the public Internet, as you may use an argo tunnel or onion service to serve the contents of the web application.
There was no direct contact of use of GAI to produce this work; it is mainly formed by pieces of different experiences and archived projects put all together.
Before proceeding any further check if you have docker and make available, plus you must be able to run commands with sudo.
The installation of Docker is covered by the project's official documentation here. Should you opt to install it by convenience, try the following:
$ curl -fsSL https://get.docker.com | sudo bash -There are other options to install it and you should be encouraged to explore, by reading their official notes and documentation.
Aren't you sure that you can use sudo? Try the following command:
$ sudo -l
Matching Defaults entries for ubuntu on my-instance:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User ubuntu may run the following commands on my-instance:
(ALL : ALL) ALL
(ALL) NOPASSWD: ALLYou should see something similar to the output above.
If that works for you, try installing make like this:
$ sudo apt-get install -qq -y makeOnce all requirements mentioned above are met, run the following command - and go play some Mario Kart ;)
$ make startShould be willing to get into a step-by-step run, this would be the order to follow:
$ make deps
$ make kube
$ make myapp
$ make proxy
$ make startThe following variables can be set to modify the setup and its deployment:
MYAPP_DOMAIN(Default: myapp.dev)MYAPP_REPLICAS(Default: 2)MYAPP_REPO(Default: https://github.com/octocat/octocat.github.io)MYAPP_REPO_BRANCH(Default: master)
Additional variable:
MYAPP_CLEAN_KUBE
The default value for
MYAPP_CLEAN_KUBEis not set; it's empty.
MYAPP_KUBE(Default: armor)
Define this to anything else other than 'armor' and you will deploy the standard
minikubeimage, without any changes from the KubeArmor project.
Considering the defaults, here's a list of commands to check and test the setup. They may differ if you changed some configuration or environment variables.
-
Send a GET request to the proxy that acts as load balancer:
$ curl -k -I https://172.17.0.2 HTTP/2 200 server: nginx date: Sat, 21 Feb 2026 11:43:57 GMT content-type: text/html content-length: 3983 last-modified: Sat, 21 Feb 2026 11:43:37 GMT etag: "69999a69-f8f" accept-ranges: bytes x-permitted-cross-domain-policies: none strict-transport-security: max-age=63072000 x-content-type-options: nosniff x-frame-options: SAMEORIGIN x-xss-protection: 1;mode=block
-
Check the
/myapproute and see which container is serving the health check$ curl -k -w '%header{x-my-app}\n' https://172.17.0.2/myapp app=myapp myapp-6f8cc7c464-fvjvm $ curl -k -w '%header{x-my-app}\n' https://172.17.0.2/myapp app=myapp myapp-6f8cc7c464-smlv8 $ curl -k -w '%header{x-my-app}\n' https://172.17.0.2/myapp app=myapp myapp-6f8cc7c464-2tr56
-
Show the TLS certificates
$ echo -n | openssl s_client -connect 172.17.0.2:443 -showcerts
-
Try connecting with TLSv1.2 (must not work, as we only allow v1.3)
$ curl -k -I --tls-max 1.2 https://172.17.0.2 curl: (35) OpenSSL/3.0.13: error:0A00042E:SSL routines::tlsv1 alert protocol version
-
Browser the website via terminal - perhaps that helps you to access the easter egg left on this setup ;)
$ sudo apt-get install -qq -y lynx $ lynx https://172.17.0.2
During the development of this code (or environments using it), the following issues and merge requests were produced:
- kubernetes-sigs/cluster-api#13386
- kubernetes-sigs/cluster-api#13417
- kubernetes-sigs/cluster-api#13418
- kubernetes-sigs/kind#4117
- kubernetes/minikube#22696
- kubernetes/minikube#22697
- kubernetes/minikube#22700
- kubernetes/minikube#22710
Willing to setup a Kubernetes as a Service using OpenStack? Try checking this project out: https://github.com/OSBA-eco-digit/scs-clusterstacks-iac
There's no A.I. prompt to be shared; this work was not relying on it.
- https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html
- https://docs.docker.com/reference/dockerfile/
- https://docs.haproxy.org/3.2/configuration.html
- https://docs.nginx.com/nginx/admin-guide/web-server/
- https://nginx.org/en/docs/http/configuring_https_servers.html
- https://ssl-config.mozilla.org/#server=haproxy
- https://ssl-config.mozilla.org/#server=nginx
- https://www.baeldung.com/linux/run-process-with-group
- https://www.cherryservers.com/blog/set-docker-environment-variables
- https://www.haproxy.com/blog/how-to-run-haproxy-with-docker
- https://www.haproxy.com/documentation/haproxy-configuration-tutorials/reliability/health-checks/
- https://www.owasp.org/index.php/OWASP_Secure_Headers_Project