Skip to content

TechnologyΒΆ

Announcing the Packer Plugin for Artifactory

Today marks the v0.1.0 release of the Packer Plugin for Artifactory, my first standalone Packer plugin. This one's been on the list for a while.

The plugin adds an artifactory post-processor. After a Packer build produces an OVA or OVF, the post-processor uploads it straight to JFrog Artifactory. Build, export, upload, done. No handoff step, no separate script, no copy-pasting artifact paths into another job.

Primary targets are the vmware/vsphere and vmware/vmware Packer plugins, covering vSphere, Fusion, and Workstation. Any other builder that emits OVA or OVF should work too. Authentication supports username and password, API key, and access token. Custom properties let teams attach version, OS, and build metadata right from the post-processor block.

Announcing the Packer Plugin for VMware vSphere v2.3.0

Packer Plugin for VMware vSphere v2.3.0 was released on August 3, 2026.

This release expands how vsphere-clone starts a build. Inventory templates still work. So do content library items, and local or remote OVF/OVA packages. Remote sources support HTTP, HTTPS, and HTTPS with basic authentication. Pick the source that matches where the image already lives, then customize, provision, and publish as usual.

v2.3.0 also expands vSphere data sources for inventory lookups in HCL, ships project documentation at vmware.github.io/packer-plugin-vsphere, and includes reliability improvements, clearer validation, broader test coverage, and dependency updates.

How to Structure an Ansible Collection

Ansible

I recently wrapped up work on an Ansible collection I developed for a customer through my role at Broadcom. With the project complete, I wanted to write down the structure it followed and the reasons behind it. This guide is for developers building their first collection and maintainers checking an existing one against Ansible's recommended practices.

Those decisions matter once an Ansible repository has been in use for a while. Roles sit beside playbooks that depend on custom filters. Lookup plugins get copied from one repository to another. The same automation has to run locally and in Ansible Automation Platform (AAP), where repository-specific plugin paths become difficult to maintain.

A collection gives that content a package name and a standard layout. Roles, plugins, playbooks, documentation, and tests can ship together as a versioned package under a namespace such as example.platform. Once you understand where each part belongs, the structure is straightforward.

Development Containers: Consistent Environments for Every Contributor

Development Containers logo

Most engineering teams hit this eventually: a new developer clones the repository, follows the setup instructions, and spends the next two hours debugging why the toolchain that works on everyone else's machine doesn't work on theirs. The versions are slightly different. A system dependency is missing. The environment variable was never set. By the time they make their first commit, they've already burned half a day on tasks that had nothing to do with the code they were hired to write.

Development containers help solve this by codifying the development environment as a container image. Every contributor uses the same tools at the same versions on every machine. Onboarding becomes opening the repository instead of working through a long setup guide.

A development container, commonly called a "devcontainer", is a Docker container configured as a development environment. It holds the runtime, tools, extensions, and settings required to work on the project, and that configuration lives in the repository right next to the codebase.

The Development Containers specification defines the format that editor and platform integrations follow. VS Code, GitHub Codespaces, JetBrains Gateway, and the Dev Containers CLI all consume the same .devcontainer/ definition. A valid configuration should work across those entry points without forked setup docs.

Instead of a README.md that says "install x v1.24, y 1.11, and z v7.1.2," the repository includes a container image specification.

MCP Servers: A Practical Guide to Context on Demand

Model Context Protocol, or MCP, is how an AI client asks for context instead of waiting for you to shuttle it in. Clients can already expose open files, workspace context, and sometimes terminal output. For remote services, databases, CI runs, and output from another shell, you still feed things in by hand. MCP gives the client a shared protocol to discover tools, read resources, and reach those systems directly. If the LLM is doing the reasoning, an MCP server is the part that lets it look around and, when you allow it, act on current state.

The model doesn't have to rely only on whatever you remembered to supply it. It can ask for current state, call a tool you exposed, and come back with something closer to evidence.

Less autocomplete. Less guessing. More "I checked x and y, and here is what I found."

MCP is an open protocol for connecting AI applications to the systems around them.

An MCP server can expose a few kinds of things:

  • Tools: actions the model can ask to run, such as read_logs, run_tests, or a query you wrapped instead of exposing raw shell.
  • Resources: readable context, such as repository metadata, service health, runbooks, or log files.
  • Prompts: reusable prompt templates that package common workflows.

An MCP client, such as VS Code or Cursor, connects to one or more servers and surfaces those capabilities to the model.

Old Workflow MCP Workflow
Paste a log into a chat The model calls a read_logs tool
Explain your repository The client provides workspace context
Paste command output The server returns structured tool output
Ask the model to guess state The model asks the system for current state
Manually perform every action The model proposes or invokes approved tool calls

MCP doesn't make a model omniscient. It gives the model a way to ask for context and use the actions you chose to expose. That matters more once you've lived through the manual context loop a few dozen times.

Generating a Python SDK from an OpenAPI Specification

If your API ships an OpenAPI spec, OpenAPI Generator can build the Python client from it. You don't have to hand-write models and endpoint wrappers.

