Wednesday, 14 May 2025

Pitfalls of the Latest Tag in Deployments and How SBOM Tools Can Help

The Problem with Using the latest Tag

Using the latest tag in your deployments might seem convenient, but it brings a host of problems that can undermine stability and traceability. Here’s why:

  • Lack of Version Control: The latest tag automatically pulls the most recent version of an image. This means you might unknowingly deploy a new version without properly testing it, leading to unexpected failures.
  • Reproducibility Issues: Since the latest tag can change over time, reproducing a bug or incident becomes challenging. You might end up debugging a version that is no longer the same as the one originally deployed.
  • Deployment Drift: Multiple environments (development, staging, production) can end up running different versions even if they all reference latest. This drift breaks the consistency needed for reliable deployments.
  • Lack of Visibility: When things go wrong, it’s hard to know which version is actually running, as latest does not directly indicate a specific build or commit.

How SBOM Tools Like Grype Can Help

Software Bill of Materials (SBOM) tools, such as Grype, are invaluable for overcoming the challenges posed by the latest tag and for managing software throughout its lifecycle. These tools enhance visibility, security, and consistency from build to production.

1. Build Phase: Secure and Compliant Images

  • Automated Vulnerability Scanning: Grype can be integrated into CI/CD pipelines to automatically generate SBOMs and identify vulnerabilities before deployment.
  • Dependency Management: Track dependencies and versions directly from the build process, allowing you to catch outdated or vulnerable libraries early.
  • Compliance Checks: SBOM tools ensure your builds meet internal and external security policies.

2. Deployment Phase: Verifying What You Ship

  • Image Verification: Grype helps confirm that the deployed image by checking hashes and versions.
  • Artifact Integrity: SBOMs can be signed and stored, providing verifiable evidence of what was deployed.
  • Version Locking: Using specific tags linked to SBOMs ensures consistency across environments.

3. Production Phase: Ongoing Monitoring and Maintenance

  • Continuous Vulnerability Scans: Regularly scan running containers to detect new vulnerabilities in your deployed software.
  • Lifecycle Management: SBOMs enable you to track when components reach end-of-life or become deprecated.
  • Audit and Compliance: Maintain an accurate record of all software versions and components running in production, helping with regulatory compliance.

Best Practices to Avoid the latest Pitfall

  • Use Specific Tags: Tag images with a version number or a commit hash to maintain consistency and traceability.
  • Automated SBOM Generation: Integrate tools like Grype in your CI/CD pipeline to automatically generate and store SBOMs for every build.
  • Regular Scanning: Continuously monitor your deployed containers with SBOM tools to catch vulnerabilities as they arise.

Conclusion: Gaining Control and Visibility

By avoiding the use of the latest tag and incorporating SBOM tools like Grype, you significantly improve the stability and security of your deployments. These tools not only mitigate the risks associated with version ambiguity but also enhance the entire software lifecycle—from build to production. With SBOMs, you gain control, maintain visibility, and ensure consistent, secure deployments.

Monday, 12 May 2025

From Fragile to Factory: Building Containers the Right Way

Containers promised us portability, consistency, and scalable deployments. But many development teams are still stuck using them like lightweight VMs — crafting images by hand, running docker exec, and treating the container itself as a mutable environment.

This isn’t just inefficient. It’s dangerous.

If you care about security, reliability, and scalability, it’s time to rethink your container lifecycle. Here's how to do it right — from build to runtime.

1. The Problem with Manual Container Workflows

You’ve probably seen (or written) something like this:

docker run -it base-image bash
# Install some tools, tweak configs
exit
docker commit ...
docker save -o myimage.tar

This “build” process might work once — but it’s:

  • Opaque: Nobody knows what’s inside that image.
  • Irreproducible: You can’t rebuild the exact same image.
  • Insecure: Unscanned, unsigned, unverifiable.
  • Unscalable: CI/CD pipelines can't run you debugging inside a shell.

Manual builds destroy the auditability and automation that containers are supposed to give you. Worse, they make it nearly impossible to maintain a clean supply chain.

2. Why Dockerfiles (and Multi-stage Builds) Matter

A Dockerfile is more than a convenience — it’s your source of truth.

