The artifact is already hardened. From FinTech to Homelab: Writing an Enterprise-Ready Dockerfile for Hugo was about building the container correctly. This post is about everything that has to happen after that.
But here’s the uncomfortable truth: shipping an artifact only once isn’t enough. You need automated validation at every step. Every time someone merges code, a pipeline should kick in and ask: Is this secure? Does it have vulnerabilities? Does it meet our quality standards?
In a regulated environment this isn’t optional. Security gates are mandatory checkpoints. And if something fails, you iterate, fix, and try again.
Today, we’re building exactly that: a GitHub Actions CI/CD pipeline that works like a bank’s security review process… except it runs in seconds, not days.
The Pipeline Architecture#
Before we dive into the code, let me show you the architecture. The pipeline has two distinct workflows:
- Pull Request Validation (Hugo_PullRequest.yaml) — runs on every PR before merge
- Deployment Pipeline (Hugo_Deploy.yaml) — runs after merge to main
The PR validation pipeline is our first line of defense. It catches problems early. The deployment pipeline is still rigorous, but it assumes the code has already passed validation.
What the PR Pipeline Does#
When you open a pull request with changes to the blog:
- Gitleaks Secret Scan: Detects if any secrets (API keys, tokens, credentials) accidentally got committed
- Trivy Configuration Scan: Scans all YAML and infrastructure-as-code files for known vulnerabilities
- SonarQube Code Quality Analysis: Measures code smells, bugs, security hotspots, and test coverage
- Trivy Image Scan: Builds a local Docker image and scans it for container vulnerabilities
- Multi-Architecture Build: Attempts to build the image for both
amd64andarm64without pushing
If any of these steps fail, the PR is blocked. You cannot merge until you fix the issues.
What the Deployment Pipeline Does#
After you merge to main, the deployment pipeline kicks in:
- Version Resolution: Resolves image name and calculates the next alpha version
- Multi-Architecture Build and Push: Builds and pushes
amd64,arm64image only after successful scan - SonarQube Analysis: Runs SonarQube scan against the full repository
The GitHub Actions Workflows#
Let me show you the workflows and trigger wiring from the files.
In this setup, both are reusable workflows (workflow_call) hosted in the workflows repository, and each project calls them explicitly.
Caller Workflow: Pull Request Trigger#
name: Pull Request Validation
on:
pull_request:
types:
- opened
- reopened
- synchronize
branches:
- main
permissions:
contents: read
jobs:
validate:
uses: anvaplus/homelab-github-workflows/.github/workflows/Hugo_PullRequest.yaml@main
with:
sonar_organization: ${{ vars.SONAR_ORGANIZATION }}
secrets:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}Caller Workflow: Deploy Trigger#
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: anvaplus/homelab-github-workflows/workflows/Hugo_Deploy.yaml@main
with:
dockerhub_username: anvaplus
sonar_organization: ${{ vars.SONAR_ORGANIZATION }}
secrets:
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}Workflow Source Links#
- Hugo Pull Request workflow: Hugo_PullRequest.yaml
- Hugo Deploy workflow: Hugo_Deploy.yaml
Versioning Every Merge#
One important part of this pipeline is the versioning model behind it. I do not tag images manually. Every merge to main produces a new integration build version automatically through my next-version action, which reads the existing git tags and generates the next semantic version with an alpha suffix.
That means the pipeline does not just build containers, it also creates a traceable release history. If the last stable release was v1.2.0, the next merges produce versions like v1.3.0-alpha.1, v1.3.0-alpha.2, v1.3.0-alpha.3, and so on. The artifact changes, the tag changes, and the repository history keeps a clean record of exactly what was built after each PR merge.
This is the same strategy I described in Stop Rebuilding Your Images: The “Build Once, Promote Everywhere” Manifesto: next-version generates the integration tag, while promote-version is responsible for advancing the same artifact through later environments such as beta, rc, and finally stable production. In this blog pipeline, we are focused on the first part of that flow: generating the integration-ready alpha build consistently.
You can see the same tag history directly in GitHub on the repository tags page. GitKraken simply makes that progression easier to read visually, because you can see the tags attached to the commit flow after each PR merge.

