Why I Built My Own CI/CD Stack (And What I Learned)¶
Most engineers use CI/CD tools. I wanted to understand them well enough to build the infrastructure underneath them: my own servers, my own domain, my own certificates, and eventually my own machine images. This is the reasoning, the honest cost, and an index to everything the project produced.
Launch Any of These Right Now
- Dev Machine - the full DevOps workstation
- Jenkins - CI server behind Nginx
- SonarQube - code quality analysis
- Nexus - artifact repository
- CI/CD Stack - all four machines composed together
No install, no signup beyond an iximiuz Labs account. Click Start on any of them.
The Goal¶
A complete self-hosted CI/CD stack, with each service on its own machine:
- Jenkins for pipeline automation
- SonarQube for code quality analysis and quality gates
- Nexus for artifact storage
Each reachable on its own subdomain with valid TLS. All of it reproducible from code, so that losing every machine costs an evening rather than a weekend.
Why Build It Yourself¶
Managed CI is genuinely better for shipping software. If the goal were to ship, this project would be a mistake.
The goal was different. Managed CI hides exactly the parts I wanted to be able to reason about: how a build agent is provisioned, what a reverse proxy is doing in front of an application, why services start in a particular order, where a certificate comes from, what happens when a machine is destroyed and rebuilt.
There is also a portfolio argument, and I will state it plainly rather than pretend otherwise. "I set up Jenkins" and "I built a reproducible Jenkins machine image with a build-time healthcheck, port-parameterised configuration, and a CI pipeline that publishes it" describe very different amounts of work. The second is checkable by anyone who opens the repository.
What It Actually Cost¶
Three days on EC2 to prove the architecture. Then a move to a microVM lab platform (iximiuz Labs), which broke the networking model completely and took another week to understand and solve properly. Then several weeks encoding everything into images so it would survive a reset.
Two things surprised me.
Most of the learning was in error messages. A PostgreSQL locale mismatch. A CREATE DATABASE statement that cannot run inside a DO block. Jenkins ignoring a configuration file it stopped reading in version 2.332. Nexus changing its download URL format. None of those were answerable from the first page of search results, and each required reading a changelog or source rather than a tutorial.
The constraints produced the good decisions. A hard resource ceiling forced me to justify every GiB, which produced JVM tuning I would otherwise have skipped. A platform with no public IP forced me to learn how outbound tunnelling actually works. An ephemeral filesystem forced a clean split between what belongs in an image and what has to be reconciled at every boot.
Three Things Worth Carrying Elsewhere¶
NAT is the normal case. A routable public IP per host is the exception, not the default. Home connections, corporate networks and most managed platforms all sit behind NAT, and the standard "point an A record at it and run Certbot" recipe silently assumes something you usually do not have.
A Dockerfile can describe a server, not just an application. Packages, users, service units, reverse proxy configuration, validation. Once the whole machine is a file in Git, rebuilding it stops being an event.
The build-time versus boot-time boundary is the hard part. SSH host keys, database provisioning and kernel parameters cannot be baked into an image. They belong in a boot-time unit ordered before everything that depends on them. Getting that split wrong produces failures that look like configuration problems and are actually lifecycle problems.
The Series¶
Everything the project produced, in reading order.
| Post | What it covers |
|---|---|
| Your server has no public IP | Why DNS and Certbot fail behind NAT, the five approaches that do not work, and the one that does |
| Building an OCI image that boots as a microVM | The image model: what gets removed on purpose, and the build-time versus boot-time split |
| The machine I actually work from | The full DevOps workstation image: 40-plus tools, aliases wired to real completion, and the USER $USER reasoning in full |
| Six errors from building systemd rootfs images | Specific failures with root causes: apt cache, DO blocks, locales, JVM preferences |
| Four machines, one playground | Composition: node roles, capacity allocation against a fixed budget, JVM sizing |
| Publishing a custom playground on iximiuz Labs | Custom rootfs requirements, the manifest, and labctl, so you can publish your own rather than copy mine |
| Three places to put provisioning | Init tasks, startup files, or the image, and why I chose the image every time |
The pipelines that run on this stack are a separate track:
| Post | What it covers |
|---|---|
| The vulnerability gate that blocked every build | A scanning gate with no passing state, and the redesign that fixed it |
| The version override Maven silently ignored | Clearing seven transitive criticals, and why <properties> works for some libraries and does nothing for others |
| Trivy could not find the image I had just built | BuildKit's store is not the Docker daemon, and a scan that cannot resolve its target |
| The same pipeline on two CI engines | Jenkins versus GitHub Actions, measured, and the gate drift only a side-by-side read found |
| Twelve fixes to a pipeline that was already passing | Interpolation timing, swallowed errors, poisoned coverage trends, and four time bombs |
| One pipeline contract, three language ecosystems | What stayed fixed across Java, Python and Node, and what had to diverge |
| Four ways to serve a frontend, three of them wrong | What decoupling two tiers actually costs, and the reverse proxy it requires |
| Release identity | What a build is called, and what is allowed to reach a cluster |
The Repository¶
Everything is at github.com/ibtisam-iq/silver-stack: Dockerfiles, install scripts, systemd units, Nginx configuration, playground manifests and the workflows that build the images. MIT licensed.
All five environments are launchable in a browser from labs.iximiuz.com/a/ibtisam-iq.
Series: Building a Self-Hosted CI/CD Stack from Scratch (Hub)
This post is the hub, not a numbered part: the reasoning, the cost, and the index above. The numbered series itself starts here.
- Next: Your Server Has No Public IP (Part 1 of 6)