
[{"content":"","date":"24 May 2026","externalUrl":null,"permalink":"/categories/artificial-intelligence/","section":"Categories","summary":"","title":"Artificial Intelligence","type":"categories"},{"content":"","date":"24 May 2026","externalUrl":null,"permalink":"/tags/automation/","section":"Tags","summary":"","title":"Automation","type":"tags"},{"content":"The Model Context Protocol (MCP) created by Antropic in 2024 is fundamentally changing how we interact with local Large Language Models (LLMs). Instead of keeping your models sandboxed and isolated, MCP acts as a secure, open standard that grants local models contextual access to real-world tools, filesystems, and APIs.\nFor security professionals and homelab enthusiasts, running an MCP server inside an isolated Kali Linux WSL2 container and hooking it up to LM Studio on Windows creates an incredibly powerful workspace. It allows a local LLM to safely generate syntax, parse security logs, or drive utilities directly inside a dedicated offensive environment.\nHowever, getting a Windows-native application like LM Studio to seamlessly execute scripts inside a specific Python virtual environment buried inside WSL2 presents unique cross-environment hurdles.\nThis guide walks through the complete setup—from installing the environment to configuring the automated cross-OS execution chain.\nDisclaimer: The content provided in this guide is strictly for educational, research, and authorized defensive testing purposes. The author assumes no liability or responsibility for any misuse, damage, or illegal actions resulting from the execution of the workflows described herein. Always ensure you have explicit, written authorization before interacting with any target network or system.\nFurthermore, this site maintains no official affiliation, sponsorship, or endorsement with LM Studio, the repository owners, or any third-party software applications mentioned in this article. Tools are selected based solely on local laboratory preferences and standalone utility performance.\nStep 1: Install WSL2 and Kali Linux on Windows # If you don\u0026rsquo;t already have the Windows Subsystem for Linux (WSL2) and Kali Linux running on your machine, you can get them set up natively using PowerShell.\nOpen Windows PowerShell as an Administrator. Execute the following command to install WSL and the Kali Linux distribution: wsl --install -d kali-linux Once the installation completes, restart your machine if prompted. Launch the new Kali Linux application from your Windows Start Menu to complete the initial setup, creating a secure default UNIX username and password when prompted. Step 2: Prepare Kali and Install Common Tool Bases # Before deploying the MCP infrastructure, we need to install the essential baseline build tools, dependencies, and core security utilities within the Kali environment.\nOpen your Kali Linux terminal and run the following commands:\n# 1. Update system package repositories sudo apt update \u0026amp;\u0026amp; sudo apt full-upgrade -y # 2. Install kali-linux-large metapackage containing a vast suite of security tools. # This includes Python, pip, git, build-essential, nmap, gobuster, and many more. sudo apt install kali-linux-large -y # 3. Optional - Install Kex - Kali Desktop Experience for Windows Subsystem for Linux # This provides a lightweight graphical desktop environment and RDP server for Kali Linux running in WSL2 sudo apt install kali-win-kex -y Step 3: Clone the MCP-Kali-Server Repository # Now we will clone the repository and establish an isolated virtual environment to keep our dependencies organized without breaking system-level Python packages.\nIn this walkthrough, we will use a generic placeholder path (/home/YOUR_USERNAME/). Replace YOUR_USERNAME with your actual Linux username.\n# 1. Move to your user home directory cd /home/YOUR_USERNAME/ # 2. Clone the MCP Kali Server repository git clone [https://github.com/Wh0am123/MCP-Kali-Server.git](https://github.com/Wh0am123/MCP-Kali-Server.git) cd MCP-Kali-Server # 3. Create an isolated Python virtual environment python3 -m venv .venv # 4. Activate the virtual environment source .venv/bin/activate # 5. Install the required protocol, framework, and network dependencies pip install -r requirements.txt Step 4: Download LM Studio and Install a Model # With the backend environment staged, we need an entry point for our local LLM client.\nDownload and install the Windows client directly from the official LM Studio website. Launch LM Studio on your Windows host. Use the search bar inside the application to find an optimal model for local workflow automation. For text-based security execution and scripting tasks without restrictive alignment filters, search for a model variant like Gemma 4 Uncensored. I recommend trying a few models for yourself. You should also have a decent amount of VRAM to run these models, so choose a model size that is appropriate for your system. (I will use gemma-4-e4b-uncensored-hauhaucs-aggressive for this guide.) Click Download to pull the model configuration locally onto your system. Step 5: Configure the MCP Server in LM Studio # This is where standard cross-OS implementations typically break down. Because source is a shell-builtin command rather than an independent binary executable file, Windows cannot invoke a direct instruction like wsl.exe source ....\nTo bypass this limitation, we instruct Windows to launch a full native bash context inside Kali using the -c flag. This allows us to smoothly chain our directory changes, activate the Python virtual environment, and execute the client script in a single, cohesive instruction stream.\nInside LM Studio, navigate to the Chat interface and click the Plug Icon in the sidebar (or go to Settings -\u0026gt; Tools \u0026amp; Integrations). Click Edit mcp.json to open LM Studio\u0026rsquo;s configuration array. Add the following sanitized object block to your \u0026quot;mcpServers\u0026quot; configuration, the timeout is set to 10 minutes to give the LLM time to think and execute tasks without timing out, the default timeout is 60 seconds. (make sure to replace YOUR_USERNAME with your exact UNIX folder name): { \u0026#34;mcpServers\u0026#34;: { \u0026#34;kali_mcp\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;wsl.exe\u0026#34;, \u0026#34;args\u0026#34;: [ \u0026#34;-d\u0026#34;, \u0026#34;kali-linux\u0026#34;, \u0026#34;bash\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;cd /home/YOUR_USERNAME/ \u0026amp;\u0026amp; source .venv/bin/activate \u0026amp;\u0026amp; python3 ./MCP-Kali-Server/client.py --server http://127.0.0.1:5000\u0026#34; ], \u0026#34;timeout\u0026#34;: 600000 } } } Step 6: Start the Server and Launch Your Chat # The MCP-Kali-Server tool infrastructure relies on a lightweight Flask backend to execute tasks. Because traffic will be passing across the virtual hypervisor network interface between Windows and WSL2. By default the server will bind locally inside your WSL container and will not be reachable outside of this container.\nOpen your Kali WSL terminal, ensure your .venv is activated, and run the server process: cd /home/YOUR_USERNAME/MCP-Kali-Server python3 server.py # Defaults to local ip and port 5000 (Leave this terminal session running in the background).\nReturn to LM Studio and save your updated mcp.json file. The software will instantly attempt to initialize the server process over the WSL boundary. Open a new chat session, select your loaded model from the top dropdown, and look for the green status indicator next to kali_mcp in your active tools panel. You can now start a live chat session and leverage your local model to run security operations, generate network scans, and analyze outputs straight inside your native Windows interface!\n","date":"24 May 2026","externalUrl":null,"permalink":"/posts/docs/llmstudio-kalimcp/","section":"Posts","summary":"A guide to get you started to running your own local AI model and having it interact with Kali tools inside WSL2 using LM Studio.","title":"Build your own Kali MCP Server inside WSL2 and use it with LM Studio","type":"posts"},{"content":"","date":"24 May 2026","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"24 May 2026","externalUrl":null,"permalink":"/categories/cybersecurity/","section":"Categories","summary":"","title":"Cybersecurity","type":"categories"},{"content":"","date":"24 May 2026","externalUrl":null,"permalink":"/tags/docs/","section":"Tags","summary":"","title":"Docs","type":"tags"},{"content":"","date":"24 May 2026","externalUrl":null,"permalink":"/tags/homelab/","section":"Tags","summary":"","title":"Homelab","type":"tags"},{"content":"","date":"24 May 2026","externalUrl":null,"permalink":"/","section":"Immutable Defense","summary":"","title":"Immutable Defense","type":"page"},{"content":"","date":"24 May 2026","externalUrl":null,"permalink":"/tags/kali-linux/","section":"Tags","summary":"","title":"Kali Linux","type":"tags"},{"content":"","date":"24 May 2026","externalUrl":null,"permalink":"/tags/lm-studio/","section":"Tags","summary":"","title":"LM Studio","type":"tags"},{"content":"","date":"24 May 2026","externalUrl":null,"permalink":"/tags/mcp/","section":"Tags","summary":"","title":"MCP","type":"tags"},{"content":"","date":"24 May 2026","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"24 May 2026","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"","date":"24 May 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"24 May 2026","externalUrl":null,"permalink":"/tags/wsl2/","section":"Tags","summary":"","title":"WSL2","type":"tags"},{"content":"","date":"7 April 2026","externalUrl":null,"permalink":"/tags/blog/","section":"Tags","summary":"","title":"Blog","type":"tags"},{"content":"","date":"7 April 2026","externalUrl":null,"permalink":"/tags/cybersecurity/","section":"Tags","summary":"","title":"Cybersecurity","type":"tags"},{"content":"","date":"7 April 2026","externalUrl":null,"permalink":"/tags/methodology/","section":"Tags","summary":"","title":"Methodology","type":"tags"},{"content":"","date":"7 April 2026","externalUrl":null,"permalink":"/tags/red-teaming/","section":"Tags","summary":"","title":"Red Teaming","type":"tags"},{"content":"","date":"7 April 2026","externalUrl":null,"permalink":"/tags/threat-hunting/","section":"Tags","summary":"","title":"Threat Hunting","type":"tags"},{"content":"Sometimes we treat cybersecurity as an art form—relying on \u0026ldquo;gut feelings\u0026rdquo; about anomalous network traffic or the intuition of a seasoned penetration tester. While intuition is valuable, it isn\u0026rsquo;t scalable, and it isn\u0026rsquo;t measurable. To build mature security programs, we should incorporate the scientific method to help remove bias and replace guesswork with empirical evidence.\nDefining the Scientific Method # At its core, the scientific method is a rigorous, systematic approach to problem-solving designed to minimize cognitive bias and replace guesswork with empirical evidence. While it originated in the physical sciences to investigate natural phenomena, its principles are universal and perfectly suited for investigating complex IT systems or correcting previous security assumptions. The framework relies on a repeatable, eight-step loop:\nDefine a Question: Identifying a specific security concern or a potential gap in defenses. Gather information and resources (Observation): Identifying a specific anomaly, understanding an environment, or establishing a baseline control group. Formulate a Hypothesis: Formulating a specific, testable, and falsifiable prediction. Experimentation: Designing and executing a controlled, isolated test to prove or disprove the hypothesis. Analysis: Reviewing the resulting empirical data to draw a definitive, evidence-based conclusion. Interpret Data and Draw Conclusions: Documenting your findings to serve as a starting point for your next hypothesis. Share your Results: Communicating findings to stakeholders, leadership, or the broader security community to improve collective defense. Retest: Validating that remediation efforts were successful and ensuring the environment remains resilient over time. The Hypothesis-Driven Threat Hunt # In science, you do not run experiements randomly. This can cost time and money and likely leads to wasted effort. While gut feels and anecdotal evidence may certainly play a part at times in the direction you may take, you start with a hypothesis and defined plan. The same must apply to how we defend our networks.\nIn the SANS whitepaper, Applying the Scientific Method to Threat Hunting by Jeremy Kerwin, the author drives that point home. Without a repeatable framework, threat hunting is an exhausting, time-consuming challenge. Staring at SIEM logs waiting for an alert is reactive. A mature threat hunt is proactive, and it begins with a falsifiable question.\nInstead of saying, \u0026ldquo;Let\u0026rsquo;s look for bad stuff on the network,\u0026rdquo; a scientific approach requires a specific hypothesis: \u0026ldquo;Because of recent geopolitical events, I hypothesize that an adversary is attempting to exfiltrate data via DNS tunneling on our legacy servers.\u0026rdquo; This gives your team a focused, measurable objective.\nMapping the Eight Stages to Security Science # If we map the complete scientific method to our daily operations, the alignment provides a comprehensive playbook:\n1. Define a Question (The Objective) Everything starts with a specific security concern. Instead of vague goals, ask direct questions: \u0026ldquo;Is our new segmentation policy actually blocking lateral movement from the guest Wi-Fi?\u0026rdquo;\n2. Gather Information (Reconnaissance \u0026amp; Baselining) You establish your baseline measurements and understand your environment. You cannot identify an anomaly if you don\u0026rsquo;t know what \u0026ldquo;normal\u0026rdquo; looks like. Whether you are a red teamer mapping out an attack surface or a blue teamer tuning a WAF, observation is the foundational step.\n3. Formulate a Hypothesis (Threat Modeling \u0026amp; Attack Scenarios) Based on your observations, you formulate a theory. For a red team, the hypothesis might be: \u0026ldquo;The current endpoint detection and response (EDR) configuration will not detect process hollowing in this specific segment.\u0026rdquo;\n4. Experimentation (Execution \u0026amp; Validation) This is where the test happens. The red team executes the exploit. The threat hunter queries the data lake for the specific indicators of compromise (IoCs). The key here is isolation and controlled variables so you know exactly what triggered (or failed to trigger) an alert.\n5. Analysis (Reviewing the Telemetry) Once the experiment is run, you analyze the empirical data. Did the logs capture the payload? Did the SIEM aggregate the data correctly? This is the raw technical review of the experiment\u0026rsquo;s outcome.\n6. Interpret Data and Draw Conclusions (Metrics \u0026amp; Remediation) Did the experiment prove or disprove the hypothesis? In Josiah Dykstra’s book, Essential Cybersecurity Science, he emphasizes that the scientific method is the only reliable way to generate meaningful security metrics. If the red team bypassed the EDR, the conclusion is a verifiable data point about a control failure that can be used to allocate resources.\n7. Share Your Results (Reporting \u0026amp; Threat Intel) A successful hunt or penetration test is useless in a vacuum. Sharing results means distributing a detailed report to leadership, updating internal knowledge bases, or even contributing sanitized IoCs back to the broader cybersecurity community.\n8. Retest (Continuous Validation) Security is not static. Once a vulnerability is remediated or a new detection rule is implemented based on your conclusions, you must retest the environment to validate that the fix actually works. This loops you right back to the beginning.\nBringing Rigor to Security Operations # A core challenge in cybersecurity is proving your security posture to the business. You cannot report \u0026ldquo;gut feelings\u0026rdquo; to leadership or a board of directors—you report data.\nBy training our teams to adopt the scientific method, we move away from alert fatigue and reactive firefighting. We create structured, repeatable, and evidence-based strategies. Whether you are dissecting malware in a sandbox, hunting for persistent threats, or simulating an adversary, a little scientific rigor goes a long way.\n","date":"7 April 2026","externalUrl":null,"permalink":"/posts/blog/scimethod/","section":"Posts","summary":"","title":"Why Cybersecurity Needs the Scientific Method","type":"posts"},{"content":"","date":"21 March 2026","externalUrl":null,"permalink":"/tags/career-advice/","section":"Tags","summary":"","title":"Career Advice","type":"tags"},{"content":"","date":"21 March 2026","externalUrl":null,"permalink":"/series/career-insights/","section":"Series","summary":"","title":"Career Insights","type":"series"},{"content":"","date":"21 March 2026","externalUrl":null,"permalink":"/tags/career-pivot/","section":"Tags","summary":"","title":"Career Pivot","type":"tags"},{"content":" Success Starts with the Fundamentals # I spent a decade as a Research Chemist before pivoting into Research IT and eventually into Cybersecurity. My path, however, wasn\u0026rsquo;t just a pivot; it was a return to my roots as a self-taught programmer and Linux user—skills I began building right out of high school. This journey reinforced a vital lesson for anyone entering the field today: your success in advanced security operations depends entirely on your mastery of the foundational environment.\nIn 2026, the most successful security practitioners are those who view cybersecurity as the \u0026ldquo;roof\u0026rdquo; of a technical house. To effectively defend a system, you first have to understand how to build, manage, and troubleshoot it under normal conditions. Without a solid foundation in networking, operating systems, and system administration, it is nearly impossible to recognize when a system is behaving anomalously—and more importantly, why.\nThis principle is what separates a technician from a specialist. In the lab, a fresh graduate isn\u0026rsquo;t handed the \u0026ldquo;keys to the kingdom\u0026rdquo; to run high-stakes experiments on day one; they spend time mastering the core mechanics first. Similarly, a security practitioner who understands the \u0026ldquo;how\u0026rdquo; and \u0026ldquo;why\u0026rdquo; of a network is far better equipped to defend it. You cannot truly secure what you do not fundamentally understand how to build.\nMoving Beyond Theory # The industry often markets a \u0026ldquo;0-to-100\u0026rdquo; path: degree, certification, and immediate analyst role. While those are important milestones, the secret to a long-term career is building technical intuition.\nTo defend a network, you must understand the mechanics of TCP/IP and the OSI model. To secure an endpoint, you must understand how the operating system’s kernel manages memory and how users interact with the interface.\nIf you embrace the \u0026ldquo;boring\u0026rdquo; work of IT operations—whether it\u0026rsquo;s handling Help Desk tickets, configuring VLANs, or troubleshooting a broken printer—you aren\u0026rsquo;t just \u0026ldquo;paying your dues.\u0026rdquo; You are building the intuition that theory alone cannot provide. In a real-world breach, that intuition is your greatest asset.\nThree Keys to a Sustainable Security Career # In my experience, there are three attributes that help a new practitioner stand out and thrive:\n1. Valuing the \u0026ldquo;Trench\u0026rdquo; Experience # Spending time in IT infrastructure is essential. Feeling the pressure of a production server or critical data source going down at 2 AM builds the resilience and muscle memory needed for incident response. You need to know what \u0026ldquo;normal\u0026rdquo; looks like in a complex environment to accurately identify a threat.\n2. Bridging Theory with Practice # In Chemistry, you must know what causes a runaway reaction before you can prevent one. In Cyber, you need to know how a system can be broken to understand how to fix it. This is why HomeLabs are so valuable. Whether it\u0026rsquo;s Architecting a Hardened OCI Stack or troubleshooting hardware in a TrueNAS build, these projects are where technical theory meets real-world consequence.\n3. Maintaining Contextual Awareness # Security exists to protect a business. If a practitioner doesn\u0026rsquo;t understand how a business operates—and how IT supports those operations—they risk implementing security controls that hinder the very organization they are trying to protect.\nA Roadmap for Growth # If you’re looking to build a sustainable career, focus on the foundation before the walls:\nEmbrace Foundation Roles: Roles in Help Desk, Networking, or SysAdmin are your \u0026ldquo;residency.\u0026rdquo; This is where you build the intuition that later becomes your greatest asset. Build and Document: Don\u0026rsquo;t just study for exams. Build your own fortress. Document your configurations, your failures, and your fixes. Work on projects that are beyond your current skill set to stretch yourself. This profession is built around how fast you can learn and adapt. Respect the Process: Understand that security is an advanced tier. Be patient with the time it takes to master the basics of IT infrastructure. In Conclusion # Cybersecurity is a rewarding and challenging field. It requires a solid foundation, a deep understanding of security principles, and the ability to think critically. If you are looking to enter the field, be prepared to put in the work and to continuously learn and adapt. Learn the fundamentals first, and the rest will follow.\nHelpful Links # The Cybersecurity Roadmap Security Certification Roadmap Cyberseek TryHackMe HackTheBox Disclaimer # The views and opinions expressed on this website are my own and do not represent the official policy or position of my current or former employers.\n","date":"21 March 2026","externalUrl":null,"permalink":"/posts/blog/csnotentry/","section":"Posts","summary":"","title":"How to succeed in your first security role","type":"posts"},{"content":"","date":"21 March 2026","externalUrl":null,"permalink":"/tags/leadership/","section":"Tags","summary":"","title":"Leadership","type":"tags"},{"content":"","date":"21 March 2026","externalUrl":null,"permalink":"/tags/mentorship/","section":"Tags","summary":"","title":"Mentorship","type":"tags"},{"content":"","date":"21 March 2026","externalUrl":null,"permalink":"/categories/professional-development/","section":"Categories","summary":"","title":"Professional Development","type":"categories"},{"content":"","date":"21 March 2026","externalUrl":null,"permalink":"/categories/security-strategy/","section":"Categories","summary":"","title":"Security Strategy","type":"categories"},{"content":"","date":"21 March 2026","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"14 March 2026","externalUrl":null,"permalink":"/tags/architecture/","section":"Tags","summary":"","title":"Architecture","type":"tags"},{"content":"","date":"14 March 2026","externalUrl":null,"permalink":"/tags/cloudflare/","section":"Tags","summary":"","title":"Cloudflare","type":"tags"},{"content":"","date":"14 March 2026","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"","date":"14 March 2026","externalUrl":null,"permalink":"/tags/gitlab/","section":"Tags","summary":"","title":"Gitlab","type":"tags"},{"content":"","date":"14 March 2026","externalUrl":null,"permalink":"/tags/hugo/","section":"Tags","summary":"","title":"Hugo","type":"tags"},{"content":"","date":"14 March 2026","externalUrl":null,"permalink":"/tags/opnsense/","section":"Tags","summary":"","title":"Opnsense","type":"tags"},{"content":" Building My Brand One Pipeline at a Time # I\u0026rsquo;ve read several books on professional advancement and one book that resonated with me was \u0026ldquo;The StartUp of You\u0026rdquo; by Reid Hoffman and Ben Casnocha. It really made me think about how I wanted to approach my career and how I could start building out my brand. Before I was in IT for the past twelve years, I was a chemist for ten years, and I thought I am overdue to share my knowledge and experiences with the outside world.\nIn the last year I\u0026rsquo;ve built my first real homelab. I not only wanted to create services for my household, but I wanted to learn technologies I wouldn\u0026rsquo;t get the chance to in my day job. I greatly believe in learning by doing, and building out a homelab to support my professional development and satisfy my strong curiosity was a perfect investment. In fact, my first ROV I presented to a major shareholder was deploying a Plex server (I love you, Ashley). When it came to a website, I wanted something simple, fast, and easy to maintain. I also wanted to make it as secure as possible, especially since it\u0026rsquo;s running on my home network.\nI started searching for the perfect CMS. I looked at Ghost and even gave WordPress a little consideration. However, those two platforms were too bulky, and WordPress has a history of being a security nightmare if you are not careful. That is where I came across Hugo, a static site generator.\nAnother major requirement, the ability to deploy it myself, so I decided to use Docker to containerize it. I’ve been using GitLab for a while now and I’m a big fan of their CI/CD pipelines; it’s a great way to automate the build and deployment process. I’ve always loved the “website as code” concept, and this project was the perfect way to implement it. To pull it all together, I use an editor like VS Code/Antigravity to make my changes and then push them to GitLab. Then the build process and deployment is all handled by GitLab CI/CD. The container is pushed to an internal Docker registry, and then pulled by Portainer via webhook to be deployed.\n50,000 Foot View - TL;DR # This section is for those that don\u0026rsquo;t care about the nitty gritty details and just want that 50,000 foot view. Further below, I will go into greater detail about the choices I made and why I made them.\nEnter the Pipeline # My workflow starts by pushing code to my internal GitLab. A CI/CD pipeline then builds the Hugo site, packages it in a Docker container, pushes it to a registry, and triggers a Portainer webhook for automated deployment.\nI maintain two fully isolated pipelines to enforce zero-trust security:\nDevelopment (Internal VLAN): Uses GitLab\u0026rsquo;s integrated registry for internal staging. Production (DMZ VLAN): Pushes to a self-hosted Zot registry in the DMZ. This strict separation ensures the DMZ is completely segregated from my secure internal network. To further reduce the attack surface, all public-facing containers are locked down in a read-only state, unauthorized outbound traffic is blocked, and lateral VM-to-VM communication is restricted.\nHow You Got Here # Regardless of how you found this site, your request first passed through Cloudflare\u0026rsquo;s WAF. To prevent circumvention, my perimeter firewall drops all traffic not explicitly originating from Cloudflare IP addresses. Valid traffic is then NAT\u0026rsquo;d over port 443 to Traefik (my internal reverse proxy), which handles SSL termination and routes the request to the lightweight Hugo container on port 80. The entire journey takes mere nanoseconds.\nThe Architectural Vision # Sure, I wanted a simple website. But as a computer nerd and Senior Cybersecurity Architect, I couldn\u0026rsquo;t just \u0026ldquo;host\u0026rdquo; it. I had to architect it. That meant my personal brand needed an infrastructure mirroring the zero-trust enterprise environments I design professionally.\nEngineering the Pipeline: Bridging the VLAN Gap # The biggest architectural hurdle of this build? Getting code from my highly secure Internal Server VLAN out to the public-facing DMZ without punching reckless exceptions into my firewall.\nIn a traditional flat network, a CI/CD pipeline might just SSH into the web server and dump the code. But in a properly designed Zero-Trust architecture, the DMZ is treated as hostile territory. It simply cannot be allowed to initiate connections back into the internal network. To maintain that strict boundary, the deployment had to be a purely one-way \u0026ldquo;push\u0026rdquo; model originating from the secure zone.\nHere is exactly how I automated that process using GitLab CI, Docker, and Portainer webhooks.\nSoftware used in this project:\nObsidian with Mermaid and Excalidraw plugins (Easier visualizations while generating content) Mermaid Excalidraw AntiGravity Git and Gitlab Hugo using the Blowfish theme Docker Portainer (Business Edition - this allows for the webhooks) Linux Traefik Zot NGINX LetsEncypt OPNsense Cloudflare The Container Strategy # Think Immutability—which actually served as the inspiration for the name of my site, Immutable Defense.\nBefore automating the deployment, I first needed a secure artifact to deploy. I opted to containerize the compiled Hugo site using a minimal Nginx image. The Dockerfile, located in the root directory of my Hugo site repository, exposes only port 80 since my internal WAF handles the SSL termination.\n# Use Nginx to serve the content FROM nginx:alpine # Copy your website files to the Nginx html directory # This assumes your site files are in the root or a \u0026#39;dist\u0026#39; folder COPY ./public /usr/share/nginx/html # Expose port 80 EXPOSE 80 CMD [\u0026#34;nginx\u0026#34;, \u0026#34;-g\u0026#34;, \u0026#34;daemon off;\u0026#34;] Component The \u0026ldquo;Architect\u0026rdquo; Reason nginx:alpine Base Alpine Linux dramatically reduces the attack surface by stripping out unnecessary OS packages. Smaller footprint = fewer CVEs. Static Pre-Compilation The container does not run Hugo. Hugo compiles the static HTML/CSS/JS during the CI pipeline, and only those flat files are injected into the container. There is no backend database or server-side rendering to exploit. Read-Only Execution While not defined in the Dockerfile itself, the resulting container is executed in a read-only state via the deployment stack. Even if an attacker compromised Nginx, they cannot alter the web pages or drop a web shell. The Deployment Architecture (Staging vs. Production) # To ensure I never break the live site, I built a two-tiered pipeline.\nStaging: Code pushed to the staging branch builds a \u0026ldquo;draft-enabled\u0026rdquo; version of the site and deploys it entirely within my internal VLAN. From here, I can visit the site internally via dev.\u0026lt;internal-domain.com\u0026gt; (reachable only internally) for inspection before deployment.\nProduction: Code merged to main builds the final site, pushes the image across the VLAN boundary into the DMZ\u0026rsquo;s Zot registry, and triggers the DMZ Portainer to redeploy the container, thus updating the live site.\nThe GitLab CI/CD Pipeline # I followed GitLab\u0026rsquo;s official documentation to install and deploy a runner in my internal GitLab deployment. Here is my .gitlab-ci.yml that orchestrates this workflow. It defines the strict separation of duties between the Staging and Production environments. I keep my API keys hidden in the CI/CD variable storage, so it\u0026rsquo;s effectively acting as a secrets manager. This file is in the parent directory of my Hugo site git repo.\nstages: - build - deploy variables: CI_REGISTRY: \u0026lt;internal-gitlab.domain.com\u0026gt;:5050 # We\u0026#39;ll override these in the jobs IMAGE_NAME: $CI_REGISTRY/\u0026lt;username\u0026gt;/\u0026lt;sitename\u0026gt;.com TOKEN: $gitlab_runner_key CI_REGISTRY_USER: \u0026lt;username\u0026gt; ZOT_USERNAME: $zot_username ZOT_PASSWORD: $zot_password # Portainer Webhook URLs PROD_PORTAINER_WEBHOOK_URL: \u0026lt;redacted\u0026gt; STAGING_PORTAINER_WEBHOOK_URL: \u0026lt;redacted\u0026gt; # --- STAGING BUILD --- build_staging: stage: build script: - hugo -D # Show drafts on DEV - echo \u0026#34;$TOKEN\u0026#34; | docker login -u \u0026#34;$CI_REGISTRY_USER\u0026#34; --password-stdin $CI_REGISTRY - docker build -t $IMAGE_NAME:staging . - docker push $IMAGE_NAME:staging only: - staging tags: - docker # --- PROD BUILD --- build_prod: stage: build script: - hugo - echo \u0026#34;$TOKEN\u0026#34; | docker login -u \u0026#34;$CI_REGISTRY_USER\u0026#34; --password-stdin $CI_REGISTRY # Push to internal - docker build -t $IMAGE_NAME:latest . - docker push $IMAGE_NAME:latest # Push to DMZ Zot Mirror - echo \u0026#34;$ZOT_PASSWORD\u0026#34; | docker login -u \u0026#34;$ZOT_USERNAME\u0026#34; --password-stdin \u0026lt;registry.dmz-domain.com\u0026gt; - docker tag $IMAGE_NAME:latest \u0026lt;registry.dmz-domain.com\u0026gt;/\u0026lt;username\u0026gt;/site:latest - docker push \u0026lt;registry.dmz-domain.com\u0026gt;/\u0026lt;username\u0026gt;/site:latest only: - main tags: - docker # --- STAGING DEPLOY --- deploy_staging: stage: deploy script: - curl -X POST \u0026#34;$STAGING_PORTAINER_WEBHOOK_URL\u0026#34; only: - staging tags: - docker # --- PROD DEPLOY --- deploy_prod: stage: deploy script: - curl -k -X POST \u0026#34;$PROD_PORTAINER_WEBHOOK_URL\u0026#34; only: - main tags: - docker Section Architectural Significance hugo -D vs hugo \u0026ldquo;The staging job compiles Hugo with the -D flag to render draft posts, allowing me to review content locally before it goes live. The prod job drops this flag, ensuring only published content makes it to the DMZ.\u0026rdquo; Split-Horizon Push \u0026ldquo;In build_prod, the runner pushes to \u0026lt;registry.dmz-domain.com\u0026gt;. Because of my internal Pi-hole DNS, this traffic routes directly to the DMZ VLAN\u0026rsquo;s internal IP (bypassing Cloudflare), allowing for massive, high-speed image transfers without hitting the WAN.\u0026rdquo; Webhook Decoupling \u0026ldquo;Instead of giving GitLab SSH access to the Docker hosts, I use Portainer\u0026rsquo;s webhook feature. GitLab simply sends an authenticated POST request. Portainer receives the signal, reaches out to the registry, pulls the new image, and gracefully recycles the container.\u0026rdquo; Securing the Management Plane \u0026amp; Webhooks # A sharp eye might notice the use of curl -k (which bypasses SSL certificate validation) on the Production deploy job, but not the Staging job. This is an intentional design choice reflecting the different trust zones.\nInternal Staging (\u0026lt;portainer.internal-domain.com\u0026gt;): My internal network utilizes an NGINX reverse proxy that handles Let\u0026rsquo;s Encrypt certificates via DNS-01 challenges. Because this is fully trusted and validated internally, the GitLab runner can make a standard, secure HTTPS POST request to the webhook.\nDMZ Production (\u0026lt;portainer-dmz.internal-domain.com\u0026gt;:9443): To keep management interfaces completely dark, I do not expose the DMZ Portainer instance in any public DNS records—that is an absolute non-negotiable for security. Because it is accessed directly via its IP/internal hostname on port 9443 without a public-facing proxy, it uses a self-signed certificate. The -k flag allows the internal GitLab runner to trigger the webhook across the VLAN boundary without failing the pipeline on a self-signed cert error.\nThe Production Stacks: Decoupling the Architecture # A common anti-pattern in home labs is throwing every service into a single, monolithic docker-compose.yml file. If you need to update a website label, you shouldn\u0026rsquo;t risk tearing down your ingress controller or container registry.\nTo maintain stability and enforce strict security boundaries, I decoupled the DMZ environment into two distinct Portainer stacks: the Gateway/Registry Stack and the Production Application Stack. In my internal Portainer instance, I used NGINX that runs alongside Portainer, so the Traefik/Zot registry is not needed. Also, for my internal network, I make use of the registry in GitLab.\nStack 1: The Gateway and Registry (Traefik \u0026amp; Zot) # This stack forms the network backbone hosted site in my DMZ. It establishes the internal proxy network, handles all SSL certificate generation via the DNS-01 challenge, and hosts the private secured Zot registry that stores the docker container. One of the risks I accepted was storing my API locally in a locked down file. In the future, I may consider a secrets manager.\nversion: \u0026#39;3.8\u0026#39; services: traefik: image: traefik:latest container_name: dmz-traefik read_only: true restart: always command: - \u0026#34;--api.dashboard=true\u0026#34; - \u0026#34;--providers.docker=true\u0026#34; - \u0026#34;--providers.docker.exposedbydefault=false\u0026#34; - \u0026#34;--entrypoints.websecure.address=:443\u0026#34; # Cloudflare DNS Challenge - \u0026#34;--certificatesresolvers.cloudflare.acme.dnschallenge=true\u0026#34; - \u0026#34;--certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare\u0026#34; - \u0026#34;--certificatesresolvers.cloudflare.acme.email=\u0026lt;redacted\u0026gt;\u0026#34; - \u0026#34;--certificatesresolvers.cloudflare.acme.storage=/letsencrypt/acme.json\u0026#34; - \u0026#34;--accesslog=true\u0026#34; - \u0026#34;--accesslog.filepath=/var/log/traefik/access.log\u0026#34; environment: # The _FILE suffix tells Traefik to read the content of this path - CF_DNS_API_TOKEN_FILE=\u0026lt;redacted\u0026gt; secrets: - cf_token ports: - \u0026#34;443:443\u0026#34; volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./letsencrypt:/letsencrypt - /var/log/traefik:/var/log/traefik tmpfs: - /tmp:size=1M - /var/run:size=1M networks: - proxy_net zot: image: ghcr.io/project-zot/zot-linux-amd64:latest container_name: dmz-registry restart: always volumes: # CONFIG: Read-Only (Safety first!) - /opt/zot/zot-config.json:/etc/zot/config.json:ro # DATA: Read/Write (Required for pushes) - /opt/zot/data:/var/lib/zot:rw # Password file - /opt/zot/htpasswd:/etc/zot/htpasswd:ro # Zot Logs - /var/log/zot:/var/log/zot:rw labels: - \u0026#34;traefik.enable=true\u0026#34; - \u0026#34;traefik.http.routers.zot.rule=Host(`\u0026lt;registry.dmz-domain.com\u0026gt;`)\u0026#34; - \u0026#34;traefik.http.routers.zot.entrypoints=websecure\u0026#34; - \u0026#34;traefik.http.routers.zot.tls.certresolver=cloudflare\u0026#34; - \u0026#34;traefik.http.services.zot.loadbalancer.server.port=5000\u0026#34; networks: - proxy_net # --- THE ARCHITECT\u0026#39;S SECRET --- secrets: cf_token: file: /home/\u0026lt;user\u0026gt;/cf_token.txt networks: proxy_net: name: proxy_net driver: bridge Architectural Decisions in Stack 1:\nConfiguration The \u0026ldquo;Architect\u0026rdquo; Reason Docker Secrets Hardcoding API tokens in environment variables is a critical flaw, as they can be exposed via docker inspect. Using Docker Secrets mounts the CF_DNS_API_TOKEN_FILE securely into the container’s memory. read_only: true \u0026amp; tmpfs Traefik is forced into a read-only state. By using tmpfs, I allocate 1MB of ephemeral RAM for /tmp and /var/run, allowing the proxy to function without ever touching the underlying disk. Granular Volume Permissions The Zot registry requires write access to store the pushed images (:rw), but the configuration and authentication files are strictly mounted as read-only (:ro) to prevent tampering. Zot is also password protected and the credentials are stored in my GitLab secrets manager. This is to prevent possible tampering, and uploading and deploying a tampered Docker container. Stack 2: The Production Website # With the ingress and registry established, the actual website stack becomes incredibly lightweight. This is the stack that gets triggered and recreated by the Portainer webhook from GitLab.\nversion: \u0026#39;3.8\u0026#39; services: my-website: image: [\u0026lt;registry.dmz-domain.com\u0026gt;/\u0026lt;username\u0026gt;/site:latest](https://\u0026lt;registry.dmz-domain.com\u0026gt;/\u0026lt;username\u0026gt;/site:latest) container_name: \u0026lt;website_container\u0026gt; restart: always pull_policy: always # 1. Lock the filesystem read_only: true # 2. Map logs to your host machine volumes: - /var/log/\u0026lt;website_name\u0026gt;:/var/log/nginx # 3. Give Nginx temporary memory space for internal operations tmpfs: - /var/cache/nginx:size=10M - /var/run:size=1M - /tmp:size=1M # 4. Traefik Routing Labels labels: - \u0026#34;traefik.enable=true\u0026#34; # This identifies which domain points to this container - \u0026#34;traefik.http.routers.hugo.rule=Host(`\u0026lt;public-domain.com\u0026gt;`) || Host(`www.\u0026lt;public-domain.com\u0026gt;`)\u0026#34; - \u0026#34;traefik.http.routers.hugo.entrypoints=websecure\u0026#34; # Tells Traefik to use the Cloudflare/Let\u0026#39;s Encrypt resolver - \u0026#34;traefik.http.routers.hugo.tls.certresolver=cloudflare\u0026#34; # Tells Traefik to route traffic to Nginx\u0026#39;s internal port 80 - \u0026#34;traefik.http.services.hugo.loadbalancer.server.port=80\u0026#34; networks: - proxy_net networks: proxy_net: external: true # This tells Portainer \u0026#34;Don\u0026#39;t create it, it\u0026#39;s already there\u0026#34; Architectural Decisions in Stack 2:\nConfiguration The \u0026ldquo;Architect\u0026rdquo; Reason pull_policy: always Crucial for the CI/CD pipeline. When the webhook triggers an update, this forces Docker to pull the fresh image from the Zot registry rather than relying on a locally cached :latest tag. Nginx tmpfs Mounts Nginx requires write access to function (for PID files and caches). Since the container is locked to read_only: true, mounting /var/cache/nginx, /var/run, and /tmp to tmpfs creates a secure, ephemeral memory space that vanishes when the container restarts. external: true Network This container exposes zero ports to the host (no ports: directive). It simply attaches to the pre-existing proxy_net. All traffic must flow through Traefik, ensuring the website is never exposed directly to the network interface. Host Log Mapping By mapping /var/log/nginx directly to the host\u0026rsquo;s /var/log/\u0026lt;website_name\u0026gt; directory, I ensure that all access logs survive container destruction and are instantly available for ingestion into my SIEM. By enforcing read_only: true and no-new-privileges:true, the container\u0026rsquo;s root filesystem is locked down, and processes cannot gain additional privileges. The container does not expose ports directly to the Docker host; it only communicates on the isolated proxy network, forcing all inbound traffic to pass through Traefik\u0026rsquo;s inspection.\nOPNSense \u0026amp; Firewalls # To prevent anyone from bypassing the Web Application Firewall (WAF), OPNsense is configured to only allow incoming traffic from authenticated Cloudflare IP addresses. Additionally, outbound egress traffic from the DMZ is strictly blocked. The web hosting VM is only permitted to initiate outbound connections to specific IPs required for system updates. Since legitimate web traffic over port 443 originates externally, any attempt to initiate an outbound connection on this port from the VM itself is inherently blocked. This strict egress filtering significantly mitigates the risk of an attacker establishing a reverse shell or Command and Control (C2) connection. Furthermore, OPNsense completely isolates the DMZ from the internal VLANs. At the hypervisor level, firewall rules within Proxmox prevent VMs in the DMZ from communicating with one another, neutralizing the risk of lateral movement.\nBeyond access control, OPNsense handles Network Address Translation (NAT) and manages Dynamic DNS, keeping Cloudflare updated with my home network\u0026rsquo;s dynamic public IP address. It also serves as the routing core for my entire VLAN architecture, working in tandem with my Ubiquiti switch to enforce port-level isolation and ensure no unintended traffic crossover occurs between network segments.\nA Note on Observability # I couldn\u0026rsquo;t build this without visibility. Traefik is configured to extract and trust the CF-Connecting-IP headers so the true visitor IPs are logged and shipped directly to my internal Splunk instance, rather than showing Cloudflare\u0026rsquo;s proxy IPs.\nHowever, diving into the SOC side of this architecture—including how I integrate Splunk, Wazuh, Zeek, and Suricata—is a massive topic on its own. That will be the focus of my upcoming Threat Hunting Lab project, so stay tuned.\nFinal Thoughts # Building this site was about more than just hosting a blog. It was a deliberate exercise in translating enterprise security principles into a tangible, functioning pipeline. By moving security to the edge and the perimeter, maintaining strict VLAN isolation, and ensuring every action is automated and observable, I’ve created a stack that is resilient, performant, and truly \u0026ldquo;production-ready.\u0026rdquo;\nFor the architecture diagrams, I initially outline them in Mermaid before translating them into Excalidraw PNGs. I prefer Excalidraw\u0026rsquo;s hand-drawn aesthetic, and retaining the source files makes any future edits trivial. I plan to automate this conversion process eventually to streamline my publishing workflow even further. To round out my content generation pipeline, I leverage Gemini to serve as my personal editor and creative department for generating high-quality images.\nIf you\u0026rsquo;re into architecture deep-dives or homelab builds, I look forward to sharing more of this journey with you. You may have noticed I have also redacted internal DNS names and IP addresses throughout this post. If you feel I missed something, or want to chat about the process please feel free to reach out to me.\n","date":"14 March 2026","externalUrl":null,"permalink":"/posts/projects/hugoandbeyond/","section":"Posts","summary":"Why I built a ‘Production at Home’ stack to host my personal brand and professional journey.","title":"Pipedream to Pipeline","type":"posts"},{"content":"","date":"14 March 2026","externalUrl":null,"permalink":"/tags/portainer/","section":"Tags","summary":"","title":"Portainer","type":"tags"},{"content":"","date":"14 March 2026","externalUrl":null,"permalink":"/tags/projects/","section":"Tags","summary":"","title":"Projects","type":"tags"},{"content":" 💡 The Learning Journey: Doing \u0026gt; Reading # Welcome to my corner of the internet. I’m Steve Manzo, a Sr. Cybersecurity Architect who believes the best way to understand a system is to build it, break it, and then monitor the wreckage. Currently, Splunk and Wazuh are my SIEMs of choice for keeping the lights on.\nStay tuned for upcoming project posts where I’ll dive deep into the specific choices I made while architecting and securing this very site. Beyond this infrastructure, I’m also actively building a comprehensive threat-hunting lab utilizing Zeek, Suricata, Osquery, and more—I look forward to sharing those workflows and findings with you soon.\nThe Evolution of the Lab # I’m a big believer in the power of open source and the \u0026ldquo;learning by doing\u0026rdquo; philosophy. While certifications have their place, there is no substitute for the panic of a storage error or the satisfaction of a perfectly automated CI/CD pipeline. Building a homelab wasn\u0026rsquo;t just a hobby; it was a deliberate investment in my professional development.\nI originally started with the idea of a mini Raspberry Pi cluster, but I quickly realized I wanted more power and a setup \u0026ldquo;truer to life.\u0026rdquo; I wanted enterprise-grade hurdles: VLANs, hardware RAID, and proper hypervisor management. I traded the desk-clutter for a 12U Network Rack and started rack-mounting my way to a private cloud.\nThe Hardware Manifest: # Perimeter: Protectli Vault (Running OPNsense). Compute: Custom 2U Server Chassis running Proxmox VE. Storage: Custom 4-Bay NAS (SilverStone Chassis) running TrueNAS SCALE. Switching: Ubiquiti 16-Port PoE Switch for VLAN segmentation. DNS: Dual Raspberry Pis running Pi-hole for redundant, ad-blocking DNS. Power: 2U CyberPower UPS managed by a NUT (Network UPS Tools) server. Wireless: Repurposed Synology router acting as a dedicated AP bridge. Current Office Setup: # Why This Matters # This stack is my sandbox. It’s where I formost learn, but also host my internal services, test new security tools, create CI/CD pipelines, and create anything I put my mind too.\nI hope you\u0026rsquo;ll join me on this journey. I look forward to hearing your thoughts and sharing ideas as the lab continues to evolve. Thanks for stopping by!\n","date":"8 March 2026","externalUrl":null,"permalink":"/posts/blog/thebeginning/","section":"Posts","summary":"From Raspberry Pis to a 12U Rack—Why building my own ‘Production’ environment was the best career investment I ever made.","title":"Architecting the Stack: Why Every Security Pro Needs a Homelab","type":"posts"},{"content":"","date":"8 March 2026","externalUrl":null,"permalink":"/tags/security-architecture/","section":"Tags","summary":"","title":"Security-Architecture","type":"tags"},{"content":"","date":"8 March 2026","externalUrl":null,"permalink":"/tags/splunk/","section":"Tags","summary":"","title":"Splunk","type":"tags"},{"content":"","date":"8 March 2026","externalUrl":null,"permalink":"/tags/wazuh/","section":"Tags","summary":"","title":"Wazuh","type":"tags"},{"content":" Architecting Resilience in a Borderless World # The Mission # I don\u0026rsquo;t just build security frameworks; I stress-test the future. My focus is at the intersection of robust, defense-in-depth, and offensive agility. In an era where \u0026ldquo;perimeter\u0026rdquo; is a legacy term, I design systems that aren\u0026rsquo;t just secure—they’re resilient.\nThe Foundation: From Molecules to Malware # I was always a computer nerd at heart. My first real experience was installing Linux on my computer in the late 90s, and teaching myself to code. It was my hobby while I was working towards my degree in chemistry.\nMy path to cybersecurity was not a straight line. I spent a decade as a Research Chemist before I migrated into research IT. I created pipelines and python code to aid scientists in their research. After I spent about six years in research IT, I transitioned into Cybersecurity as a global team leader where I was able to apply my analitical skills to understand the \u0026ldquo;why.\u0026rdquo;\nThat decade in the lab forged the analytical mindset I still use today. In research, you learn that reality doesn\u0026rsquo;t care about your assumptions—only the data matters. I treat every threat hunt and architectural challenge like a lab experiment: isolating variables; testing hypotheses; and maintaining a relentless curiosity to find the root cause.\nBeyond the Title # Security is a philosophy, not a product. Whether I’m architecting enterprise-grade solutions, or navigating the ethical boundaries of AI management, my approach remains the same: Visibility is everything.\nThe Red Team Mindset I believe the best architects understand the \u0026ldquo;how\u0026rdquo; of an exploit. By integrating offensive techniques—like social engineering and penetration testing—into defensive design I ensure that security isn\u0026rsquo;t just a checkbox. Rather I create a proactive barrier against real-world threats. I truly believe understanding how a threat actor would attack is the best way to prevent it.\nContinuous R\u0026amp;D (The Homelab) I don\u0026rsquo;t wait for industry trends to reach my desk. I build them in my private lab. Running a full Proxmox environment with automated GitLab CI/CD pipelines and a comprehensive threat-hunting stack allows me to break, build, and master emerging tech long before it hits production.\nThe Next Frontier: AI Governance As we pivot toward an AI-driven landscape, I am focused on the strategic implementation of AI management. I bridge the gap between \u0026ldquo;innovation at all costs\u0026rdquo; and \u0026ldquo;secure by design,\u0026rdquo; ensuring that LLMs and automated workflows remain assets rather than liabilities.\nLeadership Philosophy Leadership in cybersecurity isn\u0026rsquo;t about having the most certifications—it’s about clarity and impact. I view security as a business enabler.\nMy goal is to translate complex technical vulnerabilities into clear, actionable risks for stakeholders by ensuring that our technical roadmap is always in lockstep with our strategic objectives. I don\u0026rsquo;t just manage infrastructure; I champion a culture of security that empowers every level of the organization.\n","externalUrl":null,"permalink":"/about/","section":"Immutable Defense","summary":"","title":"","type":"page"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"}]