The Security Tools in Action#
Each tool in this pipeline covers a different risk surface. Together, they create layered controls similar to what you would expect in a regulated production environment.
Gitleaks: Stop Secrets Before They Start#
Gitleaks scans commit history and current changes for patterns that match known secret formats such as API tokens, cloud keys, and private keys. This is the earliest and cheapest control in the chain, because a leaked credential can expose your entire platform long before an application vulnerability is exploited.
Trivy Configuration Scanner: Infrastructure-as-Code Hygiene#
Trivy config scan reviews YAML files, Dockerfiles, Kubernetes manifests, and related IaC for risky misconfigurations, including overly permissive access, missing hardening controls, and insecure defaults. These issues are often the root cause of production incidents, so catching them at PR time prevents bad infrastructure posture from being promoted.
Trivy Image Scanner: Container Vulnerability Detection#
Trivy image scan inspects the final container artifact for known CVEs in OS and library layers. Even perfect application code can be compromised by vulnerable dependencies, so this gate enforces image hygiene before publish. In this pipeline, HIGH and CRITICAL findings block progression.
SonarQube: Code Quality and Security Hotspots#
SonarQube performs static analysis to detect security hotspots, maintainability issues, duplication, and reliability risks directly in source code. This complements Trivy and Gitleaks by covering what dependency and config scanners cannot: logic-level weaknesses and quality drift in application code.
The Iterative Hardening Journey#
This is what actually happened in practice. The hardening was not one commit; it was three focused iterations.
Iteration 1: PR #4 Fails, Then Passes#
PR link: hugo-blog-example#4
The first PR failed at the GitHub level:

At the same time, the PR validation workflow itself was healthy. Trivy configuration and image scans both passed:


The blocker was SonarCloud quality review, not container vulnerability scanning:


The key recommendation was to pin dependency references with full commit SHA. For this specific dependency (my own workflow repository), I reviewed and accepted the risk in context. Once Sonar findings were resolved/accepted, the PR became mergeable:

Iteration 2: Main Branch Check Fails, Dockerfile Fix in PR #5#
After merging PR #4, the deploy workflow ran on main and Sonar analyzed the full repository context (not just PR scope). That check failed:

The issue came from secure download handling in the Docker build process. Based on secure coding guidance, I replaced wget with curl and enforced HTTPS-only redirect behavior with TLS 1.2 to prevent downgrade or insecure redirect paths during Hugo binary download.
Fix PR: hugo-blog-example#5

After this change, PR checks passed again and, once merged, the main branch checks returned green:

Iteration 3: Centralized Workflows and Tag-Per-Merge in PR #6#
In the third iteration, I moved from in-repo workflow definitions to centralized reusable workflows (the model shown in this post). This mirrors how real platform teams manage CI/CD standards across multiple repositories.
Change PR: hugo-blog-example#6
Each merge now generates a new version tag automatically (as described in the Versioning Every Merge section), and each successful deploy publishes the image to Docker Hub.

Docker Hub repository: anvaplus/hugo-blog-example
What This Teaches Us About Pipelines#
Here’s the lesson hidden in those iterations:
True enterprise CI/CD is not about having the fanciest pipeline. It’s about making the gates so strict that broken code simply cannot reach production.
In fintech, I have seen major incidents start with someone assuming a change was “probably fine.” It was not. What looked like a small shortcut turned into long remediation cycles, unnecessary risk, and expensive follow-up work.
By contrast, a strict pipeline can look heavy at first, with all the scans and gates in place, but it is still the cheapest form of protection you can add. Catching an issue in a pull request is a routine fix. Catching it after release is a much more painful problem.
A quick note on scope: this blog is not a step-by-step tutorial for each individual tool. If you’ve read this far, you already know how to add a secret in GitHub Actions or create an organization in SonarCloud—there are plenty of tutorials online for that. My goal here is to show you how these tools fit together in a production-grade setup, the same way I’ve seen them combined in regulated financial environments. All the code, workflows, and READMEs are publicly available in the companion repository. Take it, adapt it, and make it yours.
What’s Next: Deployment to Kubernetes#
Now that we have a hardened, scanned, and versioned container image, the next step is to deploy it to Kubernetes through the GitOps model I described in The Four-Repo GitOps Structure for My Homelab Platform.
That architecture separates platform services, reusable deployment blueprints, environment state, and Argo CD application definitions into distinct repositories. It is the operating model that turns this pipeline output into a real deployment flow, with controlled promotion, clear separation of concerns, and a public record of how image versions move toward the cluster.
Key Takeaways#
- Security gates are mandatory checkpoints, not nice-to-haves. They cost nothing to run (GitHub Actions is free) and catch issues early.
- Iterate ruthlessly. The first pass won’t be perfect. Expect to fix things. That’s the point of the pipeline.
- Know your tools:
- Gitleaks for secrets
- Trivy for vulnerabilities
- SonarQube for code quality
- Docker Buildx for multi-architecture builds
- Fail fast, fail early. Block bad code at the PR stage, not production.
- The pipeline is part of your platform. Invest in it like you invest in your infrastructure.
The full code for this blog’s pipeline is available in the public companion repository. Clone it, adapt it to your project, and start shipping hardened artifacts.
Stay tuned! Andrei