Benefits of Dockerfile-driven builds:

  • 100% declarative: Every step is visible and version-controlled
  • Portable: Runs the same in CI, your laptop, or a Kubernetes cluster
  • Automatable: Can be rebuilt and scanned automatically
  • Secureable: Works with image signing, SBOM generation, and policy enforcement

Want to go further? Use multi-stage builds to separate build tools from runtime dependencies — reducing image size and attack surface.

# Build stage
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp

# Runtime stage
FROM alpine
COPY --from=builder /app/myapp /usr/bin/myapp
CMD ["myapp"]

This pattern results in much smaller, leaner containers — which improves startup time, security posture, and efficiency. As highlighted in Container Size Matters, reducing image bloat isn’t just aesthetic — it has real operational and security benefits.

3. Automate Everything: CI/CD as Your Factory Line

Your container builds should run without human hands involved:

  • ✅ Triggered by source commits or tags
  • ✅ Build from Dockerfile
  • ✅ Scan for vulnerabilities
  • ✅ Generate SBOMs
  • ✅ Sign images (e.g., with Sigstore)
  • ✅ Push to a trusted registry

This builds a secure, traceable software supply chain — not just an image blob.

If you’re using docker save, docker load, or dragging tarballs around, you’re not ready for scale.

4. Exec Into Containers? That’s a Red Flag

“Just SSH in and fix it” doesn’t belong in container-native thinking.

Running docker exec to fix a container is:

  • A band-aid, not a solution
  • A breach of immutability
  • A sign your build process is broken

If you need to exec into a container to troubleshoot, fine — but never use that session as part of your build. Fix the Dockerfile or CI pipeline instead.

5. Stateless by Design: Where Does Your State Live?

Containers should be:

  • 🔁 Restartable
  • 📦 Replaceable
  • 🚀 Scalable

But if you're writing logs, cache, or app state inside /var/lib/myapp, you’re asking for trouble.

Best practice:

  • Store logs in external logging systems (e.g., Fluent Bit → ELK or Loki)
  • Store persistent data in mounted volumes
  • Use Kubernetes PersistentVolumeClaims or cloud-native block storage

This way, your containers can be destroyed and recreated at will, without loss of data or downtime.

6. Putting It All Together

A modern container pipeline looks like this:

  1. ✅ Git push triggers automated build
  2. 🐳 Dockerfile builds image using CI
  3. 🔍 Image scanned and signed
  4. 📦 Stored in trusted registry
  5. 🚀 Deployed via declarative manifest (K8s, Helm, etc.)
  6. 💾 Volumes handle persistence, not the container

No manual tweaks. No snowflake images. Full lifecycle awareness.

Conclusion: Treat Containers Like Code, Not Pets

Containers are a critical part of your software supply chain. If you’re still crafting them by hand or baking in state, you’re setting yourself up for security risks, outages, and scaling pain.

Move to:

  • 📄 Declarative Dockerfiles
  • 🤖 Automated CI builds
  • 🔐 Secure, traceable image delivery
  • 🗃️ Clean separation of state and compute

This is how you go from “it works on my machine” to “we ship production-ready containers every commit.”

Tuesday, 6 May 2025

One Container to Host Them All

What if you could run a single container to host your .deb, .rpm, Docker images, and Helm charts—without the overhead of setting up a full artifact manager like Nexus or Artifactory?

This idea started with a simple goal: reduce complexity in self-hosted infrastructure. I needed a unified repo that would be:

  • Lightweight: Run anywhere with Docker
  • Self-contained: No external dependencies
  • Multi-purpose: Support the most common packaging formats in one place

So I built exactly that.

A Unified Artifact Repository

The container I created serves as a multi-protocol artifact repository. It handles:

  • APT (Debian) repositories for .deb packages
  • YUM/DNF (Red Hat) repositories for .rpm packages
  • Helm chart repositories for Kubernetes deployments
  • Docker Registry v2 compatible endpoints for image hosting

All in a single Dockerfile.

No glue scripts. No multiple services. Just one clean container that can be dropped into any GitOps or CI/CD pipeline.

GitLab-Compatible, But Not GitLab-Bound

While I tested and showcased this container using GitLab CI/CD (as seen in my recent posts), it's not tied to GitLab. It works anywhere Docker containers run—whether that’s a home lab, a cloud VM, or inside Kubernetes.

