I over engineered my personal infra and this is what fixed it

Usually a less experienced dev tends to overcomplicate things - and I was no exception to this rule.

V1: The cPanel era

Initially my personal infrastructure consisted of a VPS and various cPanel alternatives (VestaCP, CloudPanel and a few others I don’t remember right now). They got their job done but each platform was opinionated in certain ways. Over time this led to a bigger mess and a desire to re-architect the entire infrastructure.

V2: Kubernetes overhead

Next … Kubernetes ! At first glance, I’d say this actually works as it fits any hosting requirements and if one chooses a managed K8 cluster, the pain point of setting up a VPS is also gone. In order to deploy a project one just needs to package everything inside a docker container (which for reproducibility and various other reasons is a good practice anyway). Unfortunately, with every new project one needs to setup multiple components: Pods, Deployments, Services (Load balancer), Ingress and even though these tasks mainly consists of repetitive boilerplate, it quickly become tedious and spinning up a new project becomes a chore. I realized that that k8 just adds operational overhead for features I don’t really need in my day-to-day projects.

V3: Minimalist docker on VPS

Going back to the roots, a VPS and Docker - and no other unnecessary bloat.

Simulator Screen Shot - iPhone 14 Pro Max - 2022-12-24 at 23.11.04

We only need 3 folders and 2 scripts:

/
├── config/
├── data/
├── backups/
├── deploy.sh
└── backup.sh

/config will contain a directory for every domain/subdomain we want to host; in each there is going to be one docker-compose or docker-stack defining the actual service.

Simulator Screen Shot - iPhone 14 Pro Max - 2022-12-24 at 23.11.04

Every folder inside of /config will also be found in /data. These serve as the mounted volumes for the corresponding containers.

The root / is a git repository where /backups and /data are added to the .gitignore .

Let’s take one of my current projects as an example, forms.ifnul.com. We reference a built docker image that exposes the service on port 1003. A mandatory step is attaching it to the reverse_proxy_stack to expose it to the reverse proxy service (this ensures that all traffic coming to forms.ifnul.com is redirected to this service )

services:
	forms-ifnul-com:
		image: sorinmircea/formsidian:1.0.60
	ports:
		- "3003:3003"
	networks:
		- reverse_proxy_stack
	deploy:
		update_config:
		order: start-first
networks:
	reverse_proxy_stack:
	external: true

Web server/ reverse proxy with HTTPS support

CADDY !!!

A night-and-day difference in terms of usability compared to nginx, and it has built in automatic SSL generation and renewal (with let’s encrypt).

under /config/reverse_proxy we keep the docker-stack.yaml of the caddy deployment along with the Caddyfile where all routes are defined.

Continuing with the above example, the reverse proxy entry (from Caddyfile) for forms.ifnul.com is set up in the following way:

www.forms.ifnul.com forms.ifnul.com {
	reverse_proxy forms-ifnul-com:3003
}

where forms-ifnul-com is the name defined under the service section of the yaml file.

Deployment script

Every minute, this script - defined in the root folder - is being automatically run by a cron job

In a nutshell, all it does is to check if the current repo (that coincides with the root folder of the VPS) has any new commits, if this is True it will loop through all directories under /config and redeploy all the defined services.

I’ve also added a force flag for manual deployment ./deploy.sh --force

Backup script

Daily, another cron job executes the backup script that creates a new archive under /backups. For some more peace of mind I also suggest automating the process of uploading this archive somewhere external.

That’s it, feel free to drop an email to mircea.sorin.sebastian@gmail.com for any questions or suggestions. Thanks