Most tutorials stop at generate. After that you're still choosing generator options, picking a spec with enough surface area to matter, locking dependencies, running smoke tests, and keeping the repo sane when the spec changes again.

This walkthrough uses a compact OpenAPI 3.1 example. I use Astral uv for lock, sync, test, and build because that's what I run locally. The generator config and generate steps are the same with pip or Poetry.

Requests vs. HTTPX: Choosing a Python HTTP Client

requests earned its reputation honestly. For a long time, if you needed to call an API, download a file, or glue two services together in Python, requests was usually the obvious answer.

Async web frameworks, high-concurrency services, and I/O-heavy workloads changed what many teams need from an HTTP client. That's where httpx starts to matter.

This post compares requests and httpx from the point of view of a Python developer who already knows requests, wants a clear explanation of what httpx adds, and doesn't want hand-wavy "modernization" advice.

The Champion: requests

requests became the standard because it removed friction. Small API. Sensible defaults. Session when you need reuse.

  • The API is small and easy to learn.
  • The defaults are simple enough for scripts and internal tools.
  • Session gives you connection reuse and shared configuration without much ceremony.
  • The ecosystem around it is huge: tutorials, examples, blog posts, Stack Overflow answers, and third-party integrations all assume you know requests.

For synchronous Python code, it's still a good library. A lot of production code doesn't need async support, doesn't need HTTP/2, and doesn't need a broader transport model. In those cases, requests remains a sensible choice.

That's why this comparison isn't really about replacing a bad tool. It's about recognizing where the older tool stops fitting as cleanly.

The Challenger: httpx

httpx looks familiar on purpose. The synchronous API feels close enough to requests that most Python developers can read it immediately. That compatibility is one of its best features, because it lowers the cost of trying it.

Its two biggest advantages are:

  • Native async and await support through httpx.AsyncClient
  • Optional HTTP/2 support on both sync and async clients

Those aren't cosmetic differences.

Async support means httpx fits naturally into frameworks like FastAPI, Starlette, and any other async application that shouldn't block the event loop on outbound network calls. HTTP/2 support means one client connection can handle multiplexed requests when the remote server supports it, which can matter for high-concurrency workloads.

You opt in with httpx.Client(http2=True) or httpx.AsyncClient(http2=True), and the server still has to support HTTP/2 for you to get it.

httpx also pushes users toward safer networking defaults. The biggest example is timeouts: requests doesn't time out unless you ask it to, while httpx enforces timeouts by default.

Nginx: A Practical Deep Dive

Nginx ends up in front of a surprising amount of web traffic. You might not think much about it until you have to troubleshoot a redirect loop, a broken certificate renewal, or an application that only fails once it sits behind a proxy.

Static sites, application servers, APIs, internal tools, container platforms, and CDN origins often have Nginx somewhere out front. People keep using it because it's fast, it's predictable, and it makes you declare what the server should do.

This guide walks through the part that matters in practice: what Nginx is good at, how its config model works, how to install it, how to serve a site, how to terminate TLS, how to replace .htaccess style behavior the Nginx way, and how to use it as a reverse proxy and a basic load balancer.

Nginx is a web server, reverse proxy, and load balancer. In a modern stack it usually does one or more of these jobs:

  • Serves static files directly.
  • Terminates HTTPS connections.
  • Redirects HTTP to HTTPS.
  • Proxies requests to application servers.
  • Balances traffic across multiple backend services.
  • Adds request and response headers.
  • Buffers slow clients away from backend applications.
  • Enforces simple access control rules.
  • Caches upstream responses when configured to do so.

It's especially good at static assets, reverse proxying, and a lot of concurrent connections. It also works well as the boring edge layer in front of applications written in Node.js, Python, Go, Ruby, PHP, Java, or anything else that can listen on a port.

The simplest mental model:

graph TD
    A[Client Browser] -->|HTTPS Request| B[Nginx]
    B -->|HTTP Request to Local App| C[Backend Application]

Nginx doesn't need to understand your application. It just needs to accept the request, apply the rules you gave it, and either serve the response or pass the request upstream.

Nginx became popular because it handles several common infrastructure problems without much drama:

  • It handles many concurrent connections efficiently.
  • It serves static files quickly.
  • It's a solid reverse proxy.
  • It's good at shielding backend apps from awkward client behavior.
  • It centralizes TLS, redirects, headers, and access control.
  • It can load balance traffic without a separate appliance.
  • Its configuration is explicit and reviewable.

Nginx expects configuration in known files, not hidden per-directory overrides inside the document root. That makes behavior easier to inspect, version, test, and deploy.

VMware Workstation and Fusion 26H1 Release

VMware Workstation Pro and Fusion Pro 26H1 shipped recently, and it's a fairly quiet release.

The biggest change in Workstation Pro 26H1 is the move to a fully 64-bit architecture on Windows. Operating systems and hardware moved on from 32-bit limitations a long time ago, but parts of Workstation's underlying components were still dragging legacy dependencies forward.

The 26H1 release finally drops them. Installers, services, libraries, and application binaries now operate entirely in a 64-bit environment. It's a cleanup of years of technical debt on the Windows side.