Why This Matters

This project was born out of real pain: managing too many different tools for different artifact types. If you've ever had to set up S3-backed Helm repos, host private Docker registries, or deal with GPG-signed .deb packages—you know the sprawl.

By consolidating these into a single endpoint, you:

  • Reduce maintenance surface area
  • Improve developer onboarding
  • Simplify CI/CD configurations
  • Own your own supply chain

How to Use It

For full setup details, configuration, and source code, visit the GitLab repo:
👉 gitlab.com/jlcox70/repository-server

Running Your Own Private Container Registry

Container registries are a critical piece of any modern development and deployment workflow. They let you store, manage, and distribute container images efficiently. But if you're building proprietary software, handling sensitive data, or just don't trust public registries for your infrastructure, you need something more secure—something private.

Why Go Private?

Public registries like Docker Hub are great for sharing open-source images. But for teams working on internal tools, client-sensitive code, or systems not meant for public exposure, putting images on a public registry—even unintentionally—can be a major security risk. That's where a private container registry comes in.

Options for a Private Registry

You’ve got several routes, depending on your stack, resources, and desired level of control:

1. Docker Registry (open-source)

The simplest option: run your own Docker Registry instance.

docker run -d -p 5000:5000 --name registry registry:2

By default, this exposes an insecure, unauthenticated registry. Not great for production—but it’s a good starting point.

To secure it:

  • Use HTTPS (terminate SSL with Nginx or Caddy)
  • Add Basic Auth using htpasswd
  • Store images in S3, Azure Blob, or local volume

2. Harbor

A CNCF-graduated project with more features than Docker Registry, including:

  • Role-based access control
  • LDAP/AD integration
  • Image vulnerability scanning
  • Web UI and audit logs

It’s more work to set up but much more enterprise-ready.

3. Registry as Part of GitOps Tools

GitLab, GitHub, and AWS CodeArtifact all offer private container registries baked into their platforms. These are great if you’re already invested in those ecosystems and want tight CI/CD integration without maintaining additional infrastructure.

One Caveat: Base Images Still Come From Somewhere

Running a private registry is excellent for your images—but it doesn't mean you can or should isolate completely from public registries.

Most Dockerfiles start with something like:

FROM python:3.12-slim

That python image? It's still being pulled from Docker Hub or another public source. If your builds or base images rely on public registries and those sources change, get rate-limited, or go offline, your pipelines break.

To handle this well, consider:

  • Image mirroring tools (e.g. crane, skopeo, Harbor’s replication jobs)
  • Scheduled updates to pull newer tagged base images and push them to your private registry
  • Dependency control policies so you know what you’re inheriting and when

If you don't automate this, you'll either miss important updates or reintroduce the very risk you were trying to avoid by going private.

Best Practices

  • Security: Always serve your registry over HTTPS and require authentication.
  • Access Control: Use role-based policies to limit who can push/pull.
  • Retention Policies: Implement cleanup tasks for old tags and unused layers.
  • Backups: Even registries need DR plans—store them in S3 or similar durable storage.
  • Monitoring: Hook into Prometheus/Grafana or similar for metrics and health checks.

Final Thoughts

Running your own container registry isn't just about hiding your code—it's about having full control over the lifecycle of your images. From compliance to reliability, a private registry is often the better long-term solution.

But it's not a one-and-done setup. If you're using public base images, you'll need a plan to mirror, track, and update them. Without that, you're either vulnerable to upstream changes—or stuck on stale images.

Is the Virtual Appliance Model Dying?

Is the Virtual Appliance Model Dying?

For over a decade, virtual appliances—pre-packaged VMs delivered as OVAs—were the standard for deploying infrastructure software. They offered control, predictability, and a “batteries-included” experience for customers. But in 2025, we have to ask a hard question: Does this model still make sense in a world of Kubernetes, GitOps, and DevSecOps?

The Hidden Cost of VM Fleets

It’s easy to overlook the true operational cost of VM-based appliances until you’re managing dozens—or even hundreds—of them. Each one typically runs a full operating system, often with services and components that aren’t even used. That leads to:

  • High resource overhead: CPU, memory, and disk are consumed by the OS and non-essential services.
  • OS patching burden: Every VM needs regular updates to maintain security compliance.
  • Version sprawl: Inconsistent upgrades across a fleet create drift and lifecycle complexity.

Enter Kubernetes: Lightweight, Declarative, and Scalable

Kubernetes offers a fundamentally different approach. Instead of shipping full-stack VMs, vendors can deliver containerized microservices that run on a shared cluster. This has several major advantages:

1. Reduced OS Overhead

Containers use minimal base images and eliminate the need for full guest OSes. Teams manage host nodes centrally, reducing the patching surface and infrastructure complexity.

2. Simplified Security and Compliance

Fewer OS components mean a smaller attack surface. Container image updates can be automated through CI/CD and GitOps pipelines, improving security posture.

3. Dynamic Scaling and Resilience

With Horizontal Pod Autoscaling (HPA), services can scale automatically based on real-time load—a stark contrast to the manual scaling of VMs.

4. DevSecOps and CI/CD Integration

Declarative configs, version-controlled deployments, and observability hooks make containers first-class citizens in the modern DevSecOps pipeline.

VM-Based Appliance vs Kubernetes-Native Architecture

Aspect VM-Based Appliance Kubernetes-Native
Deployment Unit Full Virtual Machine Container in Pod
OS Overhead High – full OS per VM Low – minimal OS layers
Patch Management Manual or scripted per VM Image updates via pipeline
Scalability Static or manual scaling Dynamic (e.g., HPA)
Update Mechanism Manual patching & upgrades Declarative, automated (GitOps)
Security Surface Larger surface due to full OS Smaller surface with minimal base images
Resource Efficiency Lower – each VM includes unused components Higher – optimized runtime footprint

What Should Vendors Do?

This shift isn't theoretical—it’s already happening. Forward-thinking vendors are rebuilding their delivery models to be Kubernetes-native. Others are layering containers awkwardly inside VMs, creating unnecessary complexity.

It’s time to rethink.

Shipping your software as a monolithic VM may have made sense in 2010. But in today’s world—where infrastructure is code, automation is table stakes, and resilience is built-in—not modernizing means becoming obsolete.

Conclusion: A Leaner Future

The virtual appliance model isn’t dead—but it's dying. The future of infrastructure delivery is lighter, leaner, and more automated. Kubernetes isn’t just a new platform; it’s a new mindset.

Vendors that embrace this shift will benefit from faster deployment cycles, simpler operations, better security, and happier customers.

Those that don’t? They’ll be left patching VMs while their competitors scale with YAML.

When Containers Get Trapped: The Lifecycle Problem in Vendor-Supplied VM Appliances

🧱 When Containers Get Trapped: The Lifecycle Problem in Vendor-Supplied VM Appliances

In recent years, we’ve seen a major shift in how vendors deliver software. The traditional binary installers and setup scripts have given way to pre-packaged virtual appliances — OVAs or similar formats — that are intended to drop into your virtual infrastructure and "just work." On paper, that sounds great. But more and more, I'm seeing a worrying pattern: these appliances are bundling container-based workloads in ways that introduce serious lifecycle and supply chain problems.

🛠 The Appliance Model: Easy on Day One, Hard Forever After

A vendor-supplied OVA gives you a turnkey experience — you deploy the VM, power it on, and the application is running. It often comes with everything preconfigured: the OS, services, app runtime, and now more frequently, a container runtime with embedded container workloads.

On the surface, this looks like progress. Containers are lightweight, portable, and make dependency management easier. But there's a big difference between running containerized software the right way and just stuffing it inside a virtual machine and calling it a day.

🐳 The Hidden Runtime: Containers Without Control

Here's the pattern I'm seeing:

  • The VM boots up with Docker or containerd pre-installed.
  • A container image is already present or is loaded locally at first boot.
  • The image was built using a public base (Alpine, Ubuntu, etc.), with layers added by the vendor.
  • There’s no registry, no image tag, no version control, and no way to rebuild or update just the container.

This setup creates an illusion of modularity — you think you’re running a containerized app, but you’ve lost nearly all of the benefits of containers.

🔐 Supply Chain and Security Concerns

Containers were meant to solve supply chain traceability, not obscure it. When a container image is built properly, each layer can be signed, scanned, and audited. But in the virtual appliance model I’ve been seeing, the base image is untracked, the customizations are undocumented, and no SBOM is provided.

⚙️ Lifecycle Management: What Lifecycle?

The worst part of this is the lifecycle story. You can’t patch, update, or rollback without vendor support. You’re stuck with a frozen artifact, and the only fix is often a full re-deploy of the entire VM — including any manual tuning you’ve done post-deployment.

🧩 What's the Better Way?

This isn’t an unsolvable problem — but vendors need to start treating containers as first-class citizens, not afterthoughts.

For vendors:
  • Use proper container registries with tagging and versioning.
  • Keep runtimes decoupled from the VM image.
  • Provide SBOMs and provenance for every image.
  • Patch the container image, not just the VM appliance.
For consumers:
  • Inspect what’s running inside the VM.
  • Ask your vendors about update strategies and base image security.
  • Push for lifecycle-aware packaging and updates.

🧠 Final Thoughts

Containers are a powerful tool — but only when used transparently and responsibly. When they’re hidden inside prebuilt virtual machines without lifecycle or supply chain clarity, they become a liability.

Sometimes, the slickest deployment hides the biggest mess.

Running Your Own Private Container Registry

Container registries are a critical piece of any modern development and deployment workflow. They let you store, manage, and distribute container images efficiently. But if you're building proprietary software, handling sensitive data, or just don't trust public registries for your infrastructure, you need something more secure—something private.

Why Go Private?

Public registries like Docker Hub are great for sharing open-source images. But for teams working on internal tools, client-sensitive code, or systems not meant for public exposure, putting images on a public registry—even unintentionally—can be a major security risk. That's where a private container registry comes in.

Options for a Private Registry

You’ve got several routes, depending on your stack, resources, and desired level of control:

1. Docker Registry (open-source)

The simplest option: run your own Docker Registry instance.

docker run -d -p 5000:5000 --name registry registry:2

By default, this exposes an insecure, unauthenticated registry. Not great for production—but it’s a good starting point.

To secure it:

  • Use HTTPS (terminate SSL with Nginx or Caddy)
  • Add Basic Auth using htpasswd
  • Store images in S3, Azure Blob, or local volume

2. Harbor

A CNCF-graduated project with more features than Docker Registry, including:

  • Role-based access control
  • LDAP/AD integration
  • Image vulnerability scanning
  • Web UI and audit logs

It’s more work to set up but much more enterprise-ready.

3. Registry as Part of GitOps Tools

GitLab, GitHub, and AWS CodeArtifact all offer private container registries baked into their platforms. These are great if you’re already invested in those ecosystems and want tight CI/CD integration without maintaining additional infrastructure.

One Caveat: Base Images Still Come From Somewhere

Running a private registry is excellent for your images—but it doesn't mean you can or should isolate completely from public registries.

Most Dockerfiles start with something like:

FROM python:3.12-slim

That python image? It's still being pulled from Docker Hub or another public source. If your builds or base images rely on public registries and those sources change, get rate-limited, or go offline, your pipelines break.

To handle this well, consider:

  • Image mirroring tools (e.g. crane, skopeo, Harbor’s replication jobs)
  • Scheduled updates to pull newer tagged base images and push them to your private registry
  • Dependency control policies so you know what you’re inheriting and when

If you don't automate this, you'll either miss important updates or reintroduce the very risk you were trying to avoid by going private.

Best Practices

  • Security: Always serve your registry over HTTPS and require authentication.
  • Access Control: Use role-based policies to limit who can push/pull.
  • Retention Policies: Implement cleanup tasks for old tags and unused layers.
  • Backups: Even registries need DR plans—store them in S3 or similar durable storage.
  • Monitoring: Hook into Prometheus/Grafana or similar for metrics and health checks.

Final Thoughts

Running your own container registry isn't just about hiding your code—it's about having full control over the lifecycle of your images. From compliance to reliability, a private registry is often the better long-term solution.

But it's not a one-and-done setup. If you're using public base images, you'll need a plan to mirror, track, and update them. Without that, you're either vulnerable to upstream changes—or stuck on stale images.


For a real-world implementation and deeper dive, check out this walkthrough of a custom repo server I built to solve exactly these challenges in a lightweight, scriptable way.